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