refactoring the EP and VP explorers - hoping to get proper robust mini pronoun stuff happening soon. also fixed a problem with the EPExplorers, in EPs you omit the SUBJECT always not the king
This commit is contained in:
parent
b75ddb9689
commit
240fd11ed8
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@lingdocs/pashto-inflector",
|
"name": "@lingdocs/pashto-inflector",
|
||||||
"version": "2.3.8",
|
"version": "2.3.9",
|
||||||
"author": "lingdocs.com",
|
"author": "lingdocs.com",
|
||||||
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
|
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
|
||||||
"homepage": "https://verbs.lingdocs.com",
|
"homepage": "https://verbs.lingdocs.com",
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import * as T from "../../types";
|
import * as T from "../../types";
|
||||||
import { renderEP } from "../../lib/phrase-building/render-ep";
|
import { renderEP } from "../../lib/phrase-building/render-ep";
|
||||||
import { compileEP } from "../../lib/phrase-building/compile-ep";
|
import { compileEP } from "../../lib/phrase-building/compile-ep";
|
||||||
import AbbreviationFormSelector from "../vp-explorer/AbbreviationFormSelector";
|
|
||||||
import Examples from "../Examples";
|
import Examples from "../Examples";
|
||||||
|
import ButtonSelect from "../ButtonSelect";
|
||||||
|
|
||||||
function EPDisplay({ eps, opts, setForm }: {
|
function EPDisplay({ eps, opts, setOmitSubject }: {
|
||||||
eps: T.EPSelectionState,
|
eps: T.EPSelectionState,
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions,
|
||||||
setForm: (form: T.FormVersion) => void,
|
setOmitSubject: (value: "true" | "false") => void,
|
||||||
}) {
|
}) {
|
||||||
const EP = completeEPSelection(eps);
|
const EP = completeEPSelection(eps);
|
||||||
if (!EP) {
|
if (!EP) {
|
||||||
|
@ -22,13 +22,19 @@ function EPDisplay({ eps, opts, setForm }: {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
const rendered = renderEP(EP);
|
const rendered = renderEP(EP);
|
||||||
const result = compileEP(rendered, EP.form);
|
const result = compileEP(rendered);
|
||||||
return <div className="text-center pt-3">
|
return <div className="text-center pt-3">
|
||||||
<AbbreviationFormSelector
|
<div className="mb-2">
|
||||||
adjustable="king"
|
<ButtonSelect
|
||||||
form={EP.form}
|
small
|
||||||
onChange={setForm}
|
value={(eps.omitSubject ? "true" : "false") as "true" | "false"}
|
||||||
|
options={[
|
||||||
|
{ value: "false", label: "Full"},
|
||||||
|
{ value: "true", label: "No Subj."},
|
||||||
|
]}
|
||||||
|
handleChange={setOmitSubject}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{"long" in result.ps ?
|
{"long" in result.ps ?
|
||||||
<div>
|
<div>
|
||||||
<VariationLayer vs={result.ps.long} opts={opts} />
|
<VariationLayer vs={result.ps.long} opts={opts} />
|
||||||
|
|
|
@ -1,26 +1,14 @@
|
||||||
import * as T from "../../types";
|
import * as T from "../../types";
|
||||||
import useStickyState from "../../lib/useStickyState";
|
import useStickyState, { useStickyReducer } from "../../lib/useStickyState";
|
||||||
import NPPicker from "../np-picker/NPPicker";
|
import NPPicker from "../np-picker/NPPicker";
|
||||||
import EquativePicker from "./EquativePicker";
|
import EquativePicker from "./EquativePicker";
|
||||||
import EPDisplay from "./EPDisplay";
|
import EPDisplay from "./EPDisplay";
|
||||||
import ButtonSelect from "../ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import EqCompPicker from "./eq-comp-picker/EqCompPicker";
|
import EqCompPicker from "./eq-comp-picker/EqCompPicker";
|
||||||
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
|
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
|
||||||
import { isUnisexNounEntry } from "../../lib/type-predicates";
|
|
||||||
import {
|
|
||||||
personGender,
|
|
||||||
personNumber,
|
|
||||||
} from "../../lib/misc-helpers";
|
|
||||||
import EqChartsDisplay from "./EqChartsDisplay";
|
import EqChartsDisplay from "./EqChartsDisplay";
|
||||||
|
import epsReducer from "./eps-reducer";
|
||||||
// TODO: put the clear button beside the title in the predicate picker?
|
const blankEps: T.EPSelectionState = {
|
||||||
|
|
||||||
function EPExplorer(props: {
|
|
||||||
opts: T.TextOptions,
|
|
||||||
entryFeeder: T.EntryFeeder,
|
|
||||||
}) {
|
|
||||||
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
|
|
||||||
const [eps, setEps] = useStickyState<T.EPSelectionState>({
|
|
||||||
subject: undefined,
|
subject: undefined,
|
||||||
predicate: {
|
predicate: {
|
||||||
type: "Complement",
|
type: "Complement",
|
||||||
|
@ -32,44 +20,17 @@ function EPExplorer(props: {
|
||||||
negative: false,
|
negative: false,
|
||||||
},
|
},
|
||||||
shrunkenPossesive: undefined,
|
shrunkenPossesive: undefined,
|
||||||
form: { removeKing: false, shrinkServant: false },
|
omitSubject: false,
|
||||||
}, "EPSelectionState10");
|
};
|
||||||
function handlePredicateTypeChange(type: "NP" | "Complement") {
|
|
||||||
setEps(o => ({
|
// TODO: put the clear button beside the title in the predicate picker?
|
||||||
...o,
|
|
||||||
predicate: {
|
function EPExplorer(props: {
|
||||||
...o.predicate,
|
opts: T.TextOptions,
|
||||||
type,
|
entryFeeder: T.EntryFeeder,
|
||||||
},
|
}) {
|
||||||
}));
|
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
|
||||||
}
|
const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPSelectionState10");
|
||||||
function handleSetSubject(subject: T.NPSelection | undefined) {
|
|
||||||
setEps(old => massageSubjectChange(subject, old));
|
|
||||||
}
|
|
||||||
function setPredicateNP(selection: T.NPSelection | undefined) {
|
|
||||||
setEps(o => massageNPPredicateChange(selection, o))
|
|
||||||
}
|
|
||||||
function setPredicateComp(selection: T.EqCompSelection | undefined) {
|
|
||||||
setEps(o => ({
|
|
||||||
...o,
|
|
||||||
predicate: {
|
|
||||||
...o.predicate,
|
|
||||||
Complement: selection
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
|
|
||||||
setEps(o => ({
|
|
||||||
...o,
|
|
||||||
shrunkenPossesive,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
function handleSetForm(form: T.FormVersion) {
|
|
||||||
setEps(o => ({
|
|
||||||
...o,
|
|
||||||
form,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
const king = eps.subject?.type === "pronoun"
|
const king = eps.subject?.type === "pronoun"
|
||||||
? "subject"
|
? "subject"
|
||||||
: eps.predicate.type === "Complement"
|
: eps.predicate.type === "Complement"
|
||||||
|
@ -91,13 +52,13 @@ function EPExplorer(props: {
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<NPPicker
|
<NPPicker
|
||||||
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
|
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
|
||||||
handleShrinkPossesive={handleShrinkPossesive}
|
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
|
||||||
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
|
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
np={eps.subject}
|
np={eps.subject}
|
||||||
counterPart={undefined}
|
counterPart={undefined}
|
||||||
role="subject"
|
role="subject"
|
||||||
onChange={handleSetSubject}
|
onChange={payload => adjustEps({ type: "set subject", payload })}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,21 +72,21 @@ function EPExplorer(props: {
|
||||||
{ value: "Complement", label: "Complement" },
|
{ value: "Complement", label: "Complement" },
|
||||||
]}
|
]}
|
||||||
value={eps.predicate.type}
|
value={eps.predicate.type}
|
||||||
handleChange={handlePredicateTypeChange}
|
handleChange={payload => adjustEps({ type: "set predicate type", payload })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{eps.predicate.type === "NP" ? <NPPicker
|
{eps.predicate.type === "NP" ? <NPPicker
|
||||||
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
|
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
|
||||||
handleShrinkPossesive={handleShrinkPossesive}
|
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
|
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
|
||||||
counterPart={undefined}
|
counterPart={undefined}
|
||||||
role="subject"
|
role="subject"
|
||||||
onChange={setPredicateNP}
|
onChange={payload => adjustEps({ type: "set predicate NP", payload })}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
/> : <EqCompPicker
|
/> : <EqCompPicker
|
||||||
comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined}
|
comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined}
|
||||||
onChange={setPredicateComp}
|
onChange={payload => adjustEps({ type: "set predicate comp", payload })}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
/>}
|
/>}
|
||||||
|
@ -135,7 +96,7 @@ function EPExplorer(props: {
|
||||||
<div className="h5 text-center clickable">Equative</div>
|
<div className="h5 text-center clickable">Equative</div>
|
||||||
<EquativePicker
|
<EquativePicker
|
||||||
equative={eps.equative}
|
equative={eps.equative}
|
||||||
onChange={(equative) => setEps(o => ({ ...o, equative }))}
|
onChange={payload => adjustEps({ type: "set equative", payload })}
|
||||||
hideNegative={mode === "charts"}
|
hideNegative={mode === "charts"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -144,95 +105,9 @@ function EPExplorer(props: {
|
||||||
{mode === "phrases" && <EPDisplay
|
{mode === "phrases" && <EPDisplay
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
eps={eps}
|
eps={eps}
|
||||||
setForm={handleSetForm}
|
setOmitSubject={payload => adjustEps({ type: "set omitSubject", payload })}
|
||||||
/>}
|
/>}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EPExplorer;
|
export default EPExplorer;
|
||||||
|
|
||||||
function massageNPPredicateChange(selection: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
|
|
||||||
if (!selection) {
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
predicate: {
|
|
||||||
...old.predicate,
|
|
||||||
NP: selection,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (old.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
|
|
||||||
const { gender, number } = selection;
|
|
||||||
const pronoun = old.subject.person;
|
|
||||||
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
subject: {
|
|
||||||
...old.subject,
|
|
||||||
person: newPronoun,
|
|
||||||
},
|
|
||||||
predicate: {
|
|
||||||
...old.predicate,
|
|
||||||
NP: selection,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
predicate: {
|
|
||||||
...old.predicate,
|
|
||||||
NP: selection,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
|
|
||||||
if (!subject) {
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
subject,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) {
|
|
||||||
const predicate = old.predicate.NP;
|
|
||||||
const adjusted = {
|
|
||||||
...predicate,
|
|
||||||
...predicate.numberCanChange ? {
|
|
||||||
number: personNumber(subject.person),
|
|
||||||
} : {},
|
|
||||||
...predicate.genderCanChange ? {
|
|
||||||
gender: personGender(subject.person),
|
|
||||||
} : {},
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
subject,
|
|
||||||
predicate: {
|
|
||||||
...old.predicate,
|
|
||||||
NP: adjusted,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
subject,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
|
|
||||||
const pGender = personGender(p);
|
|
||||||
if (gender === pGender) {
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
return (gender === "masc") ? (p - 1) : (p + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
|
|
||||||
const pNumber = personNumber(p);
|
|
||||||
if (pNumber === number) {
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
return (number === "plural")
|
|
||||||
? (p + 6)
|
|
||||||
: (p - 6);
|
|
||||||
}
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
import * as T from "../../types";
|
||||||
|
import {
|
||||||
|
personGender,
|
||||||
|
personNumber,
|
||||||
|
} from "../../lib/misc-helpers";
|
||||||
|
import { isUnisexNounEntry } from "../../lib/type-predicates";
|
||||||
|
|
||||||
|
export type EpsReducerAction = {
|
||||||
|
type: "set predicate type",
|
||||||
|
payload: "NP" | "Complement",
|
||||||
|
} | {
|
||||||
|
type: "set subject",
|
||||||
|
payload: T.NPSelection | undefined,
|
||||||
|
} | {
|
||||||
|
type: "set predicate NP",
|
||||||
|
payload: T.NPSelection | undefined,
|
||||||
|
} | {
|
||||||
|
type: "set predicate comp",
|
||||||
|
payload: T.EqCompSelection | undefined,
|
||||||
|
} | {
|
||||||
|
type: "shrink possesive",
|
||||||
|
payload: number | undefined,
|
||||||
|
} | {
|
||||||
|
type: "set omitSubject",
|
||||||
|
payload: "true" | "false",
|
||||||
|
} | {
|
||||||
|
type: "set equative",
|
||||||
|
payload: T.EquativeSelection,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAction): T.EPSelectionState {
|
||||||
|
if (action.type === "set predicate type") {
|
||||||
|
return {
|
||||||
|
...eps,
|
||||||
|
predicate: {
|
||||||
|
...eps.predicate,
|
||||||
|
type: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set subject") {
|
||||||
|
return massageSubjectChange(action.payload, eps);
|
||||||
|
}
|
||||||
|
if (action.type === "set predicate NP") {
|
||||||
|
return massageNPPredicateChange(action.payload, eps);
|
||||||
|
}
|
||||||
|
if (action.type === "set predicate comp") {
|
||||||
|
return {
|
||||||
|
...eps,
|
||||||
|
predicate: {
|
||||||
|
...eps.predicate,
|
||||||
|
Complement: action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "shrink possesive") {
|
||||||
|
return {
|
||||||
|
...eps,
|
||||||
|
shrunkenPossesive: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set omitSubject") {
|
||||||
|
return {
|
||||||
|
...eps,
|
||||||
|
omitSubject: action.payload === "true",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// if (action.type === "set equative") {
|
||||||
|
return {
|
||||||
|
...eps,
|
||||||
|
equative: action.payload,
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
|
||||||
|
if (!subject) {
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) {
|
||||||
|
const predicate = old.predicate.NP;
|
||||||
|
const adjusted = {
|
||||||
|
...predicate,
|
||||||
|
...predicate.numberCanChange ? {
|
||||||
|
number: personNumber(subject.person),
|
||||||
|
} : {},
|
||||||
|
...predicate.genderCanChange ? {
|
||||||
|
gender: personGender(subject.person),
|
||||||
|
} : {},
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
subject,
|
||||||
|
predicate: {
|
||||||
|
...old.predicate,
|
||||||
|
NP: adjusted,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function massageNPPredicateChange(selection: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
|
||||||
|
if (!selection) {
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
predicate: {
|
||||||
|
...old.predicate,
|
||||||
|
NP: selection,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (old.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
|
||||||
|
const { gender, number } = selection;
|
||||||
|
const pronoun = old.subject.person;
|
||||||
|
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
subject: {
|
||||||
|
...old.subject,
|
||||||
|
person: newPronoun,
|
||||||
|
},
|
||||||
|
predicate: {
|
||||||
|
...old.predicate,
|
||||||
|
NP: selection,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
predicate: {
|
||||||
|
...old.predicate,
|
||||||
|
NP: selection,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
|
||||||
|
const pGender = personGender(p);
|
||||||
|
if (gender === pGender) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return (gender === "masc") ? (p - 1) : (p + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
|
||||||
|
const pNumber = personNumber(p);
|
||||||
|
if (pNumber === number) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return (number === "plural")
|
||||||
|
? (p + 6)
|
||||||
|
: (p - 6);
|
||||||
|
}
|
|
@ -2,9 +2,11 @@ import Select from "react-select";
|
||||||
import * as T from "../../types";
|
import * as T from "../../types";
|
||||||
import ButtonSelect from "../ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import { isImperativeTense, isModalTense, isPerfectTense, isVerbTense } from "../../lib/type-predicates";
|
import { isImperativeTense, isModalTense, isPerfectTense, isVerbTense } from "../../lib/type-predicates";
|
||||||
import { ensure2ndPersSubjPronounAndNoConflict } from "../../lib/phrase-building/vp-tools";
|
|
||||||
import useStickyState from "../../lib/useStickyState";
|
import useStickyState from "../../lib/useStickyState";
|
||||||
import { customStyles } from "../EntrySelect";
|
import { customStyles } from "../EntrySelect";
|
||||||
|
import {
|
||||||
|
VpsReducerAction
|
||||||
|
} from "./vps-reducer";
|
||||||
|
|
||||||
const verbTenseOptions: { label: string | JSX.Element, value: T.VerbTense, formula: string, modalFormula: string, }[] = [{
|
const verbTenseOptions: { label: string | JSX.Element, value: T.VerbTense, formula: string, modalFormula: string, }[] = [{
|
||||||
label: <div><i className="fas fa-video mr-2" />present</div>,
|
label: <div><i className="fas fa-video mr-2" />present</div>,
|
||||||
|
@ -113,45 +115,17 @@ function TensePicker(props: ({
|
||||||
} | {
|
} | {
|
||||||
vpsComplete: T.VPSelectionComplete,
|
vpsComplete: T.VPSelectionComplete,
|
||||||
}) & {
|
}) & {
|
||||||
onChange: (p: T.VPSelectionState) => void,
|
onChange: (p: VpsReducerAction) => void,
|
||||||
mode: "charts" | "phrases" | "quiz",
|
mode: "charts" | "phrases" | "quiz",
|
||||||
}) {
|
}) {
|
||||||
const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula");
|
const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula");
|
||||||
function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null) {
|
function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null) {
|
||||||
if ("vpsComplete" in props) return;
|
if ("vpsComplete" in props) return;
|
||||||
const value = o?.value ? o.value : undefined;
|
const tense = o?.value ? o.value : undefined;
|
||||||
if (props.vps.verb && value) {
|
|
||||||
if (isPerfectTense(value)) {
|
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.vps,
|
type: "set tense",
|
||||||
verb: {
|
payload: tense,
|
||||||
...props.vps.verb,
|
|
||||||
perfectTense: value,
|
|
||||||
tenseCategory: "perfect",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else if (isImperativeTense(value)) {
|
|
||||||
props.onChange({
|
|
||||||
...props.vps,
|
|
||||||
verb: {
|
|
||||||
...props.vps.verb,
|
|
||||||
imperativeTense: value,
|
|
||||||
tenseCategory: "imperative",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
props.onChange({
|
|
||||||
...props.vps,
|
|
||||||
verb: {
|
|
||||||
...props.vps.verb,
|
|
||||||
verbTense: value,
|
|
||||||
tenseCategory: props.vps.verb.tenseCategory === "perfect"
|
|
||||||
? "basic"
|
|
||||||
: props.vps.verb.tenseCategory,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function moveTense(dir: "forward" | "back") {
|
function moveTense(dir: "forward" | "back") {
|
||||||
if ("vpsComplete" in props) return;
|
if ("vpsComplete" in props) return;
|
||||||
|
@ -182,41 +156,20 @@ function TensePicker(props: ({
|
||||||
onTenseSelect(newTense);
|
onTenseSelect(newTense);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function onPosNegSelect(value: string) {
|
function onPosNegSelect(payload: "true" | "false") {
|
||||||
if ("vpsComplete" in props) return;
|
if ("vpsComplete" in props) return;
|
||||||
if (props.vps.verb) {
|
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.vps,
|
type: "set negativity",
|
||||||
verb: {
|
payload,
|
||||||
...props.vps.verb,
|
|
||||||
negative: value === "true",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
function onTenseCategorySelect(payload: "basic" | "modal" | "perfect" | "imperative") {
|
||||||
function onTenseCategorySelect(value: "basic" | "modal" | "perfect" | "imperative") {
|
|
||||||
if ("vpsComplete" in props) return;
|
if ("vpsComplete" in props) return;
|
||||||
if (props.vps.verb) {
|
|
||||||
if (value === "imperative") {
|
|
||||||
props.onChange(ensure2ndPersSubjPronounAndNoConflict({
|
|
||||||
...props.vps,
|
|
||||||
verb: {
|
|
||||||
...props.vps.verb,
|
|
||||||
voice: "active",
|
|
||||||
tenseCategory: value,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.vps,
|
type: "set tense category",
|
||||||
verb: {
|
payload,
|
||||||
...props.vps.verb,
|
|
||||||
tenseCategory: value,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const tOptions = ("vps" in props && (props.vps.verb?.tenseCategory === "perfect"))
|
const tOptions = ("vps" in props && (props.vps.verb?.tenseCategory === "perfect"))
|
||||||
? perfectTenseOptions
|
? perfectTenseOptions
|
||||||
: ("vps" in props && (props.vps.verb?.tenseCategory === "imperative"))
|
: ("vps" in props && (props.vps.verb?.tenseCategory === "imperative"))
|
||||||
|
@ -292,7 +245,7 @@ function TensePicker(props: ({
|
||||||
</div>
|
</div>
|
||||||
{props.mode === "phrases" && <ButtonSelect
|
{props.mode === "phrases" && <ButtonSelect
|
||||||
small
|
small
|
||||||
value={props.vps.verb.negative.toString()}
|
value={props.vps.verb.negative.toString() as "true" | "false"}
|
||||||
options={[{
|
options={[{
|
||||||
label: "Pos.",
|
label: "Pos.",
|
||||||
value: "false",
|
value: "false",
|
||||||
|
|
|
@ -3,21 +3,18 @@ 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 {
|
|
||||||
isInvalidSubjObjCombo,
|
|
||||||
} from "../../lib/phrase-building/vp-tools";
|
|
||||||
import * as T from "../../types";
|
import * as T from "../../types";
|
||||||
import ChartDisplay from "./VPChartDisplay";
|
import ChartDisplay from "./VPChartDisplay";
|
||||||
import useStickyState from "../../lib/useStickyState";
|
import useStickyState, { useStickyReducer } from "../../lib/useStickyState";
|
||||||
import { makeVPSelectionState } from "./verb-selection";
|
import { makeVPSelectionState } from "./verb-selection";
|
||||||
import { useEffect, useState } 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 VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal";
|
import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
|
import { vpsReducer } from "./vps-reducer";
|
||||||
|
|
||||||
const phraseURLParam = "VPPhrase";
|
const phraseURLParam = "VPPhrase";
|
||||||
|
|
||||||
|
@ -38,7 +35,8 @@ export function VPExplorer(props: {
|
||||||
handleLinkClick: ((ts: number) => void) | "none",
|
handleLinkClick: ((ts: number) => void) | "none",
|
||||||
entryFeeder: T.EntryFeeder,
|
entryFeeder: T.EntryFeeder,
|
||||||
}) {
|
}) {
|
||||||
const [vps, setVps] = useStickyState<T.VPSelectionState>(
|
const [vps, adjustVps] = useStickyReducer(
|
||||||
|
vpsReducer,
|
||||||
props.loaded
|
props.loaded
|
||||||
? props.loaded
|
? props.loaded
|
||||||
: savedVps => makeVPSelectionState(props.verb, savedVps),
|
: savedVps => makeVPSelectionState(props.verb, savedVps),
|
||||||
|
@ -62,51 +60,41 @@ export function VPExplorer(props: {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const VPSFromUrl = getVPSFromUrl();
|
const VPSFromUrl = getVPSFromUrl();
|
||||||
if (VPSFromUrl) {
|
if (VPSFromUrl) {
|
||||||
setVps(VPSFromUrl);
|
adjustVps({
|
||||||
|
type: "load vps",
|
||||||
|
payload: VPSFromUrl
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVps(oldVps => {
|
const newVps = makeVPSelectionState(props.verb, vps);
|
||||||
if (mode === "quiz") {
|
adjustVps({
|
||||||
setMode("phrases");
|
type: "load vps",
|
||||||
}
|
payload: newVps,
|
||||||
return makeVPSelectionState(props.verb, oldVps);
|
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [props.verb]);
|
}, [props.verb]);
|
||||||
function handleSubjectChange(subject: T.NPSelection | undefined, skipPronounConflictCheck?: boolean) {
|
function handleSubjectChange(subject: T.NPSelection | undefined, skipPronounConflictCheck?: boolean) {
|
||||||
if (!skipPronounConflictCheck && hasPronounConflict(subject, vps.verb?.object)) {
|
adjustVps({
|
||||||
alert("That combination of pronouns is not allowed");
|
type: "set subject",
|
||||||
return;
|
payload: { subject, skipPronounConflictCheck },
|
||||||
}
|
});
|
||||||
setVps(o => ({ ...o, subject }));
|
|
||||||
}
|
}
|
||||||
function handleObjectChange(object: T.NPSelection | undefined) {
|
function handleObjectChange(object: T.NPSelection | undefined) {
|
||||||
if (!vps.verb) return;
|
adjustVps({
|
||||||
if ((vps.verb.object === "none") || (typeof vps.verb.object === "number")) return;
|
type: "set object",
|
||||||
// check for pronoun conflict
|
payload: object,
|
||||||
if (hasPronounConflict(vps.subject, object)) {
|
});
|
||||||
alert("That combination of pronouns is not allowed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setVps(o => ({
|
|
||||||
...o,
|
|
||||||
verb: {
|
|
||||||
...o.verb,
|
|
||||||
object,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
function handleSubjObjSwap() {
|
function handleSubjObjSwap() {
|
||||||
if (vps.verb?.isCompound === "dynamic") return;
|
adjustVps({ type: "swap subj/obj" });
|
||||||
setVps(switchSubjObj)
|
|
||||||
}
|
}
|
||||||
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
|
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
|
||||||
setVps(o => ({
|
adjustVps({
|
||||||
...o,
|
type: "shrink possesive",
|
||||||
shrunkenPossesive,
|
payload: shrunkenPossesive,
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
function quizLock<T>(f: T) {
|
function quizLock<T>(f: T) {
|
||||||
if (mode === "quiz") {
|
if (mode === "quiz") {
|
||||||
|
@ -118,10 +106,10 @@ export function VPExplorer(props: {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
function handleSetForm(form: T.FormVersion) {
|
function handleSetForm(form: T.FormVersion) {
|
||||||
setVps(o => ({
|
adjustVps({
|
||||||
...o,
|
type: "set form",
|
||||||
form,
|
payload: form,
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
// for some crazy reason I can't get the URI share thing to encode and decode properly
|
// for some crazy reason I can't get the URI share thing to encode and decode properly
|
||||||
function handleCopyShareLink() {
|
function handleCopyShareLink() {
|
||||||
|
@ -135,7 +123,7 @@ export function VPExplorer(props: {
|
||||||
return <div className="mt-3" style={{ maxWidth: "950px"}}>
|
return <div className="mt-3" style={{ maxWidth: "950px"}}>
|
||||||
<VerbPicker
|
<VerbPicker
|
||||||
vps={vps}
|
vps={vps}
|
||||||
onChange={quizLock(setVps)}
|
onChange={quizLock(adjustVps)}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
handleLinkClick={props.handleLinkClick}
|
handleLinkClick={props.handleLinkClick}
|
||||||
/>
|
/>
|
||||||
|
@ -207,7 +195,7 @@ export function VPExplorer(props: {
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<TensePicker
|
<TensePicker
|
||||||
vps={vps}
|
vps={vps}
|
||||||
onChange={quizLock(setVps)}
|
onChange={quizLock(adjustVps)}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -237,13 +225,6 @@ export function VPExplorer(props: {
|
||||||
|
|
||||||
export default VPExplorer;
|
export default VPExplorer;
|
||||||
|
|
||||||
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
|
|
||||||
const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined;
|
|
||||||
const objPronoun = (object && typeof object === "object" && object.type === "pronoun") ? object : undefined;
|
|
||||||
if (!subjPronoun || !objPronoun) return false;
|
|
||||||
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getShareUrl(vps: T.VPSelectionState): string {
|
function getShareUrl(vps: T.VPSelectionState): string {
|
||||||
const stringJSON = JSON.stringify(vps);
|
const stringJSON = JSON.stringify(vps);
|
||||||
const encoded = LZString.compressToEncodedURIComponent(stringJSON);
|
const encoded = LZString.compressToEncodedURIComponent(stringJSON);
|
||||||
|
|
|
@ -5,13 +5,15 @@ import { getVerbInfo } from "../../lib/verb-info";
|
||||||
import Hider from "../Hider";
|
import Hider from "../Hider";
|
||||||
import useStickyState from "../../lib/useStickyState";
|
import useStickyState from "../../lib/useStickyState";
|
||||||
import CompoundDisplay from "./CompoundDisplay";
|
import CompoundDisplay from "./CompoundDisplay";
|
||||||
import { changeStatDyn, changeTransitivity, changeVoice } from "./verb-selection";
|
import {
|
||||||
|
VpsReducerAction
|
||||||
|
} from "./vps-reducer";
|
||||||
|
|
||||||
// TODO: dark on past tense selecitons
|
// TODO: dark on past tense selecitons
|
||||||
|
|
||||||
function VerbPicker(props: {
|
function VerbPicker(props: {
|
||||||
vps: T.VPSelectionState,
|
vps: T.VPSelectionState,
|
||||||
onChange: (p: T.VPSelectionState) => void,
|
onChange: (a: VpsReducerAction) => void,
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions,
|
||||||
handleLinkClick: ((ts: number) => void) | "none",
|
handleLinkClick: ((ts: number) => void) | "none",
|
||||||
}) {
|
}) {
|
||||||
|
@ -28,43 +30,26 @@ function VerbPicker(props: {
|
||||||
return <div>ERROR: Verb version should be select first</div>;
|
return <div>ERROR: Verb version should be select first</div>;
|
||||||
}
|
}
|
||||||
function onVoiceSelect(value: "active" | "passive") {
|
function onVoiceSelect(value: "active" | "passive") {
|
||||||
if (props.vps.verb && props.vps.verb.canChangeVoice) {
|
|
||||||
if (value === "passive" && props.vps.verb.tenseCategory === "imperative") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value === "passive" && (typeof props.vps.verb.object === "object")) {
|
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.vps,
|
type: "set voice",
|
||||||
subject: props.vps.verb.object,
|
payload: value,
|
||||||
verb: changeVoice(props.vps.verb, value, props.vps.verb.object),
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
props.onChange({
|
|
||||||
...props.vps,
|
|
||||||
verb: changeVoice(props.vps.verb, value, value === "active" ? props.vps.subject : undefined),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function notInstransitive(t: "transitive" | "intransitive" | "grammatically transitive"): "transitive" | "grammatically transitive" {
|
function notInstransitive(t: "transitive" | "intransitive" | "grammatically transitive"): "transitive" | "grammatically transitive" {
|
||||||
return t === "intransitive" ? "transitive" : t;
|
return t === "intransitive" ? "transitive" : t;
|
||||||
}
|
}
|
||||||
function handleChangeTransitivity(t: "transitive" | "grammatically transitive") {
|
function handleChangeTransitivity(payload: "transitive" | "grammatically transitive") {
|
||||||
if (props.vps.verb && props.vps.verb.canChangeTransitivity) {
|
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.vps,
|
type: "set transitivity",
|
||||||
verb: changeTransitivity(props.vps.verb, t),
|
payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
function handleChangeStatDyn(payload: "stative" | "dynamic") {
|
||||||
function handleChangeStatDyn(c: "stative" | "dynamic") {
|
|
||||||
if (props.vps.verb && props.vps.verb.canChangeStatDyn) {
|
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.vps,
|
type: "set statDyn",
|
||||||
verb: changeStatDyn(props.vps.verb, c),
|
payload,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return <div className="mb-3">
|
return <div className="mb-3">
|
||||||
{info && <CompoundDisplay
|
{info && <CompoundDisplay
|
||||||
info={info}
|
info={info}
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
import * as T from "../../types";
|
||||||
|
import {
|
||||||
|
isInvalidSubjObjCombo,
|
||||||
|
} from "../../lib/phrase-building/vp-tools";
|
||||||
|
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
|
||||||
|
import { changeStatDyn, changeTransitivity, changeVoice } from "./verb-selection";
|
||||||
|
import { ensure2ndPersSubjPronounAndNoConflict } from "../../lib/phrase-building/vp-tools";
|
||||||
|
import {
|
||||||
|
isPerfectTense,
|
||||||
|
isImperativeTense,
|
||||||
|
} from "../../lib/type-predicates";
|
||||||
|
|
||||||
|
export type VpsReducerAction = {
|
||||||
|
type: "load vps",
|
||||||
|
payload: T.VPSelectionState,
|
||||||
|
} | {
|
||||||
|
type: "set subject",
|
||||||
|
payload: {
|
||||||
|
subject: T.NPSelection | undefined,
|
||||||
|
skipPronounConflictCheck?: boolean,
|
||||||
|
},
|
||||||
|
} | {
|
||||||
|
type: "set object",
|
||||||
|
payload: T.NPSelection | undefined,
|
||||||
|
} | {
|
||||||
|
type: "swap subj/obj",
|
||||||
|
} | {
|
||||||
|
type: "shrink possesive",
|
||||||
|
payload: number | undefined,
|
||||||
|
} | {
|
||||||
|
type: "set form",
|
||||||
|
payload: T.FormVersion,
|
||||||
|
} | {
|
||||||
|
type: "set voice",
|
||||||
|
payload: "active" | "passive",
|
||||||
|
} | {
|
||||||
|
type: "set transitivity",
|
||||||
|
payload: "transitive" | "grammatically transitive",
|
||||||
|
} | {
|
||||||
|
type: "set statDyn",
|
||||||
|
payload: "stative" | "dynamic",
|
||||||
|
} | {
|
||||||
|
type: "set negativity",
|
||||||
|
payload: "true" | "false",
|
||||||
|
} | {
|
||||||
|
type: "set tense",
|
||||||
|
payload: T.VerbTense | T.PerfectTense | T.ImperativeTense | undefined,
|
||||||
|
} | {
|
||||||
|
type: "set tense category",
|
||||||
|
payload: "basic" | "modal" | "perfect" | "imperative",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T.VPSelectionState {
|
||||||
|
if (action.type === "load vps") {
|
||||||
|
return action.payload;
|
||||||
|
}
|
||||||
|
if (action.type === "set subject") {
|
||||||
|
const { subject, skipPronounConflictCheck } = action.payload;
|
||||||
|
if (
|
||||||
|
!skipPronounConflictCheck
|
||||||
|
&&
|
||||||
|
hasPronounConflict(subject, vps.verb?.object)
|
||||||
|
) {
|
||||||
|
alert("That combination of pronouns is not allowed");
|
||||||
|
return vps;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
subject: action.payload.subject,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set object") {
|
||||||
|
if (!vps.verb) return vps;
|
||||||
|
if ((vps.verb.object === "none") || (typeof vps.verb.object === "number")) {
|
||||||
|
return vps;
|
||||||
|
}
|
||||||
|
const object = action.payload;
|
||||||
|
// check for pronoun conflict
|
||||||
|
if (hasPronounConflict(vps.subject, object)) {
|
||||||
|
alert("That combination of pronouns is not allowed");
|
||||||
|
return vps;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
object,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "swap subj/obj") {
|
||||||
|
if (vps.verb?.isCompound === "dynamic") return vps;
|
||||||
|
return switchSubjObj(vps);
|
||||||
|
}
|
||||||
|
if (action.type === "shrink possesive") {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
shrunkenPossesive: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set form") {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
form: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set voice") {
|
||||||
|
if (vps.verb && vps.verb.canChangeVoice) {
|
||||||
|
const voice = action.payload;
|
||||||
|
if (voice === "passive" && vps.verb.tenseCategory === "imperative") {
|
||||||
|
return vps;
|
||||||
|
}
|
||||||
|
if (voice === "passive" && (typeof vps.verb.object === "object")) {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
subject: vps.verb.object,
|
||||||
|
verb: changeVoice(vps.verb, voice, vps.verb.object),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: changeVoice(vps.verb, voice, voice === "active" ? vps.subject : undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return vps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action.type === "set transitivity") {
|
||||||
|
if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps;
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: changeTransitivity(vps.verb, action.payload),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set statDyn") {
|
||||||
|
if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps;
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: changeStatDyn(vps.verb, action.payload),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set negativity") {
|
||||||
|
if (!vps.verb) return vps;
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
negative: action.payload === "true",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action.type === "set tense") {
|
||||||
|
const tense = action.payload;
|
||||||
|
if (!(vps.verb && tense)) return vps;
|
||||||
|
if (isPerfectTense(tense)) {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
perfectTense: tense,
|
||||||
|
tenseCategory: "perfect",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (isImperativeTense(tense)) {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
imperativeTense: tense,
|
||||||
|
tenseCategory: "imperative",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
verbTense: tense,
|
||||||
|
tenseCategory: vps.verb.tenseCategory === "perfect"
|
||||||
|
? "basic"
|
||||||
|
: vps.verb.tenseCategory,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (action.type === "set tense category") {
|
||||||
|
if (!vps.verb) return vps;
|
||||||
|
const category = action.payload;
|
||||||
|
if (category === "imperative") {
|
||||||
|
return ensure2ndPersSubjPronounAndNoConflict({
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
voice: "active",
|
||||||
|
tenseCategory: category,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...vps,
|
||||||
|
verb: {
|
||||||
|
...vps.verb,
|
||||||
|
tenseCategory: category,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
|
||||||
|
const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined;
|
||||||
|
const objPronoun = (object && typeof object === "object" && object.type === "pronoun") ? object : undefined;
|
||||||
|
if (!subjPronoun || !objPronoun) return false;
|
||||||
|
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
|
||||||
|
}
|
|
@ -14,14 +14,14 @@ import { removeAccents } from "../accent-helpers";
|
||||||
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
|
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
|
||||||
import {
|
import {
|
||||||
orderKidsSection,
|
orderKidsSection,
|
||||||
findPossesiveToShrink,
|
findPossesiveToShrinkInEP,
|
||||||
shrinkNP,
|
shrinkNP,
|
||||||
} from "./compile-tools";
|
} from "./compile-tools";
|
||||||
|
|
||||||
export function compileEP(EP: T.EPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] };
|
export function compileEP(EP: T.EPRendered): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] };
|
||||||
export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths: true): { ps: T.PsString[], e?: string[] };
|
export function compileEP(EP: T.EPRendered, combineLengths: true): { ps: T.PsString[], e?: string[] };
|
||||||
export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] } {
|
export function compileEP(EP: T.EPRendered, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] } {
|
||||||
const { kids, NPs } = getSegmentsAndKids(EP, form);
|
const { kids, NPs } = getSegmentsAndKids(EP);
|
||||||
const equative = EP.equative.ps;
|
const equative = EP.equative.ps;
|
||||||
const psResult = compilePs({
|
const psResult = compilePs({
|
||||||
NPs,
|
NPs,
|
||||||
|
@ -35,15 +35,9 @@ export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSegmentsAndKids(EP: T.EPRendered, form: Omit<T.FormVersion, "shrinkServant">): { kids: Segment[], NPs: Segment[] } {
|
function getSegmentsAndKids(EP: T.EPRendered): { kids: Segment[], NPs: Segment[] } {
|
||||||
function ifNotRemoved(s: Segment, role: "subject" | "predicate"): Segment[] {
|
const possToShrink = findPossesiveToShrinkInEP(EP);
|
||||||
if (form.removeKing && EP.king === role) {
|
const shrunkenPossAllowed = !((possToShrink?.from === "subject") && EP.omitSubject);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return [s];
|
|
||||||
}
|
|
||||||
const possToShrink = findPossesiveToShrink(EP);
|
|
||||||
const shrunkenPossAllowed = !(form.removeKing && possToShrink?.role === "king");
|
|
||||||
const possUid = shrunkenPossAllowed ? EP.shrunkenPossesive : undefined;
|
const possUid = shrunkenPossAllowed ? EP.shrunkenPossesive : undefined;
|
||||||
const subject = makeSegment(getPashtoFromRendered(EP.subject, possUid, false));
|
const subject = makeSegment(getPashtoFromRendered(EP.subject, possUid, false));
|
||||||
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, possUid, false));
|
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, possUid, false));
|
||||||
|
@ -53,12 +47,12 @@ function getSegmentsAndKids(EP: T.EPRendered, form: Omit<T.FormVersion, "shrinkS
|
||||||
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
|
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
|
||||||
: [],
|
: [],
|
||||||
...(possToShrink && shrunkenPossAllowed)
|
...(possToShrink && shrunkenPossAllowed)
|
||||||
? [shrinkNP(possToShrink)]
|
? [shrinkNP(possToShrink.np)]
|
||||||
: [],
|
: [],
|
||||||
]),
|
]),
|
||||||
NPs: [
|
NPs: [
|
||||||
...ifNotRemoved(subject, "subject"),
|
...EP.omitSubject ? [] : [subject],
|
||||||
...ifNotRemoved(predicate, "predicate"),
|
predicate
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,28 +26,55 @@ export function orderKidsSection(kids: Segment[]): Segment[] {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findPossesiveToShrink(VP: T.VPRendered | T.EPRendered): T.Rendered<T.NPSelection> | undefined {
|
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined, uid: number): T.Rendered<T.NPSelection> | undefined {
|
||||||
const uid = VP.shrunkenPossesive;
|
|
||||||
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection> | undefined {
|
|
||||||
if (NP === undefined) return undefined;
|
if (NP === undefined) return undefined;
|
||||||
if (typeof NP !== "object") return undefined;
|
if (typeof NP !== "object") return undefined;
|
||||||
if (!NP.possesor) return undefined;
|
if (!NP.possesor) return undefined;
|
||||||
if (NP.possesor.uid === uid) {
|
if (NP.possesor.uid === uid) {
|
||||||
return NP.possesor.np;
|
return NP.possesor.np;
|
||||||
}
|
}
|
||||||
return findPossesiveInNP(NP.possesor.np);
|
return findPossesiveInNP(NP.possesor.np, uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findPossesiveToShrinkInEP(EP: T.EPRendered): {
|
||||||
|
np: T.Rendered<T.NPSelection>,
|
||||||
|
from: "subject" | "predicate",
|
||||||
|
} | undefined {
|
||||||
|
const uid = EP.shrunkenPossesive;
|
||||||
if (uid === undefined) return undefined;
|
if (uid === undefined) return undefined;
|
||||||
const objPred: T.Rendered<T.NPSelection> | undefined = ("object" in VP)
|
const inSubject = findPossesiveInNP(EP.subject, uid);
|
||||||
? (typeof VP.object === "object" ? VP.object : undefined)
|
if (inSubject) {
|
||||||
: (VP.predicate.type === "noun" || VP.predicate.type === "participle" || VP.predicate.type === "pronoun")
|
return {
|
||||||
// typescript is dumb here;
|
np: inSubject,
|
||||||
? VP.predicate as T.Rendered<T.NPSelection>
|
from: "subject",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (EP.predicate.type === "adjective" || EP.predicate.type === "loc. adv.") {
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
}
|
||||||
|
// ts being stupid
|
||||||
|
const predicate = EP.predicate as T.Rendered<T.NPSelection>;
|
||||||
|
const inPredicate = findPossesiveInNP(predicate, uid);
|
||||||
|
if (inPredicate) {
|
||||||
|
return {
|
||||||
|
np: inPredicate,
|
||||||
|
from: "predicate",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered<T.NPSelection> | undefined {
|
||||||
|
const uid = VP.shrunkenPossesive;
|
||||||
|
if (uid === undefined) return undefined;
|
||||||
|
const obj: T.Rendered<T.NPSelection> | undefined = ("object" in VP && typeof VP.object === "object")
|
||||||
|
? VP.object
|
||||||
: undefined;
|
: undefined;
|
||||||
return (
|
return (
|
||||||
findPossesiveInNP(VP.subject)
|
findPossesiveInNP(VP.subject, uid)
|
||||||
||
|
||
|
||||||
findPossesiveInNP(objPred)
|
findPossesiveInNP(obj, uid)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predica
|
||||||
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
|
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
|
||||||
import {
|
import {
|
||||||
orderKidsSection,
|
orderKidsSection,
|
||||||
findPossesiveToShrink,
|
findPossesiveToShrinkInVP,
|
||||||
shrinkNP,
|
shrinkNP,
|
||||||
} from "./compile-tools";
|
} from "./compile-tools";
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
|
||||||
return servant;
|
return servant;
|
||||||
})();
|
})();
|
||||||
const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
|
const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
|
||||||
const possToShrink = findPossesiveToShrink(VP);
|
const possToShrink = findPossesiveToShrinkInVP(VP);
|
||||||
const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined;
|
const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined;
|
||||||
const shrunkenPossAllowed = possToShrink && shrunkenPossesive && (
|
const shrunkenPossAllowed = possToShrink && shrunkenPossesive && (
|
||||||
!shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0])
|
!shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0])
|
||||||
|
|
|
@ -33,7 +33,7 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
|
||||||
equative: renderEquative(EP.equative, kingPerson),
|
equative: renderEquative(EP.equative, kingPerson),
|
||||||
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
|
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
|
||||||
shrunkenPossesive: EP.shrunkenPossesive,
|
shrunkenPossesive: EP.shrunkenPossesive,
|
||||||
form: EP.form,
|
omitSubject: EP.omitSubject,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type SaveableData = string | number | object | boolean | undefined | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replacement from the React useState hook that will persist the state in local storage
|
* replacement from the React useState hook that will persist the state in local storage
|
||||||
*
|
*
|
||||||
|
@ -8,7 +10,7 @@ import { useEffect, useState } from "react";
|
||||||
* @param key a key for saving the state in locolStorage
|
* @param key a key for saving the state in locolStorage
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function useStickyState<T extends string | number | object | boolean | undefined | null>(defaultValue: T | ((old: T | undefined) => T), key: string): [
|
export default function useStickyState<T extends SaveableData>(defaultValue: T | ((old: T | undefined) => T), key: string): [
|
||||||
value: T,
|
value: T,
|
||||||
setValue: React.Dispatch<React.SetStateAction<T>>,
|
setValue: React.Dispatch<React.SetStateAction<T>>,
|
||||||
] {
|
] {
|
||||||
|
@ -42,3 +44,17 @@ export default function useStickyState<T extends string | number | object | bool
|
||||||
|
|
||||||
return [value, setValue];
|
return [value, setValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useStickyReducer<T extends SaveableData, A>(
|
||||||
|
reducer: (state: T, dispatch: A) => T,
|
||||||
|
defaultValue: T | ((old: T | undefined) => T),
|
||||||
|
key: string,
|
||||||
|
): [T, (action: A) => void] {
|
||||||
|
const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
|
||||||
|
function adjustState(action: A) {
|
||||||
|
unsafeSetState(oldState => {
|
||||||
|
return reducer(oldState, action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [state, adjustState];
|
||||||
|
}
|
||||||
|
|
|
@ -667,7 +667,7 @@ export type EPSelectionState = {
|
||||||
},
|
},
|
||||||
equative: EquativeSelection,
|
equative: EquativeSelection,
|
||||||
shrunkenPossesive: undefined | number,
|
shrunkenPossesive: undefined | number,
|
||||||
form: FormVersion,
|
omitSubject: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & {
|
export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & {
|
||||||
|
@ -679,7 +679,7 @@ export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"
|
||||||
type: "Complement",
|
type: "Complement",
|
||||||
selection: EqCompSelection,
|
selection: EqCompSelection,
|
||||||
},
|
},
|
||||||
form: FormVersion,
|
omitSubject: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EqCompType = "adjective" | "loc. adv."; // TODO: - more
|
export type EqCompType = "adjective" | "loc. adv."; // TODO: - more
|
||||||
|
@ -700,11 +700,11 @@ export type EPRendered = {
|
||||||
type: "EPRendered",
|
type: "EPRendered",
|
||||||
king: "subject" | "predicate",
|
king: "subject" | "predicate",
|
||||||
subject: Rendered<NPSelection>,
|
subject: Rendered<NPSelection>,
|
||||||
predicate: Rendered<NPSelection | EqCompSelection>,
|
predicate: Rendered<NPSelection> | Rendered<EqCompSelection>,
|
||||||
equative: EquativeRendered,
|
equative: EquativeRendered,
|
||||||
englishBase?: string[],
|
englishBase?: string[],
|
||||||
shrunkenPossesive: undefined | number,
|
shrunkenPossesive: undefined | number,
|
||||||
form: FormVersion,
|
omitSubject: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EntryFeeder = {
|
export type EntryFeeder = {
|
||||||
|
|
Loading…
Reference in New Issue