add servant and king explanations
This commit is contained in:
parent
9b5e19f39e
commit
d9c0ed9630
|
@ -1,12 +1,37 @@
|
||||||
import * as T from "../types";
|
import * as T from "../types";
|
||||||
import Select from "react-select";
|
import { StyleHTMLAttributes } from "react";
|
||||||
|
import Select, { StylesConfig } from "react-select";
|
||||||
import AsyncSelect from "react-select/async";
|
import AsyncSelect from "react-select/async";
|
||||||
import {
|
import {
|
||||||
makeSelectOption,
|
makeSelectOption,
|
||||||
makeVerbSelectOption,
|
makeVerbSelectOption,
|
||||||
zIndexProps,
|
|
||||||
} from "./np-picker/picker-tools";
|
} from "./np-picker/picker-tools";
|
||||||
|
|
||||||
|
const customStyles: StylesConfig = {
|
||||||
|
menuPortal: (base) => ({
|
||||||
|
...base,
|
||||||
|
zIndex: 99999,
|
||||||
|
}),
|
||||||
|
menu: (base) => ({
|
||||||
|
...base,
|
||||||
|
zIndex: 999999,
|
||||||
|
}),
|
||||||
|
option: (provided, state) => ({
|
||||||
|
...provided,
|
||||||
|
padding: "10px 5px",
|
||||||
|
}),
|
||||||
|
input: (base) => ({
|
||||||
|
...base,
|
||||||
|
padding: 0,
|
||||||
|
}),
|
||||||
|
singleValue: (provided, state) => {
|
||||||
|
const opacity = state.isDisabled ? 0.5 : 1;
|
||||||
|
const transition = 'opacity 300ms';
|
||||||
|
|
||||||
|
return { ...provided, opacity, transition };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
|
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
|
||||||
entries: E[]
|
entries: E[]
|
||||||
} | {
|
} | {
|
||||||
|
@ -18,9 +43,10 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
|
||||||
name: string | undefined,
|
name: string | undefined,
|
||||||
isVerbSelect?: boolean,
|
isVerbSelect?: boolean,
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions,
|
||||||
|
style?: StyleHTMLAttributes<HTMLDivElement>,
|
||||||
}) {
|
}) {
|
||||||
|
const divStyle = props.style || { width: "13rem" };
|
||||||
const placeholder = "entries" in props ? "Select…" : "Search Pashto";
|
const placeholder = "entries" in props ? "Select…" : "Search Pashto";
|
||||||
const minWidth = "9rem";
|
|
||||||
function makeOption(e: E | T.DictionaryEntry) {
|
function makeOption(e: E | T.DictionaryEntry) {
|
||||||
if ("entry" in e) {
|
if ("entry" in e) {
|
||||||
return (props.isVerbSelect ? makeVerbSelectOption : makeSelectOption)(e, props.opts);
|
return (props.isVerbSelect ? makeVerbSelectOption : makeSelectOption)(e, props.opts);
|
||||||
|
@ -42,16 +68,17 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
props.onChange(s);
|
props.onChange(s);
|
||||||
}
|
}
|
||||||
return <div style={{ minWidth }}>
|
return <div style={divStyle}>
|
||||||
<AsyncSelect
|
<AsyncSelect
|
||||||
|
styles={customStyles}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
value={value}
|
value={value}
|
||||||
|
// @ts-ignore
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
defaultOptions={[]}
|
defaultOptions={[]}
|
||||||
loadOptions={options}
|
loadOptions={options}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
{...zIndexProps}
|
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -76,15 +103,16 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: ({
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
props.onChange(s);
|
props.onChange(s);
|
||||||
}
|
}
|
||||||
return <div style={{ minWidth }}>
|
return <div style={divStyle}>
|
||||||
<Select
|
<Select
|
||||||
|
styles={customStyles}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
value={value}
|
value={value}
|
||||||
|
// @ts-ignore
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
{...zIndexProps}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ function NPPicker(props: {
|
||||||
const clearButton = !props.cantClear
|
const clearButton = !props.cantClear
|
||||||
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
|
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
|
||||||
: <div></div>;
|
: <div></div>;
|
||||||
return <div>
|
return <div style={{ minWidth: "9rem" }}>
|
||||||
{!npType && <div className="text-center mt-3">
|
{!npType && <div className="text-center mt-3">
|
||||||
<div className="h6 mr-3">
|
<div className="h6 mr-3">
|
||||||
Choose NP
|
Choose NP
|
||||||
|
|
|
@ -3,7 +3,7 @@ import VerbPicker from "./VerbPicker";
|
||||||
import TensePicker from "./TensePicker";
|
import TensePicker from "./TensePicker";
|
||||||
import VPDisplay from "./VPDisplay";
|
import VPDisplay from "./VPDisplay";
|
||||||
import ButtonSelect from "../ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
|
import { Modal } from "react-bootstrap";
|
||||||
import {
|
import {
|
||||||
isInvalidSubjObjCombo,
|
isInvalidSubjObjCombo,
|
||||||
} from "../../lib/phrase-building/vp-tools";
|
} from "../../lib/phrase-building/vp-tools";
|
||||||
|
@ -11,15 +11,14 @@ import * as T from "../../types";
|
||||||
import ChartDisplay from "./ChartDisplay";
|
import ChartDisplay from "./ChartDisplay";
|
||||||
import useStickyState from "../../lib/useStickyState";
|
import useStickyState from "../../lib/useStickyState";
|
||||||
import { makeVPSelectionState } from "./verb-selection";
|
import { makeVPSelectionState } from "./verb-selection";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { getKingAndServant } from "../../lib/phrase-building/render-vp";
|
import { getKingAndServant } from "../../lib/phrase-building/render-vp";
|
||||||
import { isPastTense } from "../../lib/phrase-building/vp-tools";
|
import { isPastTense } from "../../lib/phrase-building/vp-tools";
|
||||||
import VPExplorerQuiz from "./VPExplorerQuiz";
|
import VPExplorerQuiz from "./VPExplorerQuiz";
|
||||||
import { switchSubjObj } from "../../lib/phrase-building/vp-tools"
|
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
|
||||||
|
|
||||||
const kingEmoji = "👑";
|
|
||||||
const servantEmoji = "🙇♂️";
|
|
||||||
|
|
||||||
|
const kingIcon = <i className="mx-1 fas fa-crown" />;
|
||||||
|
const servantIcon = <i className="mx-1 fas fa-male" />;
|
||||||
|
|
||||||
// TODO: make answerFeedback emojis appear at random translate angles a little bit
|
// TODO: make answerFeedback emojis appear at random translate angles a little bit
|
||||||
// add energy drinks?
|
// add energy drinks?
|
||||||
|
@ -61,7 +60,9 @@ export function VPExplorer(props: {
|
||||||
},
|
},
|
||||||
"verbExplorerMode",
|
"verbExplorerMode",
|
||||||
);
|
);
|
||||||
|
const [showingKingExplanation, setShowingKingExplanation] = useState<"subject" | "object" | false>(false);
|
||||||
|
const [showingServantExplanation, setShowingServantExplanation] = useState<"subject" | "object" | false>(false);
|
||||||
|
const roles = getKingAndServant(isPastTense(vps.verb.tense), vps.verb.transitivity !== "intransitive");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVps(oldVps => {
|
setVps(oldVps => {
|
||||||
if (mode === "quiz") {
|
if (mode === "quiz") {
|
||||||
|
@ -139,7 +140,9 @@ export function VPExplorer(props: {
|
||||||
{mode !== "quiz" && <div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
|
{mode !== "quiz" && <div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
|
||||||
{mode === "phrases" && <>
|
{mode === "phrases" && <>
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<div className="h5 text-center">Subject {showRole(vps, "subject")}</div>
|
{roles.king === "subject"
|
||||||
|
? <div className="h5 text-center clickable" onClick={() => setShowingKingExplanation("subject")}>Subject {kingIcon}</div>
|
||||||
|
: <div className="h5 text-center clickable" onClick={() => setShowingKingExplanation("subject")}>Subject {servantIcon}</div>}
|
||||||
<NPPicker
|
<NPPicker
|
||||||
{..."getNounByTs" in props ? {
|
{..."getNounByTs" in props ? {
|
||||||
getNounByTs: props.getNounByTs,
|
getNounByTs: props.getNounByTs,
|
||||||
|
@ -157,7 +160,9 @@ export function VPExplorer(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{vps.verb && (vps.verb.object !== "none") && <div className="my-2">
|
{vps.verb && (vps.verb.object !== "none") && <div className="my-2">
|
||||||
<div className="h5 text-center">Object {showRole(vps, "object")}</div>
|
{roles.king === "object"
|
||||||
|
? <div className="h5 text-center clickable" onClick={() => setShowingKingExplanation("object")}>Object {kingIcon}</div>
|
||||||
|
: <div className="h5 text-center clickable" onClick={() => setShowingServantExplanation("object")}>Object {servantIcon}</div>}
|
||||||
{(typeof vps.verb.object === "number")
|
{(typeof vps.verb.object === "number")
|
||||||
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
|
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
|
||||||
: <NPPicker
|
: <NPPicker
|
||||||
|
@ -189,6 +194,64 @@ export function VPExplorer(props: {
|
||||||
{mode === "phrases" && <VPDisplay VP={vps} opts={props.opts} />}
|
{mode === "phrases" && <VPDisplay VP={vps} opts={props.opts} />}
|
||||||
{mode === "charts" && <ChartDisplay VS={vps.verb} opts={props.opts} />}
|
{mode === "charts" && <ChartDisplay VS={vps.verb} opts={props.opts} />}
|
||||||
{mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />}
|
{mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />}
|
||||||
|
<Modal show={!!showingKingExplanation} onHide={() => setShowingKingExplanation(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>About the King {kingIcon}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
In this tense/form, the {showingKingExplanation} is the <strong>king</strong> {kingIcon} of the phrase. That means that:
|
||||||
|
<ul className="mt-2">
|
||||||
|
<li>
|
||||||
|
<div>It controls the verb conjugation. The verb agrees with the gender and number of the king.</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>👍 It <strong>can</strong> be removed / left out from the phrase.</div>
|
||||||
|
<div className="text-muted">(You can kill the king)</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>🙅♂️ It <strong>cannot be</strong> shrunk it into a <a target="_blank" rel="noreferrer" href="https://grammar.lingdocs.com/pronouns/pronouns-mini/">mini-pronoun</a>. 👶</div>
|
||||||
|
<div className="text-muted">(You can't shrink the king)</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Mnemonic for shortening phrases:</p>
|
||||||
|
<p className="text-muted">"🚫 Kill the king 👶 Shrink the servant"</p>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<button type="button" className="btn btn-primary clb" onClick={() => setShowingKingExplanation(false)}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
|
<Modal show={!!showingServantExplanation} onHide={() => setShowingServantExplanation(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>About the Servant {servantIcon}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<p>
|
||||||
|
In this tense/form, the {showingServantExplanation} is the <strong>servant</strong> {servantIcon} of the phrase. That means that:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
It does not affect the conjugation of the verb. That's the king's job.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>👍 It <strong>can</strong> shrink it into a <a target="_blank" rel="noreferrer" href="https://grammar.lingdocs.com/pronouns/pronouns-mini/">mini-pronoun</a>. 👶</div>
|
||||||
|
<div className="text-muted">(You can <strong>shrink the servant</strong>)</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>🙅♂️ It <strong>cannot</strong> be removed / left out of the phrase</div>
|
||||||
|
<div className="text-muted">(You can't kill the servant)</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Mnemonic for shortening phrases:</p>
|
||||||
|
<p className="text-muted">"🚫 Kill the king 👶 Shrink the servant"</p>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<button type="button" className="btn btn-primary clb" onClick={() => setShowingServantExplanation(false)}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,14 +264,4 @@ function hasPronounConflict(subject: T.NPSelection | undefined, object: undefine
|
||||||
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
|
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRole(VP: T.VPSelection, member: "subject" | "object") {
|
|
||||||
const roles = getKingAndServant(
|
|
||||||
isPastTense(VP.verb.tense),
|
|
||||||
VP.verb.transitivity !== "intransitive",
|
|
||||||
);
|
|
||||||
return VP
|
|
||||||
? <span className="ml-2">
|
|
||||||
{(roles.king === member ? kingEmoji : roles.servant === member ? servantEmoji : "")}
|
|
||||||
</span>
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue