Compare commits

..

No commits in common. "c708666bafac66a1c8180327375faff4a5b1ed86" and "a254592fd16ccdb2a01a59564c65f007fcb06cf7" have entirely different histories.

16 changed files with 841 additions and 1166 deletions

View File

@ -17,7 +17,7 @@
"@formkit/auto-animate": "^1.0.0-beta.6", "@formkit/auto-animate": "^1.0.0-beta.6",
"@fortawesome/fontawesome-free": "5.15.4", "@fortawesome/fontawesome-free": "5.15.4",
"@lingdocs/lingdocs-main": "^0.3.3", "@lingdocs/lingdocs-main": "^0.3.3",
"@lingdocs/ps-react": "^7.7.6", "@lingdocs/ps-react": "^7.7.3",
"@mdx-js/rollup": "^2.2.1", "@mdx-js/rollup": "^2.2.1",
"@stefanprobst/rehype-extract-toc": "^2.2.0", "@stefanprobst/rehype-extract-toc": "^2.2.0",
"@types/mdx": "^2.0.3", "@types/mdx": "^2.0.3",

View File

@ -49,13 +49,12 @@ function BasicVerbShowCase({
render={(item) => { render={(item) => {
return { return {
title: ( title: (
<InlinePs <InlinePs opts={opts}>
opts={opts} {{
ps={{
...removeFVarients(item.entry), ...removeFVarients(item.entry),
e: undefined, e: undefined,
}} }}
/> </InlinePs>
), ),
body: ( body: (
<BasicVerbChart <BasicVerbChart

View File

@ -20,10 +20,10 @@ export default function GenderTable({ rows }) {
return <td key={`row-${i}-${gender}`} style={{ background: genderColors[gender === "masc" ? "m" : "f"] }}> return <td key={`row-${i}-${gender}`} style={{ background: genderColors[gender === "masc" ? "m" : "f"] }}>
{item.ending && <div> {item.ending && <div>
{typeof item.ending === "string" ? item.ending {typeof item.ending === "string" ? item.ending
: <InlinePs opts={opts} ps={item.ending} />} : <InlinePs opts={opts}>{item.ending}</InlinePs>}
</div>} </div>}
<div> <div>
{item.ending ? "eg. " : ""}<InlinePs opts={opts} ps={item.ex} /> {item.ending ? "eg. " : ""}<InlinePs opts={opts}>{item.ex}</InlinePs>
</div> </div>
</td> </td>
})} })}

View File

@ -1,67 +1,51 @@
import Carousel from "./Carousel"; import Carousel from "./Carousel";
import { import {
InlinePs, InlinePs,
removeFVarients, removeFVarients,
InflectionsTable, InflectionsTable,
inflectWord, inflectWord,
defaultTextOptions as opts, defaultTextOptions as opts,
getEnglishWord, getEnglishWord,
Types as T, Types as T,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
function InflectionCarousel({ function InflectionCarousel({ items }: { items: (T.NounEntry | T.AdjectiveEntry)[] }) {
items, if (!items.length) {
}: { return "no items for carousel";
items: (T.NounEntry | T.AdjectiveEntry)[]; }
}) { return (
if (!items.length) { <div className="mt-3">
return "no items for carousel"; <Carousel items={items} render={item => {
} const e = getEnglishWord(item);
return ( const english = e === undefined
<div className="mt-3"> ? item.e
<Carousel : typeof e === "string"
items={items} ? e
render={(item) => { : e.singular !== undefined
const e = getEnglishWord(item); ? e.singular
const english = : item.e;
e === undefined const infOut = inflectWord(item);
? item.e if (!infOut || !infOut.inflections) {
: typeof e === "string" return {
? e title: "Oops! 🤷‍♂️",
: e.singular !== undefined // @ts-ignore
? e.singular body: <div>Oops! No inflections for <InlinePs opts={opts}>{item}</InlinePs></div>,
: item.e; };
const infOut = inflectWord(item); }
if (!infOut || !infOut.inflections) { return {
return { // @ts-ignore
title: "Oops! 🤷‍♂️", title: <InlinePs opts={opts} ps={{
// @ts-ignore ...removeFVarients(item),
body: ( e: english,
<div> }} />,
Oops! No inflections for <InlinePs opts={opts} ps={item} /> body: <InflectionsTable
</div> inf={infOut.inflections}
), textOptions={opts}
}; />,
} };
return { }}/>
// @ts-ignore </div>
title: ( );
<InlinePs
opts={opts}
ps={{
...removeFVarients(item),
e: english,
}}
/>
),
body: (
<InflectionsTable inf={infOut.inflections} textOptions={opts} />
),
};
}}
/>
</div>
);
} }
export default InflectionCarousel; export default InflectionCarousel;

View File

@ -1,47 +1,33 @@
import { import {
renderNPSelection, renderNPSelection,
Types as T, Types as T,
renderAPSelection, renderAPSelection,
NPBlock, NPBlock,
APBlock, APBlock,
getEnglishFromRendered, getEnglishFromRendered,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
function BlockDiagram({ function BlockDiagram({ opts, children }: {
opts, opts: T.TextOptions,
children, children: T.NPSelection | T.APSelection,
}: {
opts: T.TextOptions;
children: T.NPSelection | T.APSelection;
}) { }) {
try { try {
const rendered = const rendered = children.type === "AP"
children.type === "AP"
? renderAPSelection(children, 0) ? renderAPSelection(children, 0)
: renderNPSelection(children, false, false, "subject", "none", false); : renderNPSelection(children, false, false, "subject", "none", false);
const english = getEnglishFromRendered(rendered); const english = getEnglishFromRendered(rendered);
return ( return <div className="mb-3">
<div className="mb-3"> <div className="d-flex flex-row justify-content-center text-center" style={{ maxWidth: "100%" }}>
<div {rendered.type === "NP"
className="d-flex flex-row justify-content-center text-center" ? <NPBlock script="f" opts={opts} english={english}>{rendered}</NPBlock>
style={{ maxWidth: "100%" }} : <APBlock script="f" opts={opts} english={english}>{rendered}</APBlock>}
>
{rendered.type === "NP" ? (
<NPBlock script="f" opts={opts} english={english}>
{rendered}
</NPBlock>
) : (
<APBlock script="f" opts={opts} english={english}>
{rendered}
</APBlock>
)}
</div> </div>
</div> </div>;
); } catch(e) {
} catch (e) { console.log(e);
console.log(e); return <div>ERROR</div>;
return <div>ERROR</div>; }
}
} }
export default BlockDiagram;
export default BlockDiagram;

View File

@ -1,95 +1,88 @@
import { Types as T, NPPicker, APPicker } from "@lingdocs/ps-react"; import {
import { useEffect, useRef } from "react"; Types as T,
NPPicker,
APPicker,
} from "@lingdocs/ps-react";
import {
useEffect,
useRef,
} from "react";
import { useState } from "react"; import { useState } from "react";
import BlockDiagram from "./BlockDiagram"; import BlockDiagram from "./BlockDiagram";
import entryFeeder from "../../lib/entry-feeder"; import entryFeeder from "../../lib/entry-feeder";
import autoAnimate from "@formkit/auto-animate"; import autoAnimate from "@formkit/auto-animate";
export function EditIcon() { export function EditIcon() {
return <i className="fas fa-edit" />; return <i className="fas fa-edit" />;
} }
function selectionToBlock( function selectionToBlock(s: T.NPSelection | T.APSelection): { type: "NP", block: T.NPSelection | undefined } | { type: "AP", block: T.APSelection | undefined } {
s: T.NPSelection | T.APSelection return (s.type === "AP")
): ? { type: "AP", block: s }
| { type: "NP"; block: T.NPSelection | undefined } : { type: "NP", block: s };
| { type: "AP"; block: T.APSelection | undefined } {
return s.type === "AP" ? { type: "AP", block: s } : { type: "NP", block: s };
} }
function EditableBlock({ function EditableBlock({ opts, children: block }: {
opts, opts: T.TextOptions,
children: block, children: T.NPSelection | T.APSelection,
}: {
opts: T.TextOptions;
children: T.NPSelection | T.APSelection;
}) { }) {
const parent = useRef<HTMLDivElement>(null); const parent = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
parent.current && autoAnimate(parent.current); parent.current && autoAnimate(parent.current)
}, [parent]); }, [parent]);
const [editing, setEditing] = useState<boolean>(false); const [editing, setEditing] = useState<boolean>(false);
const [edited, setEdited] = useState< const [edited, setEdited] = useState<{
| { type: "NP",
type: "NP"; block: T.NPSelection | undefined,
block: T.NPSelection | undefined; } | {
} type: "AP",
| { block: T.APSelection | undefined,
type: "AP"; }>(selectionToBlock(block));
block: T.APSelection | undefined; function handleNPChange(np: T.NPSelection | undefined) {
} setEdited({ type: "NP", block: np });
>(selectionToBlock(block)); }
function handleNPChange(np: T.NPSelection | undefined) { function handleAPChange(ap: T.APSelection | undefined) {
setEdited({ type: "NP", block: np }); setEdited({ type: "AP", block: ap });
} }
function handleAPChange(ap: T.APSelection | undefined) { function handleReset() {
setEdited({ type: "AP", block: ap }); setEdited(selectionToBlock(block));
} setEditing(false);
function handleReset() { }
setEdited(selectionToBlock(block)); return <div>
setEditing(false); <div
} className="text-right clickable"
return ( onClick={editing ? handleReset : () => setEditing(true)}
<div> >
<div {!editing ? <EditIcon /> : <i className="fas fa-undo" />}
className="text-right clickable" </div>
onClick={editing ? handleReset : () => setEditing(true)} <div ref={parent} className="d-flex flex-column align-items-center">
> {editing && <div style={{ maxWidth: "225px", marginBottom: "2rem" }}>
{!editing ? <EditIcon /> : <i className="fas fa-undo" />} {edited.type === "NP"
</div> ? <NPPicker
<div ref={parent} className="d-flex flex-column align-items-center"> opts={opts}
{editing && ( np={edited.block}
<div style={{ maxWidth: "225px", marginBottom: "2rem" }}> onChange={handleNPChange}
{edited.type === "NP" ? ( entryFeeder={entryFeeder}
<NPPicker role="subject"
opts={opts} counterPart={undefined}
np={edited.block} phraseIsComplete={false}
onChange={handleNPChange} />
entryFeeder={entryFeeder} : <APPicker
role="subject" opts={opts}
counterPart={undefined} AP={edited.block}
phraseIsComplete={false} onChange={handleAPChange}
negative={false} entryFeeder={entryFeeder}
/> phraseIsComplete={false}
) : ( onRemove={() => null}
<APPicker />
opts={opts} }
AP={edited.block} </div>}
onChange={handleAPChange} {edited.block
entryFeeder={entryFeeder} && <BlockDiagram opts={opts}>
phraseIsComplete={false} {edited.block}
onRemove={() => null} </BlockDiagram>}
negative={false} </div>
/> </div>;
)}
</div>
)}
{edited.block && (
<BlockDiagram opts={opts}>{edited.block}</BlockDiagram>
)}
</div>
</div>
);
} }
export default EditableBlock; export default EditableBlock;

View File

@ -1,123 +1,36 @@
import Carousel from "../../components/Carousel"; import Carousel from "../../components/Carousel";
import { import {
defaultTextOptions as opts, defaultTextOptions as opts,
InlinePs, InlinePs,
RootsAndStems, RootsAndStems,
getVerbInfo, getVerbInfo,
getAbilityRootsAndStems, getAbilityRootsAndStems,
removeFVarients, removeFVarients,
ensureNonComboVerbInfo, ensureNonComboVerbInfo,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
export default function GoingStems() { export default function GoingStems() {
const items = [ const items = [
{ {"ts":1527815348,"i":3666,"p":"تلل","f":"tlul","g":"tlul","e":"to go","c":"v. intrans. irreg.","psp":"ځ","psf":"dz","ssp":"لاړ ش","ssf":"láaR sh","prp":"لاړ","prf":"láaR","ec":"go,goes,going,went,gone"},
ts: 1527815348, {"ts":1527815216,"i":6642,"p":"راتلل","f":"raatlúl","g":"raatlul","e":"to come","c":"v. intrans. irreg.","psp":"راځ","psf":"raadz","ssp":"راش","ssf":"ráash","prp":"راغلل","prf":"ráaghlul","pprtp":"راغلی","pprtf":"raaghúley","tppp":"راغی","tppf":"ráaghey","noOo":true,"separationAtP":2,"separationAtF":3,"ec":"come,comes,coming,came,come"},
i: 3666, {"ts":1585228551150,"i":6062,"p":"درتلل","f":"dărtlul","g":"dartlul","e":"to come (to you / second person)","c":"v. intrans. irreg.","psp":"درځ","psf":"dărdz","ssp":"درش","ssf":"dársh","prp":"درغلل","prf":"dáraghlul","pprtp":"درغلی","pprtf":"dăraghúley","tppp":"درغی","tppf":"dáraghey","noOo":true,"separationAtP":2,"separationAtF":3,"ec":"come,comes,coming,came,come"},
p: "تلل", {"ts":1585228579997,"i":14282,"p":"ورتلل","f":"wărtlul","g":"wartlul","e":"to come / go over to (third person or place)","c":"v. intrans. irreg.","psp":"ورځ","psf":"wărdz","ssp":"ورش","ssf":"wársh","prp":"ورغلل","prf":"wáraghlul","pprtp":"ورغلی","pprtf":"wăraghúley","tppp":"ورغی","tppf":"wáraghey","noOo":true,"separationAtP":2,"separationAtF":3,"ec":"come,comes,coming,came,come"},
f: "tlul", ].map(entry => ({ entry }));
g: "tlul", return <Carousel stickyTitle items={items} render={(item) => {
e: "to go", const rs = getAbilityRootsAndStems(
c: "v. intrans. irreg.", ensureNonComboVerbInfo(getVerbInfo(item.entry))
psp: "ځ", );
psf: "dz", return {
ssp: "لاړ ش", title: <InlinePs opts={opts}>
ssf: "láaR sh", {{
prp: "لاړ", ...removeFVarients(item.entry),
prf: "láaR", e: undefined,
ec: "go,goes,going,went,gone", }}
}, </InlinePs>,
{ body: <RootsAndStems
ts: 1527815216, textOptions={opts}
i: 6642, info={rs}
p: "راتلل", />,
f: "raatlúl", };
g: "raatlul", }}/>
e: "to come", }
c: "v. intrans. irreg.",
psp: "راځ",
psf: "raadz",
ssp: "راش",
ssf: "ráash",
prp: "راغلل",
prf: "ráaghlul",
pprtp: "راغلی",
pprtf: "raaghúley",
tppp: "راغی",
tppf: "ráaghey",
noOo: true,
separationAtP: 2,
separationAtF: 3,
ec: "come,comes,coming,came,come",
},
{
ts: 1585228551150,
i: 6062,
p: "درتلل",
f: "dărtlul",
g: "dartlul",
e: "to come (to you / second person)",
c: "v. intrans. irreg.",
psp: "درځ",
psf: "dărdz",
ssp: "درش",
ssf: "dársh",
prp: "درغلل",
prf: "dáraghlul",
pprtp: "درغلی",
pprtf: "dăraghúley",
tppp: "درغی",
tppf: "dáraghey",
noOo: true,
separationAtP: 2,
separationAtF: 3,
ec: "come,comes,coming,came,come",
},
{
ts: 1585228579997,
i: 14282,
p: "ورتلل",
f: "wărtlul",
g: "wartlul",
e: "to come / go over to (third person or place)",
c: "v. intrans. irreg.",
psp: "ورځ",
psf: "wărdz",
ssp: "ورش",
ssf: "wársh",
prp: "ورغلل",
prf: "wáraghlul",
pprtp: "ورغلی",
pprtf: "wăraghúley",
tppp: "ورغی",
tppf: "wáraghey",
noOo: true,
separationAtP: 2,
separationAtF: 3,
ec: "come,comes,coming,came,come",
},
].map((entry) => ({ entry }));
return (
<Carousel
stickyTitle
items={items}
render={(item) => {
const rs = getAbilityRootsAndStems(
ensureNonComboVerbInfo(getVerbInfo(item.entry))
);
return {
title: (
<InlinePs
opts={opts}
ps={{
...removeFVarients(item.entry),
e: undefined,
}}
/>
),
body: <RootsAndStems textOptions={opts} info={rs} />,
};
}}
/>
);
}

View File

@ -1,15 +1,17 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { comparePs } from "../../lib/game-utils"; import {
comparePs,
} from "../../lib/game-utils";
import GameCore from "../GameCore"; import GameCore from "../GameCore";
import { import {
Types as T, Types as T,
Examples, Examples,
defaultTextOptions as opts, defaultTextOptions as opts,
renderEP, renderEP,
compileEP, compileEP,
flattenLengths, flattenLengths,
InlinePs, InlinePs,
grammarUnits, grammarUnits,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
import { randomEPSPool } from "./makeRandomEPS"; import { randomEPSPool } from "./makeRandomEPS";
@ -19,215 +21,168 @@ const amount = 12;
const timeLimit = 100; const timeLimit = 100;
type Question = { type Question = {
EPS: T.EPSelectionComplete; EPS: T.EPSelectionComplete,
phrase: { ps: T.PsString[]; e?: string[] }; phrase: { ps: T.PsString[], e?: string[] },
equative: T.EquativeRendered; equative: T.EquativeRendered,
}; };
export default function EquativeGame({ export default function EquativeGame({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: T.EquativeTense | "allTenses" }) {
inChapter, const epsPool = randomEPSPool(level);
id, function getQuestion(): Question {
link, const EPS = epsPool();
level, const EP = renderEP(EPS);
}: { const compiled = compileEP(
inChapter: boolean; EP,
id: string; true,
link: string; { equative: true, kidsSection: true },
level: T.EquativeTense | "allTenses"; );
}) { const phrase = {
const epsPool = randomEPSPool(level); ps: compiled.ps,
function getQuestion(): Question { e: compiled.e,
const EPS = epsPool(); };
const EP = renderEP(EPS); return {
const compiled = compileEP(EP, true, { equative: true, kidsSection: true }); EPS,
const phrase = { phrase,
ps: compiled.ps, equative: getEqFromRendered(EP),
e: compiled.e, };
}; };
return {
EPS, function Display({ question, callback }: QuestionDisplayProps<Question>) {
phrase, const [answer, setAnswer] = useState<string>("");
equative: getEqFromRendered(EP), const [withBa, setWithBa] = useState<boolean>(false);
}; const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
} setAnswer(value);
}
function Display({ question, callback }: QuestionDisplayProps<Question>) { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const [answer, setAnswer] = useState<string>(""); e.preventDefault();
const [withBa, setWithBa] = useState<boolean>(false); const correct = comparePs(answer, question.equative.ps)
const handleInput = ({ && (withBa === question.equative.hasBa);
target: { value }, if (correct) {
}: React.ChangeEvent<HTMLInputElement>) => { setAnswer("");
setAnswer(value); }
}; callback(correct);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { }
e.preventDefault(); useEffect(() => {
const correct = if (level === "allTenses") setWithBa(false);
comparePs(answer, question.equative.ps) && }, [question]);
withBa === question.equative.hasBa;
if (correct) { return <div>
setAnswer(""); <div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
} <Examples lineHeight={1} opts={opts}>
callback(correct); {/* @ts-ignore TODO: REMOVE AS P_INFLE */}
}; {modExs(question.phrase.ps, withBa)[0]}
useEffect(() => { </Examples>
if (level === "allTenses") setWithBa(false); {question.phrase.e && question.phrase.e.map((e, i) => (
}, [question]); <div key={e+i} className="text-muted">{e}</div>
))}
return ( <div>{humanReadableTense(question.EPS.equative.tense)} equative</div>
<div> </div>
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}> <form onSubmit={handleSubmit}>
<Examples lineHeight={1} opts={opts}> <div className="form-check mt-1">
{/* @ts-ignore TODO: REMOVE AS P_INFLE */} <input
{modExs(question.phrase.ps, withBa)[0]} id="baCheckbox"
</Examples> className="form-check-input"
{question.phrase.e && type="checkbox"
question.phrase.e.map((e, i) => ( checked={withBa}
<div key={e + i} className="text-muted"> onChange={e => setWithBa(e.target.checked)}
{e} />
</div> <label className="form-check-label text-muted" htmlFor="baCheckbox">
))} with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the <span style={{ color: kidsColor }}>kids' section</span>
<div>{humanReadableTense(question.EPS.equative.tense)} equative</div> </label>
</div> </div>
<form onSubmit={handleSubmit}> <div className="my-1" style={{ maxWidth: "200px", margin: "0 auto" }}>
<div className="form-check mt-1"> <input
<input type="text"
id="baCheckbox" className="form-control"
className="form-check-input" autoComplete="off"
type="checkbox" autoCapitalize="off"
checked={withBa} spellCheck="false"
onChange={(e) => setWithBa(e.target.checked)} dir="auto"
/> value={answer}
<label className="form-check-label text-muted" htmlFor="baCheckbox"> onChange={handleInput}
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the{" "} />
<span style={{ color: kidsColor }}>kids' section</span> </div>
</label> <div className="text-center my-2">
</div> {/* <div> */}
<div className="my-1" style={{ maxWidth: "200px", margin: "0 auto" }}> <button className="btn btn-primary" type="submit">submit </button>
<input {/* </div> */}
type="text" {/* <div className="text-muted small text-center mt-2">
className="form-control"
autoComplete="off"
autoCapitalize="off"
spellCheck="false"
dir="auto"
value={answer}
onChange={handleInput}
/>
</div>
<div className="text-center my-2">
{/* <div> */}
<button className="btn btn-primary" type="submit">
submit
</button>
{/* </div> */}
{/* <div className="text-muted small text-center mt-2">
Type <kbd>Enter</kbd> to check Type <kbd>Enter</kbd> to check
</div> */} </div> */}
</div> </div>
</form> </form>
</div> </div>
);
}
function Instructions() {
return (
<div>
<p className="lead">
Fill in the blank with the correct{" "}
{level === "allTenses" ? "" : humanReadableTense(level)} equative
</p>
{level === "allTenses" && <div> All tenses included...</div>}
</div>
);
}
return (
<GameCore
inChapter={inChapter}
studyLink={link}
getQuestion={getQuestion}
id={id}
Display={Display}
DisplayCorrectAnswer={DisplayCorrectAnswer}
timeLimit={level === "allTenses" ? timeLimit * 1.3 : timeLimit}
amount={amount}
Instructions={Instructions}
/>
);
}
function DisplayCorrectAnswer({
question,
}: {
question: Question;
}): JSX.Element {
return (
<div>
<div>
{flattenLengths(question.equative.ps).reduce(
(accum, curr, i): JSX.Element[] => [
...accum,
...(i > 0 ? [<span className="text-muted"> or </span>] : []),
<span key={i}>{curr.p}</span>,
],
[] as JSX.Element[]
)}
</div>
<div>
<strong>{question.equative.hasBa ? "with" : "without"}</strong> a{" "}
<InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the kids'
section.
</div>
</div>
);
}
function modExs(
exs: T.PsString[],
withBa: boolean
): { p: JSX.Element; f: JSX.Element }[] {
return exs.map((ps) => {
if (!ps.p.includes(" ___ ")) {
return {
p: <>{ps.p}</>,
f: <>{ps.f}</>,
};
} }
const splitP = ps.p.split(" ___ ");
const splitF = ps.f.split(" ___ "); function Instructions() {
return { return <div>
p: ( <p className="lead">
<> Fill in the blank with the correct {level === "allTenses" ? "" : humanReadableTense(level)} equative
{splitP[0]}{" "} </p>
<span style={{ color: kidsColor }}>{withBa ? "به" : "__"}</span>{" "} {level === "allTenses" && <div> All tenses included...</div>}
{splitP[1]} </div>;
</> }
),
f: ( return <GameCore
<> inChapter={inChapter}
{splitF[0]}{" "} studyLink={link}
<span style={{ color: kidsColor }}>{withBa ? "ba" : "__"}</span>{" "} getQuestion={getQuestion}
{splitF[1]} id={id}
</> Display={Display}
), DisplayCorrectAnswer={DisplayCorrectAnswer}
}; timeLimit={level === "allTenses" ? timeLimit * 1.3 : timeLimit}
}); amount={amount}
Instructions={Instructions}
/>
};
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
return <div>
<div>
{flattenLengths(question.equative.ps).reduce(((accum, curr, i): JSX.Element[] => (
[
...accum,
...i > 0 ? [<span className="text-muted"> or </span>] : [],
<span key={i}>{curr.p}</span>,
]
)), [] as JSX.Element[])}
</div>
<div><strong>{question.equative.hasBa ? "with" : "without"}</strong> a <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids' section.</div>
</div>;
}
function modExs(exs: T.PsString[], withBa: boolean): { p: JSX.Element, f: JSX.Element }[] {
return exs.map(ps => {
if (!ps.p.includes(" ___ ")) {
return {
p: <>{ps.p}</>,
f: <>{ps.f}</>,
};
}
const splitP = ps.p.split(" ___ ");
const splitF = ps.f.split(" ___ ");
return {
p: <>{splitP[0]} <span style={{ color: kidsColor }}>{withBa ? "به" : "__"}</span> {splitP[1]}</>,
f: <>{splitF[0]} <span style={{ color: kidsColor }}>{withBa ? "ba" : "__"}</span> {splitF[1]}</>,
};
});
} }
function humanReadableTense(tense: T.EquativeTense | "allProduce"): string { function humanReadableTense(tense: T.EquativeTense | "allProduce"): string {
return tense === "allProduce" return tense === "allProduce"
? "" ? ""
: tense === "pastSubjunctive" : tense === "pastSubjunctive"
? "past subjunctive" ? "past subjunctive"
: tense === "wouldBe" : tense === "wouldBe"
? `"would be"` ? `"would be"`
: tense === "wouldHaveBeen" : tense === "wouldHaveBeen"
? `"would have been"` ? `"would have been"`
: tense; : tense;
} }
function getEqFromRendered(e: T.EPRendered): T.EquativeRendered { function getEqFromRendered(e: T.EPRendered): T.EquativeRendered {
const eblock = e.blocks[0].find((x) => x.block.type === "equative"); const eblock = e.blocks[0].find(x => x.block.type === "equative");
if (!eblock || eblock.block.type !== "equative") if (!eblock || eblock.block.type !== "equative") throw new Error("Error getting equative block");
throw new Error("Error getting equative block"); return eblock.block.equative;
return eblock.block.equative; }
}

View File

@ -1,13 +1,13 @@
import GameCore from "../GameCore"; import GameCore from "../GameCore";
import { import {
Types as T, Types as T,
renderNPSelection, renderNPSelection,
getEnglishFromRendered, getEnglishFromRendered,
getPashtoFromRendered, getPashtoFromRendered,
renderAPSelection, renderAPSelection,
InlinePs, InlinePs,
defaultTextOptions as opts, defaultTextOptions as opts,
concatPsString, concatPsString,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
import { makeNPAdjGenerator } from "../../lib/block-generators/np-adj-generator"; import { makeNPAdjGenerator } from "../../lib/block-generators/np-adj-generator";
import { useState } from "react"; import { useState } from "react";
@ -19,183 +19,141 @@ const amount = 16;
const timeLimit = 270; const timeLimit = 270;
type Question = { type Question = {
selection: T.NPSelection | T.APSelection; selection: T.NPSelection | T.APSelection,
answer: T.PsString[]; answer: T.PsString[],
english: string; english: string,
}; };
type Level = "hints" | "no-hints" | "sandwiches"; type Level = "hints" | "no-hints" | "sandwiches";
const NPAdjWriting: GameSubCore<Level> = ({ const NPAdjWriting: GameSubCore<Level> = ({ inChapter, id, link, level }: {
inChapter, inChapter: boolean,
id, id: string,
link, link: string,
level, level: Level,
}: {
inChapter: boolean;
id: string;
link: string;
level: Level;
}) => { }) => {
const npPool = makeNPAdjGenerator(level === "sandwiches" ? "low" : "high"); const npPool = makeNPAdjGenerator(level === "sandwiches" ? "low" : "high");
function getQuestion(): Question { function getQuestion(): Question {
const np = npPool(); const np = npPool();
const selection: T.NPSelection | T.APSelection = const selection: T.NPSelection | T.APSelection = level === "sandwiches"
level === "sandwiches" ? makeSandwich(np) : np; ? makeSandwich(np) : np;
const rendered: T.Rendered<T.NPSelection> | T.Rendered<T.APSelection> = const rendered: T.Rendered<T.NPSelection> | T.Rendered<T.APSelection> = selection.type === "AP"
selection.type === "AP" ? renderAPSelection(selection, 0) // WOULD BE CLEANER IF THIS WAS JUST A PURE SANDWICH, NOT AT AP
? renderAPSelection(selection, 0) // WOULD BE CLEANER IF THIS WAS JUST A PURE SANDWICH, NOT AT AP : renderNPSelection(np, false, false, "subject", "none", false);
: renderNPSelection(np, false, false, "subject", "none", false); const answer = getPashtoFromRendered(rendered, false);
const answer = getPashtoFromRendered(rendered, false); return {
return { selection,
selection, answer,
answer, english: getEnglishFromRendered(rendered) || "ERROR",
english: getEnglishFromRendered(rendered) || "ERROR", };
}; };
}
function Display({ question, callback }: QuestionDisplayProps<Question>) {
function Display({ question, callback }: QuestionDisplayProps<Question>) { const [answer, setAnswer] = useState<string>("");
const [answer, setAnswer] = useState<string>(""); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault();
e.preventDefault(); const correct = comparePs(answer, question.answer);
const correct = comparePs(answer, question.answer); if (correct) {
if (correct) { setAnswer("");
setAnswer(""); }
} callback(correct);
callback(correct); }
}; if (!(
if ( (question.selection.type === "AP" && question.selection.selection.type === "sandwich" && question.selection.selection.inside.selection.type === "noun")
!( ||
(question.selection.type === "AP" && (question.selection.type === "NP" && question.selection.selection.type === "noun")
question.selection.selection.type === "sandwich" && )) {
question.selection.selection.inside.selection.type === "noun") || throw new Error("QUESTION ERROR - BAD SELECTION")
(question.selection.type === "NP" && }
question.selection.selection.type === "noun") const nounSelection: T.NounSelection = question.selection.type === "AP"
) ? question.selection.selection.inside.selection as T.NounSelection // ts being dumb
) { : question.selection.selection;
throw new Error("QUESTION ERROR - BAD SELECTION"); const adjEntry: T.AdjectiveEntry | undefined = nounSelection.adjectives[0]?.entry;
} if (!adjEntry) {
const nounSelection: T.NounSelection = throw new Error("QUESTION ERROR - MISSING ADJECTIVE");
question.selection.type === "AP" }
? (question.selection.selection.inside.selection as T.NounSelection) // ts being dumb const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
: question.selection.selection; setAnswer(value);
const adjEntry: T.AdjectiveEntry | undefined = }
nounSelection.adjectives[0]?.entry; const sandwich = question.selection.type === "AP"
if (!adjEntry) { ? question.selection.selection
throw new Error("QUESTION ERROR - MISSING ADJECTIVE"); : undefined;
} return <div>
const handleInput = ({ <div className="my-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
target: { value }, <div className="d-flex flex-row justify-content-center" style={{ gap: "1rem"}}>
}: React.ChangeEvent<HTMLInputElement>) => { <WordCard
setAnswer(value); showHint={level === "hints"}
}; entry={adjEntry}
const sandwich = selection={undefined}
question.selection.type === "AP" />
? question.selection.selection <WordCard
: undefined; showHint={level === "hints"}
return ( entry={nounSelection.entry}
<div> selection={nounSelection}
<div className="my-2" style={{ maxWidth: "300px", margin: "0 auto" }}> />
<div </div>
className="d-flex flex-row justify-content-center" {sandwich && <div className="mt-2">
style={{ gap: "1rem" }} <InlinePs opts={opts}>
> {concatPsString(sandwich.before, " ... ", sandwich.after)}
<WordCard </InlinePs>
showHint={level === "hints"} </div>}
entry={adjEntry} <div className="my-3 h5">
selection={undefined} {question.english}
/> </div>
<WordCard <form onSubmit={handleSubmit}>
showHint={level === "hints"} <div className="mt-2 mb-1" style={{ maxWidth: "200px", margin: "0 auto" }}>
entry={nounSelection.entry} <input
selection={nounSelection} type="text"
/> className="form-control"
</div> autoComplete="off"
{sandwich && ( autoCapitalize="off"
<div className="mt-2"> spellCheck="false"
<InlinePs dir="auto"
opts={opts} value={answer}
ps={concatPsString(sandwich.before, " ... ", sandwich.after)} onChange={handleInput}
/> />
</div>
<div className="text-center my-3">
<button className="btn btn-primary" type="submit">submit </button>
</div>
</form>
</div> </div>
)}
<div className="my-3 h5">{question.english}</div>
<form onSubmit={handleSubmit}>
<div
className="mt-2 mb-1"
style={{ maxWidth: "200px", margin: "0 auto" }}
>
<input
type="text"
className="form-control"
autoComplete="off"
autoCapitalize="off"
spellCheck="false"
dir="auto"
value={answer}
onChange={handleInput}
/>
</div>
<div className="text-center my-3">
<button className="btn btn-primary" type="submit">
submit
</button>
</div>
</form>
</div> </div>
</div> }
);
} function Instructions() {
return <div>
<p className="lead">Write the {level === "sandwiches" ? "sandwich including the" : ""} adjective and noun together with the proper inflections.</p>
</div>
}
function Instructions() { return <GameCore
return ( inChapter={inChapter}
<div> studyLink={link}
<p className="lead"> getQuestion={getQuestion}
Write the {level === "sandwiches" ? "sandwich including the" : ""}{" "} id={id}
adjective and noun together with the proper inflections. Display={Display}
</p> DisplayCorrectAnswer={DisplayCorrectAnswer}
</div> timeLimit={timeLimit}
); amount={amount}
} Instructions={Instructions}
return (
<GameCore
inChapter={inChapter}
studyLink={link}
getQuestion={getQuestion}
id={id}
Display={Display}
DisplayCorrectAnswer={DisplayCorrectAnswer}
timeLimit={timeLimit}
amount={amount}
Instructions={Instructions}
/> />
);
}; };
function DisplayCorrectAnswer({ function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
question, // TODO: extract this reduces for the or, or different variations, used in VerbGame (etc.?)
}: { return <div>
question: Question; <div>
}): JSX.Element { {question.answer.reduce(((accum, curr, i): JSX.Element[] => (
// TODO: extract this reduces for the or, or different variations, used in VerbGame (etc.?) [
return ( ...accum,
<div> ...i > 0 ? [<span className="text-muted"> or </span>] : [],
<div> <span key={i}>{curr.p} - {curr.f}</span>,
{question.answer.reduce( ]
(accum, curr, i): JSX.Element[] => [ )), [] as JSX.Element[])}
...accum, </div>
...(i > 0 ? [<span className="text-muted"> or </span>] : []), </div>;
<span key={i}>
{curr.p} - {curr.f}
</span>,
],
[] as JSX.Element[]
)}
</div>
</div>
);
} }
export default NPAdjWriting; export default NPAdjWriting;

View File

@ -270,8 +270,8 @@ const VerbGame: GameSubCore<PerfectGameLevel> = ({
className="form-check-label text-muted" className="form-check-label text-muted"
htmlFor="baCheckbox" htmlFor="baCheckbox"
> >
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs>{" "}
the <span style={{ color: kidsColor }}>kids' section</span> in the <span style={{ color: kidsColor }}>kids' section</span>
</label> </label>
</div> </div>
)} )}
@ -352,7 +352,7 @@ function DisplayCorrectAnswer({
</div> </div>
<div> <div>
<strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a{" "} <strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a{" "}
<InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the kids' <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids'
section. section.
</div> </div>
</div> </div>

View File

@ -45,7 +45,7 @@ export const nouns = wordQuery("nouns", [
"gaawanDay", "gaawanDay",
"sakhtee", "sakhtee",
"dostee", "dostee",
"aRtyaa", "aRtiyaa",
"DaakTar", "DaakTar",
"laas", "laas",
"waadu", "waadu",

View File

@ -1,10 +1,10 @@
import GameCore from "../GameCore"; import GameCore from "../GameCore";
import { import {
humanReadableVerbForm, humanReadableVerbForm,
Types as T, Types as T,
InlinePs, InlinePs,
grammarUnits, grammarUnits,
defaultTextOptions as opts, defaultTextOptions as opts,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
import { makePool } from "../../lib/pool"; import { makePool } from "../../lib/pool";
import { CSSProperties, useEffect, useState } from "react"; import { CSSProperties, useEffect, useState } from "react";
@ -14,439 +14,347 @@ import { isImperativeTense } from "@lingdocs/ps-react/dist/lib/src/type-predicat
const amount = 12; const amount = 12;
const timeLimit = 125; const timeLimit = 125;
type StemRoot = type StemRoot = "imperfective stem" | "perfective stem" | "imperfective root" | "perfective root";
| "imperfective stem" type Ending = "present" | "past" | "imperative";
| "perfective stem"
| "imperfective root"
| "perfective root";
type Ending = "present" | "past" | "imperative";
type Formula = { type Formula = {
ba: boolean; ba: boolean,
mu: boolean; mu: boolean,
stemRoot: StemRoot; stemRoot: StemRoot,
ending: Ending; ending: Ending,
}; };
type Question = { type Question = {
tense: T.VerbTense | T.ImperativeTense | "negative imperative"; tense: T.VerbTense | T.ImperativeTense | "negative imperative",
formula: Formula; formula: Formula,
}; }
const questions: Question[] = [ const questions: Question[] = [
{ {
tense: "presentVerb", tense: "presentVerb",
formula: { formula: {
ba: false, ba: false,
mu: false, mu: false,
stemRoot: "imperfective stem", stemRoot: "imperfective stem",
ending: "present", ending: "present",
},
}, },
}, {
{ tense: "subjunctiveVerb",
tense: "subjunctiveVerb", formula: {
formula: { ba: false,
ba: false, mu: false,
mu: false, stemRoot: "perfective stem",
stemRoot: "perfective stem", ending: "present",
ending: "present", },
}, },
}, {
{ tense: "imperfectiveFuture",
tense: "imperfectiveFuture", formula: {
formula: { ba: true,
ba: true, mu: false,
mu: false, stemRoot: "imperfective stem",
stemRoot: "imperfective stem", ending: "present",
ending: "present", },
}, },
}, {
{ tense: "perfectiveFuture",
tense: "perfectiveFuture", formula: {
formula: { ba: true,
ba: true, mu: false,
mu: false, stemRoot: "perfective stem",
stemRoot: "perfective stem", ending: "present",
ending: "present", },
}, },
}, {
{ tense: "imperfectivePast",
tense: "imperfectivePast", formula: {
formula: { ba: false,
ba: false, mu: false,
mu: false, stemRoot: "imperfective root",
stemRoot: "imperfective root", ending: "past",
ending: "past", },
}, },
}, {
{ tense: "perfectivePast",
tense: "perfectivePast", formula: {
formula: { ba: false,
ba: false, mu: false,
mu: false, stemRoot: "perfective root",
stemRoot: "perfective root", ending: "past",
ending: "past", },
}, },
}, {
{ tense: "habitualImperfectivePast",
tense: "habitualImperfectivePast", formula: {
formula: { ba: true,
ba: true, mu: false,
mu: false, stemRoot: "imperfective root",
stemRoot: "imperfective root", ending: "past",
ending: "past", },
}, },
}, {
{ tense: "habitualPerfectivePast",
tense: "habitualPerfectivePast", formula: {
formula: { ba: true,
ba: true, mu: false,
mu: false, stemRoot: "perfective root",
stemRoot: "perfective root", ending: "past",
ending: "past", },
}, },
}, {
{ tense: "perfectiveImperative",
tense: "perfectiveImperative", formula: {
formula: { ba: false,
ba: false, mu: false,
mu: false, stemRoot: "perfective stem",
stemRoot: "perfective stem", ending: "imperative",
ending: "imperative", },
}, },
}, {
{ tense: "imperfectiveImperative",
tense: "imperfectiveImperative", formula: {
formula: { ba: false,
ba: false, mu: false,
mu: false, stemRoot: "imperfective stem",
stemRoot: "imperfective stem", ending: "imperative",
ending: "imperative", },
}, },
}, {
{ tense: "negative imperative",
tense: "negative imperative", formula: {
formula: { ba: false,
ba: false, mu: true,
mu: true, stemRoot: "imperfective stem",
stemRoot: "imperfective stem", ending: "imperative",
ending: "imperative", },
}, }
},
]; ];
export default function VerbFormulas({ export default function VerbFormulas({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "all" }) {
inChapter, const questionsPool = makePool(questions);
id, function getQuestion() {
link, return questionsPool();
level, }
}: { function Instructions() {
inChapter: boolean; return <p className="lead">Pick the formula for each verb tense</p>;
id: string; }
link: string; return <GameCore
level: "all"; inChapter={inChapter}
}) { studyLink={link}
const questionsPool = makePool(questions); getQuestion={getQuestion}
function getQuestion() { id={id}
return questionsPool(); Display={Display}
} DisplayCorrectAnswer={DisplayCorrectAnswer}
function Instructions() { timeLimit={timeLimit}
return <p className="lead">Pick the formula for each verb tense</p>; amount={amount}
} Instructions={Instructions}
return (
<GameCore
inChapter={inChapter}
studyLink={link}
getQuestion={getQuestion}
id={id}
Display={Display}
DisplayCorrectAnswer={DisplayCorrectAnswer}
timeLimit={timeLimit}
amount={amount}
Instructions={Instructions}
/> />
); };
}
function Display({ question, callback }: QuestionDisplayProps<Question>) { function Display({ question, callback }: QuestionDisplayProps<Question>) {
const [ba, setBa] = useState<boolean>(false); const [ba, setBa] = useState<boolean>(false);
const [mu, setMu] = useState<boolean>(false); const [mu, setMu] = useState<boolean>(false);
const [stemRoot, setStemRoot] = useState<StemRoot | "">(""); const [stemRoot, setStemRoot] = useState<StemRoot | "">("");
const [ending, setEnding] = useState<Ending | "">(""); const [ending, setEnding] = useState<Ending | "">("");
useEffect(() => { useEffect(() => {
setBa(false); setBa(false);
setStemRoot(""); setStemRoot("");
setEnding(""); setEnding("");
setMu(false); setMu(false);
}, [question]); }, [question]);
const canSubmit = !!(stemRoot && ending); const canSubmit = !!(stemRoot && ending);
const showMu = const showMu = question.tense === "negative imperative" || isImperativeTense(question.tense);
question.tense === "negative imperative" || function handleSubmit() {
isImperativeTense(question.tense); const { formula } = question;
function handleSubmit() { callback(
const { formula } = question; (ba === formula.ba)
callback( &&
ba === formula.ba && (mu === formula.mu)
mu === formula.mu && &&
stemRoot === formula.stemRoot && (stemRoot === formula.stemRoot)
ending === formula.ending &&
); (ending === formula.ending)
} );
return ( }
<div> return <div>
<div className="my-4" style={{ maxWidth: "300px", margin: "0 auto" }}> <div className="my-4" style={{ maxWidth: "300px", margin: "0 auto" }}>
<p className="lead">{humanReadableT(question.tense)}</p> <p className="lead">
</div> {humanReadableT(question.tense)}
<div className="text-center mb-2"> </p>
<BaPicker value={ba} onChange={setBa} /> </div>
{showMu && <MuPicker value={mu} onChange={setMu} />} <div className="text-center mb-2">
<RootsAndStemsPicker value={stemRoot} onChange={setStemRoot} /> <BaPicker value={ba} onChange={setBa} />
<EndingPicker value={ending} onChange={setEnding} /> {showMu && <MuPicker value={mu} onChange={setMu} />}
<button <RootsAndStemsPicker value={stemRoot} onChange={setStemRoot} />
className="btn btn-primary my-2" <EndingPicker value={ending} onChange={setEnding} />
disabled={!canSubmit} <button
onClick={canSubmit ? handleSubmit : undefined} className="btn btn-primary my-2"
> disabled={!canSubmit}
Submit onClick={canSubmit ? handleSubmit : undefined}
</button> >
</div> Submit
<samp>{printFormula({ ba, stemRoot, ending, mu })}</samp> </button>
</div>
<samp>{printFormula({ ba, stemRoot, ending, mu })}</samp>
</div> </div>
);
} }
function humanReadableT( function humanReadableT(tense: T.VerbTense | T.ImperativeTense | "negative imperative"): string {
tense: T.VerbTense | T.ImperativeTense | "negative imperative" if (tense === "negative imperative") {
): string { return tense;
if (tense === "negative imperative") { }
return tense; return humanReadableVerbForm(tense);
}
return humanReadableVerbForm(tense);
} }
function printFormula({ function printFormula({ mu, ba, stemRoot, ending }: {
mu, mu: boolean,
ba, ba: boolean,
stemRoot, stemRoot: StemRoot | "",
ending, ending: Ending | "",
}: {
mu: boolean;
ba: boolean;
stemRoot: StemRoot | "";
ending: Ending | "";
}): string { }): string {
const elements = [ const elements = [
mu ? "mu" : undefined, mu ? "mu" : undefined,
ba ? "ba" : undefined, ba ? "ba" : undefined,
stemRoot, stemRoot,
ending ? `${ending} ending` : undefined, ending ? `${ending} ending` : undefined,
].filter((x) => x) as string[]; ].filter(x => x) as string[];
return elements.join(" + "); return elements.join(" + ");
} }
function DisplayCorrectAnswer({ function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
question, return <div>
}: { <samp>{printFormula(question.formula)}</samp>
question: Question; </div>;
}): JSX.Element { }
return (
<div> function EndingPicker({ onChange, value }: { value: Ending | "", onChange: (e: Ending | "") => void }) {
<samp>{printFormula(question.formula)}</samp> const options: { label: string, value: Ending }[] = [
{ label: "Present", value: "present" },
{ label: "Past", value: "past" },
{ label: "Imperative", value: "imperative" },
];
function handleClick(e: Ending) {
// onChange(ending === e ? "" : e);
onChange(e);
}
return <div className="my-3">
<span className="mr-2">Ending:</span>
<div className="btn-group">
{options.map((option) => (
<button
key={option.value}
type="button"
className={classNames(
"btn",
"btn-outline-secondary",
{ active: value === option.value },
)}
onClick={() => handleClick(option.value)}
>
{option.label}
</button>
))}
</div>
</div>;
}
function BaPicker({ onChange, value }: { value: boolean, onChange: (b: boolean) => void }) {
return <div className="form-check my-3" style={{ fontSize: "larger" }}>
<input
id="baCheckbox"
className="form-check-input"
type="checkbox"
checked={value}
onChange={e => onChange(e.target.checked)}
/>
<label className="form-check-label text-muted" htmlFor="baCheckbox">
with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids' section
</label>
</div> </div>
);
} }
function EndingPicker({ function MuPicker({ onChange, value }: { value: boolean, onChange: (b: boolean) => void }) {
onChange, return <div className="form-check my-3" style={{ fontSize: "larger" }}>
value, <input
}: { id="baCheckbox"
value: Ending | ""; className="form-check-input"
onChange: (e: Ending | "") => void; type="checkbox"
}) { checked={value}
const options: { label: string; value: Ending }[] = [ onChange={e => onChange(e.target.checked)}
{ label: "Present", value: "present" }, />
{ label: "Past", value: "past" }, <label className="form-check-label text-muted" htmlFor="baCheckbox">
{ label: "Imperative", value: "imperative" }, with <InlinePs opts={opts}>{{ p: "مه", f: "mú" }}</InlinePs>
]; </label>
function handleClick(e: Ending) {
// onChange(ending === e ? "" : e);
onChange(e);
}
return (
<div className="my-3">
<span className="mr-2">Ending:</span>
<div className="btn-group">
{options.map((option) => (
<button
key={option.value}
type="button"
className={classNames("btn", "btn-outline-secondary", {
active: value === option.value,
})}
onClick={() => handleClick(option.value)}
>
{option.label}
</button>
))}
</div>
</div> </div>
);
} }
function BaPicker({ function RootsAndStemsPicker({ onChange, value }: { value: StemRoot | "", onChange: (s: StemRoot | "" ) => void }) {
onChange, const colClass = "col col-md-5 px-0 mb-1";
value, const rowClass = "row justify-content-between";
}: { const title: CSSProperties = {
value: boolean; fontWeight: "bolder",
onChange: (b: boolean) => void; paddingBottom: "1.75rem",
}) { paddingTop: "1.75rem",
return ( };
<div className="form-check my-3" style={{ fontSize: "larger" }}> const highlight = {
<input background: "rgba(255, 227, 10, 0.6)",
id="baCheckbox" };
className="form-check-input" function handleStemRootClick(s: StemRoot) {
type="checkbox" onChange(value === s ? "" : s);
checked={value} }
onChange={(e) => onChange(e.target.checked)} return <div className="verb-info" style={{
/>
<label className="form-check-label text-muted" htmlFor="baCheckbox">
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the kids'
section
</label>
</div>
);
}
function MuPicker({
onChange,
value,
}: {
value: boolean;
onChange: (b: boolean) => void;
}) {
return (
<div className="form-check my-3" style={{ fontSize: "larger" }}>
<input
id="baCheckbox"
className="form-check-input"
type="checkbox"
checked={value}
onChange={(e) => onChange(e.target.checked)}
/>
<label className="form-check-label text-muted" htmlFor="baCheckbox">
with <InlinePs opts={opts} ps={{ p: "مه", f: "mú" }} />
</label>
</div>
);
}
function RootsAndStemsPicker({
onChange,
value,
}: {
value: StemRoot | "";
onChange: (s: StemRoot | "") => void;
}) {
const colClass = "col col-md-5 px-0 mb-1";
const rowClass = "row justify-content-between";
const title: CSSProperties = {
fontWeight: "bolder",
paddingBottom: "1.75rem",
paddingTop: "1.75rem",
};
const highlight = {
background: "rgba(255, 227, 10, 0.6)",
};
function handleStemRootClick(s: StemRoot) {
onChange(value === s ? "" : s);
}
return (
<div
className="verb-info"
style={{
textAlign: "center", textAlign: "center",
maxWidth: "400px", maxWidth: "400px",
margin: "0 auto", margin: "0 auto",
// backgroundImage: `url(${fadedTree})`, // backgroundImage: `url(${fadedTree})`,
backgroundRepeat: "no-repeat", backgroundRepeat: "no-repeat",
backgroundPosition: "50% 45%", backgroundPosition: "50% 45%",
backgroundSize: "50%", backgroundSize: "50%",
}} }}>
> <div style={{
<div border: "2px solid black",
style={{ padding: "1rem",
border: "2px solid black", margin: "0.25rem",
padding: "1rem", }} className="container">
margin: "0.25rem", <div className={rowClass + " align-items-center"}>
}} <div className={colClass}>
className="container" <i className="fas fa-video fa-lg" />
> </div>
<div className={rowClass + " align-items-center"}> <div className={colClass}>
<div className={colClass}> <div className="d-flex flex-row justify-content-center align-items-center">
<i className="fas fa-video fa-lg" /> <div>
</div> <i className="fas fa-camera fa-lg mx-3" />
<div className={colClass}> </div>
<div className="d-flex flex-row justify-content-center align-items-center"> </div>
<div> </div>
<i className="fas fa-camera fa-lg mx-3" /> </div>
</div> <div className={rowClass}>
<div className={colClass} style={value === "imperfective stem" ? highlight : {}}>
<div className="clickable" style={title} onClick={() => handleStemRootClick("imperfective stem")}>
<div>Imperfective Stem</div>
</div>
</div>
<div className={colClass} style={value === "perfective stem" ? highlight : {}}>
<div className="clickable" style={title} onClick={() => handleStemRootClick("perfective stem")}>
<div>Perfective Stem</div>
</div>
</div>
</div>
<div className={rowClass}>
<div className={colClass} style={value === "imperfective root" ? highlight : {}}>
<div className="clickable" style={title} onClick={() => handleStemRootClick("imperfective root")}>
<div>Imperfective Root</div>
</div>
</div>
<div className={colClass} style={value === "perfective root" ? highlight : {}}>
<div>
<div className="clickable" style={title} onClick={() => handleStemRootClick("perfective root")}>
<div>Perfective Root</div>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
<div className={rowClass}> </div>;
<div
className={colClass}
style={value === "imperfective stem" ? highlight : {}}
>
<div
className="clickable"
style={title}
onClick={() => handleStemRootClick("imperfective stem")}
>
<div>Imperfective Stem</div>
</div>
</div>
<div
className={colClass}
style={value === "perfective stem" ? highlight : {}}
>
<div
className="clickable"
style={title}
onClick={() => handleStemRootClick("perfective stem")}
>
<div>Perfective Stem</div>
</div>
</div>
</div>
<div className={rowClass}>
<div
className={colClass}
style={value === "imperfective root" ? highlight : {}}
>
<div
className="clickable"
style={title}
onClick={() => handleStemRootClick("imperfective root")}
>
<div>Imperfective Root</div>
</div>
</div>
<div
className={colClass}
style={value === "perfective root" ? highlight : {}}
>
<div>
<div
className="clickable"
style={title}
onClick={() => handleStemRootClick("perfective root")}
>
<div>Perfective Root</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
} }

View File

@ -276,8 +276,8 @@ const VerbGame: GameSubCore<VerbGameLevel> = ({
onChange={(e) => setWithBa(e.target.checked)} onChange={(e) => setWithBa(e.target.checked)}
/> />
<label className="form-check-label text-muted" htmlFor="baCheckbox"> <label className="form-check-label text-muted" htmlFor="baCheckbox">
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the{" "} with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in
<span style={{ color: kidsColor }}>kids' section</span> the <span style={{ color: kidsColor }}>kids' section</span>
</label> </label>
</div> </div>
<div className="my-1" style={{ maxWidth: "200px", margin: "0 auto" }}> <div className="my-1" style={{ maxWidth: "200px", margin: "0 auto" }}>
@ -400,7 +400,7 @@ function DisplayCorrectAnswer({
</div> </div>
<div> <div>
<strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a{" "} <strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a{" "}
<InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the kids' <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids'
section. section.
</div> </div>
</div> </div>

View File

@ -2,9 +2,5 @@
* Removes ă and replaces with a * Removes ă and replaces with a
*/ */
export function removeAShort(s: string): string { export function removeAShort(s: string): string {
return s.replace(/ă/g, "a"); return s.replace(/ă/g, "a");
} }
export function removeAyn(s: string): string {
return s.replace(/'/g, "");
}

View File

@ -1,29 +1,27 @@
import rawWords from "./raw-words"; import rawWords from "./raw-words";
import { import {
removeAccents, removeAccents,
removeFVarients, removeFVarients,
typePredicates as tp, typePredicates as tp,
Types as T, Types as T,
} from "@lingdocs/ps-react"; } from "@lingdocs/ps-react";
import { categorize } from "../lib/categorize"; import { categorize } from "../lib/categorize";
import { removeAShort, removeAyn } from "../lib/misc-helpers"; import { removeAShort } from "../lib/misc-helpers";
// TODO: BIG ISSUE WITH THE LOC ADVERBS BEING LUMPED INTO THE ADVERBS! // TODO: BIG ISSUE WITH THE LOC ADVERBS BEING LUMPED INTO THE ADVERBS!
const words = categorize< const words = categorize<T.Entry, {
T.Entry, nouns: T.NounEntry[],
{ adjectives: T.AdjectiveEntry[],
nouns: T.NounEntry[]; verbs: T.VerbEntry[],
adjectives: T.AdjectiveEntry[]; adverbs: T.AdverbEntry[],
verbs: T.VerbEntry[]; locativeAdverbs: T.LocativeAdverbEntry[],
adverbs: T.AdverbEntry[]; }>(rawWords, {
locativeAdverbs: T.LocativeAdverbEntry[]; nouns: tp.isNounEntry,
} adjectives: tp.isAdjectiveEntry,
>(rawWords, { verbs: tp.isVerbEntry,
nouns: tp.isNounEntry, adverbs: tp.isAdverbEntry,
adjectives: tp.isAdjectiveEntry, locativeAdverbs: tp.isLocativeAdverbEntry,
verbs: tp.isVerbEntry,
adverbs: tp.isAdverbEntry,
locativeAdverbs: tp.isLocativeAdverbEntry,
}); });
export default words; export default words;
@ -31,55 +29,40 @@ export default words;
export const { nouns, adjectives, verbs, adverbs, locativeAdverbs } = words; export const { nouns, adjectives, verbs, adverbs, locativeAdverbs } = words;
export function wordQuery(category: "nouns", w: string[]): T.NounEntry[]; export function wordQuery(category: "nouns", w: string[]): T.NounEntry[];
export function wordQuery( export function wordQuery(category: "adjectives", w: string[]): T.AdjectiveEntry[];
category: "adjectives",
w: string[]
): T.AdjectiveEntry[];
export function wordQuery(category: "adverbs", w: string[]): T.AdverbEntry[]; export function wordQuery(category: "adverbs", w: string[]): T.AdverbEntry[];
export function wordQuery( export function wordQuery(category: "locativeAdverbs", w: string[]): T.LocativeAdverbEntry[];
category: "locativeAdverbs",
w: string[]
): T.LocativeAdverbEntry[];
export function wordQuery(category: "verbs", w: string[]): T.VerbEntry[]; export function wordQuery(category: "verbs", w: string[]): T.VerbEntry[];
export function wordQuery( export function wordQuery(
category: "nouns" | "adjectives" | "adverbs" | "locativeAdverbs" | "verbs", category: "nouns" | "adjectives" | "adverbs" | "locativeAdverbs" | "verbs",
w: string[] w: string[],
): ): T.NounEntry[] | T.AdjectiveEntry[] | T.AdverbEntry[] | T.LocativeAdverbEntry[] | T.VerbEntry[] {
| T.NounEntry[] function queryRemoveAccents(s: string): string {
| T.AdjectiveEntry[] return removeAShort(removeAccents(s));
| T.AdverbEntry[] }
| T.LocativeAdverbEntry[] if (category === "verbs") {
| T.VerbEntry[] { return w.map(word => {
function queryRemoveAccents(s: string): string { const l = words[category];
return removeAyn(removeAShort(removeAccents(s))); const found = l.find(x => vMatches(x, word));
} if (!found) throw new Error(`${word} not found by wordQuery`);
if (category === "verbs") { return found;
return w.map((word) => { });
const l = words[category]; }
const found = l.find((x) => vMatches(x, word)); function vMatches(x: T.VerbEntry, y: string) {
if (!found) throw new Error(`${word} not found by wordQuery`); return (y === x.entry.p)
return found; || (queryRemoveAccents(y) === queryRemoveAccents(removeFVarients(x.entry.f)));
}
function wMatches(x: T.DictionaryEntry, y: string) {
return (y === x.p)
|| (queryRemoveAccents(y) === queryRemoveAccents(removeFVarients(x.f)));
}
return w.map(word => {
const l = words[category];
// @ts-ignore
const found = l.find(x => wMatches(x, word));
if (!found) throw new Error(`${word} not found by wordQuery`);
return found;
}); });
}
function vMatches(x: T.VerbEntry, y: string) {
return (
y === x.entry.p ||
queryRemoveAccents(y) === queryRemoveAccents(removeFVarients(x.entry.f))
);
}
function wMatches(x: T.DictionaryEntry, y: string) {
return (
y === x.p ||
queryRemoveAccents(y) === queryRemoveAccents(removeFVarients(x.f))
);
}
return w.map((word) => {
const l = words[category];
// @ts-ignore
const found = l.find((x) => wMatches(x, word));
if (!found) throw new Error(`${word} not found by wordQuery`);
return found;
});
} }
// console.log( // console.log(

View File

@ -1597,10 +1597,10 @@
rambda "^6.7.0" rambda "^6.7.0"
react-select "^5.2.2" react-select "^5.2.2"
"@lingdocs/ps-react@^7.7.6": "@lingdocs/ps-react@^7.7.3":
version "7.7.6" version "7.7.3"
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.7.6.tgz#07b6d30a326ca567ad469bbc338275d456fdc1a1" resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.7.3.tgz#353b6ddabdfabb4eae45a1d8554adb360f7238fc"
integrity sha512-pE8Fn8/9fSaJHSvcJiFxQOJ8M3OoI/PireM1+NSko6HvDN6Y7QIoVp785rnWJm3ZTHfq/7NzQrqfGIaMoGD/9w== integrity sha512-TSK5kgyCEHTmJRH+9XfchVPUVvsjOfsLlrZlW4PL95LhvBD1dh5CazZDQlPV99CLZGz3jVf9VKpiQP7RdGYTrg==
dependencies: dependencies:
"@formkit/auto-animate" "^1.0.0-beta.3" "@formkit/auto-animate" "^1.0.0-beta.3"
classnames "^2.5.1" classnames "^2.5.1"