more customizability on the conjugation viewer

This commit is contained in:
lingdocs 2021-07-09 13:36:56 +03:00
parent 991db5b67f
commit a36ae0a112
9 changed files with 102 additions and 55 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/pashto-inflector", "name": "@lingdocs/pashto-inflector",
"version": "0.6.9", "version": "0.7.0",
"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",

View File

@ -7,7 +7,7 @@
*/ */
import { useEffect, useReducer } from "react"; import { useEffect, useReducer } from "react";
import VerbInfo from "./verb-info/VerbInfo"; import VerbInfo, { RootsAndStems } from "./verb-info/VerbInfo";
import VerbFormDisplay from "./VerbFormDisplay"; import VerbFormDisplay from "./VerbFormDisplay";
import ButtonSelect from "./ButtonSelect"; import ButtonSelect from "./ButtonSelect";
import Hider from "./Hider"; import Hider from "./Hider";
@ -172,11 +172,15 @@ const initialState: State = {
formsOpened: [], formsOpened: [],
}; };
function ConjugationViewer({ entry, complement, textOptions, aayTailType }: { function ConjugationViewer({ entry, complement, textOptions, aayTailType, showOnly, highlightInRootsAndStems, hidePastParticiple, sentenceLevel }: {
entry: T.DictionaryEntry, entry: T.DictionaryEntry,
complement?: T.DictionaryEntry, complement?: T.DictionaryEntry,
textOptions: T.TextOptions, textOptions: T.TextOptions,
aayTailType?: T.AayTail, aayTailType?: T.AayTail,
showOnly?: string | string[],
highlightInRootsAndStems?: T.RootsOrStemsToHighlight,
hidePastParticiple?: boolean,
sentenceLevel?: "easy" | "medium" | "hard",
}) { }) {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => { useEffect(() => {
@ -223,13 +227,22 @@ function ConjugationViewer({ entry, complement, textOptions, aayTailType }: {
const filterDifficulty = (f: T.DisplayForm): boolean => ( const filterDifficulty = (f: T.DisplayForm): boolean => (
state.difficulty === "advanced" || !f.advanced state.difficulty === "advanced" || !f.advanced
); );
const limitTo = !showOnly
? undefined
: Array.isArray(showOnly)
? showOnly
: [showOnly];
const forms = getForms({ const forms = getForms({
conj: verbConj, conj: verbConj,
filterFunc: filterDifficulty, filterFunc: [
filterDifficulty,
...limitTo ? [(f: T.DisplayForm): boolean => limitTo.includes(f.label)] : [],
],
mode: state.mode, mode: state.mode,
subject: state.subject, subject: state.subject,
object: state.object, object: state.object,
negative: state.negative, negative: state.negative,
sentenceLevel,
englishConjugation, englishConjugation,
}); });
return <div className="mb-4"> return <div className="mb-4">
@ -282,44 +295,57 @@ function ConjugationViewer({ entry, complement, textOptions, aayTailType }: {
/> />
</div> </div>
</div>} </div>}
<VerbInfo {!limitTo ?
info={verbConj.info} <VerbInfo
textOptions={textOptions} info={verbConj.info}
showingStemsAndRoots={state.showingStemsAndRoots} textOptions={textOptions}
toggleShowingSar={() => dispatch({ type: "toggle showingStemsAndRoots" })} showingStemsAndRoots={state.showingStemsAndRoots}
/> highlightInRootsAndStems={highlightInRootsAndStems}
toggleShowingSar={() => dispatch({ type: "toggle showingStemsAndRoots" })}
hidePastParticiple={hidePastParticiple}
/>
:
<RootsAndStems
textOptions={textOptions}
info={verbConj.info}
highlighted={highlightInRootsAndStems}
hidePastParticiple={hidePastParticiple}
/>
}
<div className="d-flex flex-row align-items-center justify-content-around flex-wrap mt-4 mb-2"> <div className="d-flex flex-row align-items-center justify-content-around flex-wrap mt-4 mb-2">
<div className="mb-3"> <div className="mb-3">
<ButtonSelect <ButtonSelect
options={[ options={[
{ label: `Charts`, value: "chart" }, { label: `Chart${forms.length !== 1 ? "s" : ""}`, value: "chart" },
{ label: `Sentences`, value: "sentence" }, { label: `Sentences`, value: "sentence" },
]} ]}
value={state.mode} value={state.mode}
handleChange={(p) => dispatch({ type: "setMode", payload: p as "chart" | "sentence" })} handleChange={(p) => dispatch({ type: "setMode", payload: p as "chart" | "sentence" })}
/> />
</div> </div>
<div className="mb-3"> {!limitTo && <>
<ButtonSelect <div className="mb-3">
options={[ <ButtonSelect
{ label: "👶 Beginner", value: "beginner" }, options={[
{ label: "🤓 Advanced", value: "advanced" }, { label: "👶 Beginner", value: "beginner" },
]} { label: "🤓 Advanced", value: "advanced" },
value={state.difficulty} ]}
handleChange={(p) => dispatch({ type: "set difficulty", payload: p as Difficulty })} value={state.difficulty}
/> handleChange={(p) => dispatch({ type: "set difficulty", payload: p as Difficulty })}
</div> />
<div className="form-group form-check"> </div>
<input <div className="form-group form-check">
type="checkbox" <input
className="form-check-input" type="checkbox"
checked={state.showingFormInfo} className="form-check-input"
onChange={(e) => { checked={state.showingFormInfo}
dispatch({ type: "setShowingFormInfo", payload: e.target.checked }) onChange={(e) => {
}} dispatch({ type: "setShowingFormInfo", payload: e.target.checked })
/> }}
<label className="form-check-label">Show Form Info</label> />
</div> <label className="form-check-label">Show Form Info</label>
</div>
</>}
</div> </div>
{state.mode === "sentence" && {state.mode === "sentence" &&
<div className="position-sticky pb-1" style={{ top: 0, background: "var(--theme-shade)", zIndex: 1000 }}> <div className="position-sticky pb-1" style={{ top: 0, background: "var(--theme-shade)", zIndex: 1000 }}>
@ -352,7 +378,7 @@ function ConjugationViewer({ entry, complement, textOptions, aayTailType }: {
state={state} state={state}
handleChange={(payload: string) => dispatch({ type: "set forms opened", payload })} handleChange={(payload: string) => dispatch({ type: "set forms opened", payload })}
verbConj={verbConj} verbConj={verbConj}
textOptions={textOptions} textOptions={textOptions}
/> />
</div>; </div>;
} }
@ -381,6 +407,7 @@ function FormsDisplay({ forms, state, handleChange, textOptions, verbConj }: {
aspect={"aspect" in f ? f.aspect : undefined} aspect={"aspect" in f ? f.aspect : undefined}
showing={state.formsOpened.includes(f.label)} showing={state.formsOpened.includes(f.label)}
handleChange={() => handleChange(f.label)} handleChange={() => handleChange(f.label)}
ignore={forms.length === 1}
> >
{"content" in f ? {"content" in f ?
drawLevel(f.content, level + 1) drawLevel(f.content, level + 1)

View File

@ -27,11 +27,17 @@ function Hider(props: {
handleChange: () => void, handleChange: () => void,
children: React.ReactNode, children: React.ReactNode,
hLevel?: number, hLevel?: number,
ignore?: boolean,
}) { }) {
const hLev = Math.min((props.hLevel ? props.hLevel : defaultLevel), 6); const hLev = Math.min((props.hLevel ? props.hLevel : defaultLevel), 6);
const extraMargin = (props.hLevel && (props.hLevel > indentAfterLevel)) const extraMargin = (props.hLevel && (props.hLevel > indentAfterLevel))
? `ml-${(props.hLevel - indentAfterLevel) + 1}` ? `ml-${(props.hLevel - indentAfterLevel) + 1}`
: ""; : "";
if (props.ignore) {
return <>
{props.children}
</>;
}
return <div className="mb-3"> return <div className="mb-3">
{createElement( {createElement(
`h${hLev}`, `h${hLev}`,

View File

@ -64,7 +64,8 @@ function PersonSelection(props: {
<div className="row align-items-baseline"> <div className="row align-items-baseline">
<div className="col"> <div className="col">
<label className="form-label"> <label className="form-label">
<strong>{props.info.transitivity === "intransitive" ? "Subject" : "Subject/Agent"}</strong> {/* TODO: Should I put the Subject/Agent label back in for non-transitive verbs?? */}
<strong>Subject</strong>
</label> </label>
<PersonSelect <PersonSelect
setting="subject" setting="subject"

View File

@ -30,7 +30,7 @@ const indentR = {
}; };
const highlight = { const highlight = {
background: "yellow", background: "rgba(255, 227, 10, 0.6)",
}; };
const title: CSSProperties = { const title: CSSProperties = {
@ -43,7 +43,7 @@ export function RootsAndStems({ textOptions, info, hidePastParticiple, highlight
textOptions: T.TextOptions, textOptions: T.TextOptions,
info: T.NonComboVerbInfo, info: T.NonComboVerbInfo,
hidePastParticiple?: boolean, hidePastParticiple?: boolean,
highlighted?: ("imperfective root" | "perfective root" | "imperfective stem" | "perfective stem" | "past participle")[], highlighted?: T.RootsOrStemsToHighlight,
}) { }) {
const hasPerfectiveSplit = !!(info.root.perfectiveSplit || info.stem.perfectiveSplit); const hasPerfectiveSplit = !!(info.root.perfectiveSplit || info.stem.perfectiveSplit);
const showPersInf = hasPersInfs(info); const showPersInf = hasPersInfs(info);
@ -173,11 +173,13 @@ export function RootsAndStems({ textOptions, info, hidePastParticiple, highlight
); );
} }
function VerbInfo({ info, textOptions, showingStemsAndRoots, toggleShowingSar }: { function VerbInfo({ info, textOptions, showingStemsAndRoots, toggleShowingSar, highlightInRootsAndStems, hidePastParticiple }: {
info: T.NonComboVerbInfo, info: T.NonComboVerbInfo,
textOptions: T.TextOptions, textOptions: T.TextOptions,
showingStemsAndRoots: boolean, showingStemsAndRoots: boolean,
highlightInRootsAndStems?: T.RootsOrStemsToHighlight,
toggleShowingSar: () => void, toggleShowingSar: () => void,
hidePastParticiple?: boolean,
}) { }) {
const inf = noPersInfs(info.root.imperfective).long; const inf = noPersInfs(info.root.imperfective).long;
return ( return (
@ -195,6 +197,8 @@ function VerbInfo({ info, textOptions, showingStemsAndRoots, toggleShowingSar }:
<RootsAndStems <RootsAndStems
textOptions={textOptions} textOptions={textOptions}
info={info} info={info}
highlighted={highlightInRootsAndStems}
hidePastParticiple={hidePastParticiple}
/> />
</Hider> </Hider>
</div> </div>

View File

@ -254,17 +254,17 @@ function VerbTypeInfo({ info, textOptions }: {
<div className="text-center my-2"> <div className="text-center my-2">
This is a This is a
<button <button
className="btn btn-light mx-2 my-1" className="btn btn-sm btn-light mx-2 my-1"
onClick={() => setShowingTypeModal(true)} onClick={() => setShowingTypeModal(true)}
> >
<strong>{info.type}</strong> <i className={`fa fa-question-circle`}></i> <strong>{info.type}</strong>
</button> </button>
verb and it's verb and it's
<button <button
className="btn btn-light mx-2 my-1" className="btn btn-sm btn-light mx-2 my-1"
onClick={() => setShowingTransModal(true)} onClick={() => setShowingTransModal(true)}
> >
<strong>{info.transitivity}</strong> <i className={`fa fa-question-circle`}></i> <strong>{info.transitivity}</strong>
</button> </button>
</div> </div>
<CompoundBreakdown info={info} textOptions={textOptions} /> <CompoundBreakdown info={info} textOptions={textOptions} />

View File

@ -63,7 +63,7 @@ type Pronouns = undefined | {
const nuParticle = { p: "نه", f: "nú" }; const nuParticle = { p: "نه", f: "nú" };
export default function addPronouns({ s, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative }: { export default function addPronouns({ s, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel = "hard" }: {
s: T.SentenceForm, s: T.SentenceForm,
subject: T.Person, subject: T.Person,
object: T.Person, object: T.Person,
@ -74,13 +74,14 @@ export default function addPronouns({ s, subject, object, info, displayForm, int
matrixKey: T.PersonInflectionsField, matrixKey: T.PersonInflectionsField,
negative: boolean, negative: boolean,
englishConjugation?: T.EnglishVerbConjugation, englishConjugation?: T.EnglishVerbConjugation,
sentenceLevel?: "easy" | "medium" | "hard",
}): T.SentenceForm { }): T.SentenceForm {
if ("long" in s) { if ("long" in s) {
return { return {
long: addPronouns({ s: s.long, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative }) as T.ArrayOneOrMore<T.PsString>, long: addPronouns({ s: s.long, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore<T.PsString>,
short: addPronouns({ s: s.short, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative }) as T.ArrayOneOrMore<T.PsString>, short: addPronouns({ s: s.short, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore<T.PsString>,
...s.mini ? { ...s.mini ? {
mini: addPronouns({ s: s.mini, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative }) as T.ArrayOneOrMore<T.PsString>, mini: addPronouns({ s: s.mini, subject, object, info, displayForm, intransitive, ergative, matrixKey, englishConjugation, negative, sentenceLevel }) as T.ArrayOneOrMore<T.PsString>,
} : {}, } : {},
} }
} }
@ -126,11 +127,11 @@ export default function addPronouns({ s, subject, object, info, displayForm, int
? undefined ? undefined
: noObjectPronoun : noObjectPronoun
? { ? {
subject: nearPronounPossible(subject) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun, subject: ((sentenceLevel === "hard") && nearPronounPossible(subject)) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun,
mini: miniPronoun, mini: miniPronoun,
} : { } : {
subject: nearPronounPossible(subject) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun, subject: ((sentenceLevel === "hard") && nearPronounPossible(subject)) ? [subjectPronoun, nearSubjectPronoun] : subjectPronoun,
object: nearPronounPossible(object) ? [objectPronoun, nearObjectPronoun] : objectPronoun, object: ((sentenceLevel === "hard") && nearPronounPossible(object)) ? [objectPronoun, nearObjectPronoun] : objectPronoun,
mini: miniPronoun, mini: miniPronoun,
}; };
const english = (displayForm.englishBuilder && englishConjugation) const english = (displayForm.englishBuilder && englishConjugation)
@ -163,7 +164,7 @@ export default function addPronouns({ s, subject, object, info, displayForm, int
// basic form two full pronouns // basic form two full pronouns
...makeBasicPronounForm(ps, splitHead, displayForm, info, negative, prns.subject, prns.object), ...makeBasicPronounForm(ps, splitHead, displayForm, info, negative, prns.subject, prns.object),
// basic form one full, one mini pronoun // basic form one full, one mini pronoun
...makeBasicPronounForm( ...sentenceLevel !== "easy" ? makeBasicPronounForm(
ps, ps,
splitHead, splitHead,
displayForm, displayForm,
@ -171,7 +172,7 @@ export default function addPronouns({ s, subject, object, info, displayForm, int
negative, negative,
ergative ? prns.object : prns.subject, ergative ? prns.object : prns.subject,
prns.mini, prns.mini,
), ) : [],
] as T.ArrayOneOrMore<T.PsString>; ] as T.ArrayOneOrMore<T.PsString>;
const ergativeGrammTrans = (info.transitivity === "grammatically transitive" && ergative); const ergativeGrammTrans = (info.transitivity === "grammatically transitive" && ergative);
@ -179,7 +180,7 @@ export default function addPronouns({ s, subject, object, info, displayForm, int
|| transDynCompPast || ergativeGrammTrans; || transDynCompPast || ergativeGrammTrans;
return [ return [
...basicForms, ...basicForms,
...canWorkWithOnlyMini ...(sentenceLevel !== "easy" && canWorkWithOnlyMini)
? makeOnlyMiniForm(ps, splitHead, displayForm, info, negative, prns.mini) ? makeOnlyMiniForm(ps, splitHead, displayForm, info, negative, prns.mini)
: [], : [],
].map((ps) => english ? { ...ps, e: english } : ps) as T.ArrayOneOrMore<T.PsString>; ].map((ps) => english ? { ...ps, e: english } : ps) as T.ArrayOneOrMore<T.PsString>;

View File

@ -40,6 +40,7 @@ type MapFunc = (opts: {
info: T.NonComboVerbInfo, info: T.NonComboVerbInfo,
negative: boolean, negative: boolean,
englishConjugation?: T.EnglishVerbConjugation, englishConjugation?: T.EnglishVerbConjugation,
sentenceLevel?: "easy" | "medium" | "hard",
}) => T.DisplayFormItem; }) => T.DisplayFormItem;
/** /**
@ -87,20 +88,22 @@ const formMap = (
object: T.Person, object: T.Person,
negative: boolean, negative: boolean,
englishConjugation?: T.EnglishVerbConjugation, englishConjugation?: T.EnglishVerbConjugation,
sentenceLevel?: "easy" | "medium" | "hard",
): T.DisplayFormItem[] => { ): T.DisplayFormItem[] => {
return input.map((f) => ( return input.map((f) => (
"content" in f "content" in f
? { ...f, content: formMap(f.content, func, info, subject, object, negative, englishConjugation) } ? { ...f, content: formMap(f.content, func, info, subject, object, negative, englishConjugation, sentenceLevel) }
: func({ displayForm: f as T.DisplayFormForSentence, info, subject, object, negative, englishConjugation }) : func({ displayForm: f as T.DisplayFormForSentence, info, subject, object, negative, englishConjugation, sentenceLevel })
)); ));
}; };
const makeSentence = ({ subject, object, info, displayForm, englishConjugation, negative }: { const makeSentence = ({ subject, object, info, displayForm, englishConjugation, negative, sentenceLevel }: {
subject: T.Person, subject: T.Person,
object: T.Person, object: T.Person,
info: T.NonComboVerbInfo, info: T.NonComboVerbInfo,
displayForm: T.DisplayFormForSentence, displayForm: T.DisplayFormForSentence,
negative: boolean, negative: boolean,
sentenceLevel?: "easy" | "medium" | "hard",
englishConjugation?: T.EnglishVerbConjugation, englishConjugation?: T.EnglishVerbConjugation,
}): T.DisplayForm => { }): T.DisplayForm => {
const intransitive = info.transitivity === "intransitive" || !!displayForm.passive; const intransitive = info.transitivity === "intransitive" || !!displayForm.passive;
@ -127,6 +130,7 @@ const makeSentence = ({ subject, object, info, displayForm, englishConjugation,
matrixKey, matrixKey,
negative, negative,
englishConjugation, englishConjugation,
sentenceLevel,
}); });
return { return {
...displayForm, ...displayForm,
@ -641,13 +645,14 @@ const formsOfConjugation = (conj: T.VerbConjugation): T.DisplayFormItem[] => [
: [], : [],
]; ];
export const getForms = ({ conj, filterFunc, mode, subject, object, englishConjugation, negative } : { export const getForms = ({ conj, filterFunc, mode, subject, object, sentenceLevel, englishConjugation, negative } : {
conj: T.VerbConjugation, conj: T.VerbConjugation,
englishConjugation?: T.EnglishVerbConjugation englishConjugation?: T.EnglishVerbConjugation
filterFunc?: FilterFunc | FilterFunc[], filterFunc?: FilterFunc | FilterFunc[],
mode: "chart" | "sentence", mode: "chart" | "sentence",
subject: T.Person, subject: T.Person,
object: T.Person, object: T.Person,
sentenceLevel?: "easy" | "medium" | "hard",
negative: boolean, negative: boolean,
}): T.DisplayFormItem[] => { }): T.DisplayFormItem[] => {
const forms = formsOfConjugation(conj); const forms = formsOfConjugation(conj);
@ -665,6 +670,7 @@ export const getForms = ({ conj, filterFunc, mode, subject, object, englishConju
object, object,
negative, negative,
englishConjugation, englishConjugation,
sentenceLevel,
); );
} }

View File

@ -389,6 +389,8 @@ export type ArrayOneOrMore<T> = {
0: T 0: T
} & Array<T> } & Array<T>
export type RootsOrStemsToHighlight = ("imperfective root" | "perfective root" | "imperfective stem" | "perfective stem" | "past participle")[];
/* i.e. ec: ["take", "takes", "taking", "took", "taken"], ep: out */ /* i.e. ec: ["take", "takes", "taking", "took", "taken"], ep: out */
export type EnglishVerbConjugationEc = [string, string, string, string, string]; export type EnglishVerbConjugationEc = [string, string, string, string, string];
export type EnglishVerbConjugation = { export type EnglishVerbConjugation = {