refactoring to allow for dictionary lookup in phrase builder

This commit is contained in:
lingdocs 2022-04-08 11:54:47 +05:00
parent 8191b48f05
commit 759df4811e
6 changed files with 195 additions and 120 deletions

View File

@ -13,7 +13,7 @@ function EntrySelect<E extends T.DictionaryEntry | VerbEntry>(props: ({
entries: E[] entries: E[]
} | { } | {
searchF: (search: string) => E[], searchF: (search: string) => E[],
getByTs: (ts: number) => E, getByTs: (ts: number) => E | undefined,
}) & { }) & {
value: E | undefined, value: E | undefined,
onChange: (value: E | undefined) => void, onChange: (value: E | undefined) => void,

View File

@ -6,83 +6,93 @@ import {
InlinePs, InlinePs,
Types as T, Types as T,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { useState } from "react"; // import { useState } from "react";
import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates"; // import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates";
import EntrySelect from "../EntrySelect"; import EntrySelect from "../EntrySelect";
const filterOptions = [ // const filterOptions = [
{ // {
label: "1", // label: "1",
value: "1", // value: "1",
}, // },
{ // {
label: "2", // label: "2",
value: "2", // value: "2",
}, // },
{ // {
label: "3", // label: "3",
value: "3", // value: "3",
}, // },
{ // {
label: "4", // label: "4",
value: "4", // value: "4",
}, // },
{ // {
label: "5", // label: "5",
value: "5", // value: "5",
}, // },
{ // {
label: "6", // label: "6",
value: "6", // value: "6",
}, // },
]; // ];
type FilterPattern = "1" | "2" | "3" | "4" | "5" | "6"; // type FilterPattern = "1" | "2" | "3" | "4" | "5" | "6";
function nounFilter(p: FilterPattern | undefined) { // function nounFilter(p: FilterPattern | undefined) {
return p === undefined // return p === undefined
? () => true // ? () => true
: (p === "1") // : (p === "1")
? isPattern1Entry // ? isPattern1Entry
: (p === "2") // : (p === "2")
? isPattern2Entry // ? isPattern2Entry
: (p === "3") // : (p === "3")
? isPattern3Entry // ? isPattern3Entry
: (p === "4") // : (p === "4")
? isPattern4Entry // ? isPattern4Entry
: (p === "5") // : (p === "5")
? isPattern5Entry // ? isPattern5Entry
: (p === "6") // : (p === "6")
? (n: NounEntry) => (isFemNounEntry(n) && isPattern6FemEntry(n)) // ? (n: NounEntry) => (isFemNounEntry(n) && isPattern6FemEntry(n))
: () => true; // : () => true;
} // }
function NPNounPicker({ onChange, noun, nouns, clearButton, opts }: { nouns: NounEntry[], noun: NounSelection | undefined, onChange: (p: NounSelection | undefined) => void, clearButton?: JSX.Element, opts: T.TextOptions }) { function NPNounPicker(props: ({
const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined); nouns: NounEntry[],
const [showFilter, setShowFilter] = useState<boolean>(false) } | {
const nounsFiltered = nouns nouns: (s: string) => NounEntry[],
.filter(nounFilter(patternFilter)) getNounByTs: (ts: number) => NounEntry | undefined;
.sort((a, b) => (a.p.localeCompare(b.p, "af-PS"))); }) & {
noun: NounSelection | undefined,
onChange: (p: NounSelection | undefined) => void,
clearButton?: JSX.Element,
opts: T.TextOptions,
}) {
// const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined);
// const [showFilter, setShowFilter] = useState<boolean>(false)
// const nounsFiltered = props.nouns
// .filter(nounFilter(patternFilter))
// .sort((a, b) => (a.p.localeCompare(b.p, "af-PS")));
function onEntrySelect(entry: NounEntry | undefined) { function onEntrySelect(entry: NounEntry | undefined) {
if (!entry) { if (!entry) {
return onChange(undefined); return props.onChange(undefined);
} }
onChange(makeNounSelection(entry)); props.onChange(makeNounSelection(entry));
}
function handleFilterClose() {
setPatternFilter(undefined);
setShowFilter(false);
} }
// function handleFilterClose() {
// setPatternFilter(undefined);
// setShowFilter(false);
// }
return <div style={{ maxWidth: "225px", minWidth: "125px" }}> return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
<div className="d-flex flex-row justify-content-between"> <div className="d-flex flex-row justify-content-left">
{clearButton} {props.clearButton}
{(!showFilter && !(noun?.dynamicComplement)) && <div className="text-right"> {/* {(!showFilter && !(noun?.dynamicComplement)) && <div className="text-right">
<button className="btn btn-sm btn-light mb-2 text-small" onClick={() => setShowFilter(true)}> <button className="btn btn-sm btn-light mb-2 text-small" onClick={() => setShowFilter(true)}>
<i className="fas fa-filter fa-xs" /> <i className="fas fa-filter fa-xs" />
</button> </button>
</div>} </div>} */}
</div> </div>
{showFilter && <div className="mb-2 text-center"> {/* {showFilter && <div className="mb-2 text-center">
<div className="d-flex flex-row justify-content-between"> <div className="d-flex flex-row justify-content-between">
<div className="text-small mb-1">Filter by inflection pattern</div> <div className="text-small mb-1">Filter by inflection pattern</div>
<div className="clickable" onClick={handleFilterClose}>X</div> <div className="clickable" onClick={handleFilterClose}>X</div>
@ -94,54 +104,59 @@ function NPNounPicker({ onChange, noun, nouns, clearButton, opts }: { nouns: Nou
// @ts-ignore // @ts-ignore
handleChange={setPatternFilter} handleChange={setPatternFilter}
/> />
</div>} </div>} */}
{!(noun && noun.dynamicComplement) ? <div> {!(props.noun && props.noun.dynamicComplement) ? <div>
<EntrySelect <EntrySelect
value={noun?.entry} value={props.noun?.entry}
entries={nounsFiltered} {..."getNounByTs" in props ? {
getByTs: props.getNounByTs,
searchF: props.nouns
} : {
entries: props.nouns,
}}
onChange={onEntrySelect} onChange={onEntrySelect}
name="Noun" name="Noun"
opts={opts} opts={props.opts}
/> />
</div> : <div> </div> : <div>
{noun && <div> {props.noun && <div>
<div className="mb-2">Included in Dyn. Compound:</div> <div className="mb-2">Included in Dyn. Compound:</div>
<div className="mb-3 text-center"> <div className="mb-3 text-center">
<InlinePs opts={opts}> <InlinePs opts={props.opts}>
{{ p: noun.entry.p, f: noun.entry.f }} {{ p: props.noun.entry.p, f: props.noun.entry.f }}
</InlinePs> </InlinePs>
<div className="text-muted">{noun.entry.e}</div> <div className="text-muted">{props.noun.entry.e}</div>
</div> </div>
</div>} </div>}
</div>} </div>}
{noun && <div className="my-2 d-flex flex-row justify-content-around align-items-center"> {props.noun && <div className="my-2 d-flex flex-row justify-content-around align-items-center">
<div> <div>
{noun.changeGender ? <ButtonSelect {props.noun.changeGender ? <ButtonSelect
small small
options={[ options={[
{ label: "Masc", value: "masc" }, { label: "Masc", value: "masc" },
{ label: "Fem", value: "fem" }, { label: "Fem", value: "fem" },
]} ]}
value={noun.gender} value={props.noun.gender}
handleChange={(g) => { handleChange={(g) => {
if (!noun.changeGender) return; if (!props.noun || !props.noun.changeGender) return;
onChange(noun.changeGender(g)); props.onChange(props.noun.changeGender(g));
}} }}
/> : noun.gender === "masc" ? "Masc." : "Fem."} /> : props.noun.gender === "masc" ? "Masc." : "Fem."}
</div> </div>
<div> <div>
{noun.changeNumber ? <ButtonSelect {props.noun.changeNumber ? <ButtonSelect
small small
options={[ options={[
{ label: "Sing.", value: "singular" }, { label: "Sing.", value: "singular" },
{ label: "Plur.", value: "plural" }, { label: "Plur.", value: "plural" },
]} ]}
value={noun.number} value={props.noun.number}
handleChange={(n) => { handleChange={(n) => {
if (!noun.changeNumber) return; if (!props.noun || !props.noun.changeNumber) return;
onChange(noun.changeNumber(n)); props.onChange(props.noun.changeNumber(n));
}} }}
/> : noun.number === "singular" ? "Sing." : "Plur."} /> : props.noun.number === "singular" ? "Sing." : "Plur."}
</div> </div>
</div>} </div>}
</div>; </div>;

View File

@ -10,8 +10,12 @@ function makeParticipleSelection(verb: VerbEntry): ParticipleSelection {
}; };
} }
function NPParticiplePicker({ onChange, participle, verbs, clearButton, opts }: { function NPParticiplePicker(props: ({
verbs: VerbEntry[], verbs: VerbEntry[],
} | {
verbs: (s: string) => VerbEntry[],
getVerbByTs: (ts: number) => VerbEntry | undefined;
}) & {
participle: ParticipleSelection | undefined, participle: ParticipleSelection | undefined,
onChange: (p: ParticipleSelection | undefined) => void, onChange: (p: ParticipleSelection | undefined) => void,
clearButton: JSX.Element, clearButton: JSX.Element,
@ -19,21 +23,26 @@ function NPParticiplePicker({ onChange, participle, verbs, clearButton, opts }:
}) { }) {
function onEntrySelect(entry: VerbEntry | undefined) { function onEntrySelect(entry: VerbEntry | undefined) {
if (!entry) { if (!entry) {
onChange(undefined); props.onChange(undefined);
return; return;
} }
onChange(makeParticipleSelection(entry)); props.onChange(makeParticipleSelection(entry));
} }
return <div style={{ maxWidth: "225px" }}> return <div style={{ maxWidth: "225px" }}>
{clearButton} {props.clearButton}
<EntrySelect <EntrySelect
value={participle?.verb} value={props.participle?.verb}
entries={verbs} {..."getVerbByTs" in props ? {
getByTs: props.getVerbByTs,
searchF: props.verbs,
} : {
entries: props.verbs,
}}
onChange={onEntrySelect} onChange={onEntrySelect}
name="Pariticple" name="Pariticple"
opts={opts} opts={props.opts}
/> />
{participle && <div className="my-2 d-flex flex-row justify-content-around align-items-center"> {props.participle && <div className="my-2 d-flex flex-row justify-content-around align-items-center">
<div>Masc.</div> <div>Masc.</div>
<div>Plur.</div> <div>Plur.</div>
</div>} </div>}

View File

@ -5,7 +5,6 @@ import ParticiplePicker from "./NPParticiplePicker";
// import { ButtonSelect } from "@lingdocs/pashto-inflector"; // import { ButtonSelect } from "@lingdocs/pashto-inflector";
import { randomPerson } from "../../lib/np-tools"; import { randomPerson } from "../../lib/np-tools";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { nouns, verbs } from "../../words/words";
import { import {
Types as T, Types as T,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
@ -13,38 +12,46 @@ import {
const npTypes: NPType[] = ["pronoun", "noun", "participle"]; const npTypes: NPType[] = ["pronoun", "noun", "participle"];
function NPPicker({ np, onChange, counterPart, asObject, opts }: { function NPPicker(props: {
onChange: (nps: NPSelection | undefined) => void, onChange: (nps: NPSelection | undefined) => void,
np: NPSelection | undefined, np: NPSelection | undefined,
counterPart: NPSelection | VerbObject | undefined, counterPart: NPSelection | VerbObject | undefined,
asObject?: boolean, asObject?: boolean,
opts: T.TextOptions, opts: T.TextOptions,
}) { } & ({
const [npType, setNpType] = useState<NPType | undefined>(np ? np.type : undefined); nouns: (s: string) => NounEntry[],
verbs: (s: string) => VerbEntry[],
getNounByTs: (ts: number) => NounEntry | undefined,
getVerbByTs: (ts: number) => VerbEntry | undefined,
} | {
nouns: NounEntry[],
verbs: VerbEntry[],
})) {
const [npType, setNpType] = useState<NPType | undefined>(props.np ? props.np.type : undefined);
useEffect(() => { useEffect(() => {
setNpType(np ? np.type : undefined); setNpType(props.np ? props.np.type : undefined);
}, [np]); }, [props.np]);
function handleClear() { function handleClear() {
if (np && np.type === "noun" && np.dynamicComplement) return; if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return;
setNpType(undefined); setNpType(undefined);
onChange(undefined); props.onChange(undefined);
} }
function handleNPTypeChange(ntp: NPType) { function handleNPTypeChange(ntp: NPType) {
if (ntp === "pronoun") { if (ntp === "pronoun") {
const person = randomPerson({ counterPart }); const person = randomPerson({ counterPart: props.counterPart });
const pronoun: PronounSelection = { const pronoun: PronounSelection = {
type: "pronoun", type: "pronoun",
person, person,
distance: "far", distance: "far",
}; };
setNpType(ntp); setNpType(ntp);
onChange(pronoun); props.onChange(pronoun);
} else { } else {
onChange(undefined); props.onChange(undefined);
setNpType(ntp); setNpType(ntp);
} }
} }
const isDynamicComplement = np && np.type === "noun" && np.dynamicComplement; const isDynamicComplement = props.np && props.np.type === "noun" && props.np.dynamicComplement;
const clearButton = <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>; const clearButton = <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>;
return <div> return <div>
{!npType && <div className="text-center mt-3"> {!npType && <div className="text-center mt-3">
@ -62,29 +69,39 @@ function NPPicker({ np, onChange, counterPart, asObject, opts }: {
</button> </button>
</div>)} </div>)}
</div>} </div>}
{(npType === "pronoun" && np?.type === "pronoun") {(npType === "pronoun" && props.np?.type === "pronoun")
? <PronounPicker ? <PronounPicker
asObject={asObject} asObject={props.asObject}
pronoun={np} pronoun={props.np}
onChange={onChange} onChange={props.onChange}
clearButton={clearButton} clearButton={clearButton}
opts={opts} opts={props.opts}
/> />
: npType === "noun" : npType === "noun"
? <NounPicker ? <NounPicker
nouns={nouns} {..."getNounByTs" in props ? {
noun={(np && np.type === "noun") ? np : undefined} nouns: props.nouns,
onChange={onChange} getNounByTs: props.getNounByTs,
} : {
nouns: props.nouns,
}}
noun={(props.np && props.np.type === "noun") ? props.np : undefined}
onChange={props.onChange}
clearButton={!isDynamicComplement ? clearButton : undefined} clearButton={!isDynamicComplement ? clearButton : undefined}
opts={opts} opts={props.opts}
/> />
: npType === "participle" : npType === "participle"
? <ParticiplePicker ? <ParticiplePicker
verbs={verbs} {..."getVerbByTs" in props ? {
participle={(np && np.type === "participle") ? np : undefined} verbs: props.verbs,
onChange={onChange} getVerbByTs: props.getVerbByTs,
} : {
verbs: props.verbs,
}}
participle={(props.np && props.np.type === "participle") ? props.np : undefined}
onChange={props.onChange}
clearButton={clearButton} clearButton={clearButton}
opts={opts} opts={props.opts}
/> />
: null : null
} }

View File

@ -33,8 +33,16 @@ const servantEmoji = "🙇‍♂️";
// TODO: error handling on error with rendering etc // TODO: error handling on error with rendering etc
export function PhraseBuilder(props: { export function PhraseBuilder(props: {
verb?: VerbEntry, verb?: VerbEntry,
opts?: T.TextOptions, opts: T.TextOptions,
}) { } & ({
nouns: NounEntry[],
verbs: VerbEntry[],
} | {
nouns: (s: string) => NounEntry[],
verbs: (s: string) => VerbEntry[],
getNounByTs: (ts: number) => NounEntry | undefined,
getVerbByTs: (ts: number) => VerbEntry | undefined,
})) {
const [subject, setSubject] = useStickyState<NPSelection | undefined>(undefined, "subjectNPSelection"); const [subject, setSubject] = useStickyState<NPSelection | undefined>(undefined, "subjectNPSelection");
const [mode, setMode] = useStickyState<"charts" | "phrases">("phrases", "verbExplorerMode"); const [mode, setMode] = useStickyState<"charts" | "phrases">("phrases", "verbExplorerMode");
const passedVerb = props.verb; const passedVerb = props.verb;
@ -101,6 +109,15 @@ export function PhraseBuilder(props: {
<div className="my-2"> <div className="my-2">
<div className="h5 text-center">Subject {showRole(VPRendered, "subject")}</div> <div className="h5 text-center">Subject {showRole(VPRendered, "subject")}</div>
<NPPicker <NPPicker
{..."getNounByTs" in props ? {
getNounByTs: props.getNounByTs,
getVerbByTs: props.getVerbByTs,
nouns: props.nouns,
verbs: props.verbs,
} : {
nouns: props.nouns,
verbs: props.verbs,
}}
np={subject} np={subject}
counterPart={verb ? verb.object : undefined} counterPart={verb ? verb.object : undefined}
onChange={handleSubjectChange} onChange={handleSubjectChange}
@ -112,6 +129,15 @@ export function PhraseBuilder(props: {
{(typeof verb.object === "number") {(typeof 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
{..."getNounByTs" in props ? {
getNounByTs: props.getNounByTs,
getVerbByTs: props.getVerbByTs,
nouns: props.nouns,
verbs: props.verbs,
} : {
nouns: props.nouns,
verbs: props.verbs,
}}
asObject asObject
np={verb.object} np={verb.object}
counterPart={subject} counterPart={subject}

View File

@ -4,5 +4,13 @@ fullWidth: true
--- ---
import PhraseBuilder from "../../components/phrase-builder/PhraseBuilder"; import PhraseBuilder from "../../components/phrase-builder/PhraseBuilder";
import {
defaultTextOptions,
} from "@lingdocs/pashto-inflector";
import { nouns, verbs } from "../../words/words";
<PhraseBuilder /> <PhraseBuilder
opts={defaultTextOptions}
nouns={nouns}
verbs={verbs}
/>