Compare commits
3 Commits
a254592fd1
...
c708666baf
Author | SHA1 | Date |
---|---|---|
adueck | c708666baf | |
adueck | 6e8b1113f5 | |
adueck | 35f29e8048 |
|
@ -17,7 +17,7 @@
|
|||
"@formkit/auto-animate": "^1.0.0-beta.6",
|
||||
"@fortawesome/fontawesome-free": "5.15.4",
|
||||
"@lingdocs/lingdocs-main": "^0.3.3",
|
||||
"@lingdocs/ps-react": "^7.7.3",
|
||||
"@lingdocs/ps-react": "^7.7.6",
|
||||
"@mdx-js/rollup": "^2.2.1",
|
||||
"@stefanprobst/rehype-extract-toc": "^2.2.0",
|
||||
"@types/mdx": "^2.0.3",
|
||||
|
|
|
@ -49,12 +49,13 @@ function BasicVerbShowCase({
|
|||
render={(item) => {
|
||||
return {
|
||||
title: (
|
||||
<InlinePs opts={opts}>
|
||||
{{
|
||||
<InlinePs
|
||||
opts={opts}
|
||||
ps={{
|
||||
...removeFVarients(item.entry),
|
||||
e: undefined,
|
||||
}}
|
||||
</InlinePs>
|
||||
/>
|
||||
),
|
||||
body: (
|
||||
<BasicVerbChart
|
||||
|
|
|
@ -20,10 +20,10 @@ export default function GenderTable({ rows }) {
|
|||
return <td key={`row-${i}-${gender}`} style={{ background: genderColors[gender === "masc" ? "m" : "f"] }}>
|
||||
{item.ending && <div>
|
||||
{typeof item.ending === "string" ? item.ending
|
||||
: <InlinePs opts={opts}>{item.ending}</InlinePs>}
|
||||
: <InlinePs opts={opts} ps={item.ending} />}
|
||||
</div>}
|
||||
<div>
|
||||
{item.ending ? "eg. " : ""}<InlinePs opts={opts}>{item.ex}</InlinePs>
|
||||
{item.ending ? "eg. " : ""}<InlinePs opts={opts} ps={item.ex} />
|
||||
</div>
|
||||
</td>
|
||||
})}
|
||||
|
|
|
@ -1,51 +1,67 @@
|
|||
import Carousel from "./Carousel";
|
||||
import {
|
||||
InlinePs,
|
||||
removeFVarients,
|
||||
InflectionsTable,
|
||||
inflectWord,
|
||||
defaultTextOptions as opts,
|
||||
getEnglishWord,
|
||||
Types as T,
|
||||
InlinePs,
|
||||
removeFVarients,
|
||||
InflectionsTable,
|
||||
inflectWord,
|
||||
defaultTextOptions as opts,
|
||||
getEnglishWord,
|
||||
Types as T,
|
||||
} from "@lingdocs/ps-react";
|
||||
|
||||
function InflectionCarousel({ items }: { items: (T.NounEntry | T.AdjectiveEntry)[] }) {
|
||||
if (!items.length) {
|
||||
return "no items for carousel";
|
||||
}
|
||||
return (
|
||||
<div className="mt-3">
|
||||
<Carousel items={items} render={item => {
|
||||
const e = getEnglishWord(item);
|
||||
const english = e === undefined
|
||||
? item.e
|
||||
: typeof e === "string"
|
||||
? e
|
||||
: e.singular !== undefined
|
||||
? e.singular
|
||||
: item.e;
|
||||
const infOut = inflectWord(item);
|
||||
if (!infOut || !infOut.inflections) {
|
||||
return {
|
||||
title: "Oops! 🤷♂️",
|
||||
// @ts-ignore
|
||||
body: <div>Oops! No inflections for <InlinePs opts={opts}>{item}</InlinePs></div>,
|
||||
};
|
||||
}
|
||||
return {
|
||||
// @ts-ignore
|
||||
title: <InlinePs opts={opts} ps={{
|
||||
...removeFVarients(item),
|
||||
e: english,
|
||||
}} />,
|
||||
body: <InflectionsTable
|
||||
inf={infOut.inflections}
|
||||
textOptions={opts}
|
||||
/>,
|
||||
};
|
||||
}}/>
|
||||
</div>
|
||||
);
|
||||
function InflectionCarousel({
|
||||
items,
|
||||
}: {
|
||||
items: (T.NounEntry | T.AdjectiveEntry)[];
|
||||
}) {
|
||||
if (!items.length) {
|
||||
return "no items for carousel";
|
||||
}
|
||||
return (
|
||||
<div className="mt-3">
|
||||
<Carousel
|
||||
items={items}
|
||||
render={(item) => {
|
||||
const e = getEnglishWord(item);
|
||||
const english =
|
||||
e === undefined
|
||||
? item.e
|
||||
: typeof e === "string"
|
||||
? e
|
||||
: e.singular !== undefined
|
||||
? e.singular
|
||||
: item.e;
|
||||
const infOut = inflectWord(item);
|
||||
if (!infOut || !infOut.inflections) {
|
||||
return {
|
||||
title: "Oops! 🤷♂️",
|
||||
// @ts-ignore
|
||||
body: (
|
||||
<div>
|
||||
Oops! No inflections for <InlinePs opts={opts} ps={item} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}
|
||||
return {
|
||||
// @ts-ignore
|
||||
title: (
|
||||
<InlinePs
|
||||
opts={opts}
|
||||
ps={{
|
||||
...removeFVarients(item),
|
||||
e: english,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
body: (
|
||||
<InflectionsTable inf={infOut.inflections} textOptions={opts} />
|
||||
),
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default InflectionCarousel;
|
||||
export default InflectionCarousel;
|
||||
|
|
|
@ -1,33 +1,47 @@
|
|||
import {
|
||||
renderNPSelection,
|
||||
Types as T,
|
||||
renderAPSelection,
|
||||
NPBlock,
|
||||
APBlock,
|
||||
getEnglishFromRendered,
|
||||
renderNPSelection,
|
||||
Types as T,
|
||||
renderAPSelection,
|
||||
NPBlock,
|
||||
APBlock,
|
||||
getEnglishFromRendered,
|
||||
} from "@lingdocs/ps-react";
|
||||
|
||||
function BlockDiagram({ opts, children }: {
|
||||
opts: T.TextOptions,
|
||||
children: T.NPSelection | T.APSelection,
|
||||
function BlockDiagram({
|
||||
opts,
|
||||
children,
|
||||
}: {
|
||||
opts: T.TextOptions;
|
||||
children: T.NPSelection | T.APSelection;
|
||||
}) {
|
||||
try {
|
||||
const rendered = children.type === "AP"
|
||||
try {
|
||||
const rendered =
|
||||
children.type === "AP"
|
||||
? renderAPSelection(children, 0)
|
||||
: renderNPSelection(children, false, false, "subject", "none", false);
|
||||
const english = getEnglishFromRendered(rendered);
|
||||
return <div className="mb-3">
|
||||
<div className="d-flex flex-row justify-content-center text-center" style={{ maxWidth: "100%" }}>
|
||||
{rendered.type === "NP"
|
||||
? <NPBlock script="f" opts={opts} english={english}>{rendered}</NPBlock>
|
||||
: <APBlock script="f" opts={opts} english={english}>{rendered}</APBlock>}
|
||||
return (
|
||||
<div className="mb-3">
|
||||
<div
|
||||
className="d-flex flex-row justify-content-center text-center"
|
||||
style={{ maxWidth: "100%" }}
|
||||
>
|
||||
{rendered.type === "NP" ? (
|
||||
<NPBlock script="f" opts={opts} english={english}>
|
||||
{rendered}
|
||||
</NPBlock>
|
||||
) : (
|
||||
<APBlock script="f" opts={opts} english={english}>
|
||||
{rendered}
|
||||
</APBlock>
|
||||
)}
|
||||
</div>
|
||||
</div>;
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
return <div>ERROR</div>;
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return <div>ERROR</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default BlockDiagram;
|
||||
export default BlockDiagram;
|
||||
|
|
|
@ -1,88 +1,95 @@
|
|||
import {
|
||||
Types as T,
|
||||
NPPicker,
|
||||
APPicker,
|
||||
} from "@lingdocs/ps-react";
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { Types as T, NPPicker, APPicker } from "@lingdocs/ps-react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useState } from "react";
|
||||
import BlockDiagram from "./BlockDiagram";
|
||||
import entryFeeder from "../../lib/entry-feeder";
|
||||
import autoAnimate from "@formkit/auto-animate";
|
||||
|
||||
export function EditIcon() {
|
||||
return <i className="fas fa-edit" />;
|
||||
return <i className="fas fa-edit" />;
|
||||
}
|
||||
|
||||
function selectionToBlock(s: T.NPSelection | T.APSelection): { type: "NP", block: T.NPSelection | undefined } | { type: "AP", block: T.APSelection | undefined } {
|
||||
return (s.type === "AP")
|
||||
? { type: "AP", block: s }
|
||||
: { type: "NP", block: s };
|
||||
function selectionToBlock(
|
||||
s: T.NPSelection | T.APSelection
|
||||
):
|
||||
| { type: "NP"; block: T.NPSelection | undefined }
|
||||
| { type: "AP"; block: T.APSelection | undefined } {
|
||||
return s.type === "AP" ? { type: "AP", block: s } : { type: "NP", block: s };
|
||||
}
|
||||
|
||||
function EditableBlock({ opts, children: block }: {
|
||||
opts: T.TextOptions,
|
||||
children: T.NPSelection | T.APSelection,
|
||||
function EditableBlock({
|
||||
opts,
|
||||
children: block,
|
||||
}: {
|
||||
opts: T.TextOptions;
|
||||
children: T.NPSelection | T.APSelection;
|
||||
}) {
|
||||
const parent = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
parent.current && autoAnimate(parent.current)
|
||||
}, [parent]);
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const [edited, setEdited] = useState<{
|
||||
type: "NP",
|
||||
block: T.NPSelection | undefined,
|
||||
} | {
|
||||
type: "AP",
|
||||
block: T.APSelection | undefined,
|
||||
}>(selectionToBlock(block));
|
||||
function handleNPChange(np: T.NPSelection | undefined) {
|
||||
setEdited({ type: "NP", block: np });
|
||||
}
|
||||
function handleAPChange(ap: T.APSelection | undefined) {
|
||||
setEdited({ type: "AP", block: ap });
|
||||
}
|
||||
function handleReset() {
|
||||
setEdited(selectionToBlock(block));
|
||||
setEditing(false);
|
||||
}
|
||||
return <div>
|
||||
<div
|
||||
className="text-right clickable"
|
||||
onClick={editing ? handleReset : () => setEditing(true)}
|
||||
>
|
||||
{!editing ? <EditIcon /> : <i className="fas fa-undo" />}
|
||||
</div>
|
||||
<div ref={parent} className="d-flex flex-column align-items-center">
|
||||
{editing && <div style={{ maxWidth: "225px", marginBottom: "2rem" }}>
|
||||
{edited.type === "NP"
|
||||
? <NPPicker
|
||||
opts={opts}
|
||||
np={edited.block}
|
||||
onChange={handleNPChange}
|
||||
entryFeeder={entryFeeder}
|
||||
role="subject"
|
||||
counterPart={undefined}
|
||||
phraseIsComplete={false}
|
||||
/>
|
||||
: <APPicker
|
||||
opts={opts}
|
||||
AP={edited.block}
|
||||
onChange={handleAPChange}
|
||||
entryFeeder={entryFeeder}
|
||||
phraseIsComplete={false}
|
||||
onRemove={() => null}
|
||||
/>
|
||||
}
|
||||
</div>}
|
||||
{edited.block
|
||||
&& <BlockDiagram opts={opts}>
|
||||
{edited.block}
|
||||
</BlockDiagram>}
|
||||
</div>
|
||||
</div>;
|
||||
const parent = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
parent.current && autoAnimate(parent.current);
|
||||
}, [parent]);
|
||||
const [editing, setEditing] = useState<boolean>(false);
|
||||
const [edited, setEdited] = useState<
|
||||
| {
|
||||
type: "NP";
|
||||
block: T.NPSelection | undefined;
|
||||
}
|
||||
| {
|
||||
type: "AP";
|
||||
block: T.APSelection | undefined;
|
||||
}
|
||||
>(selectionToBlock(block));
|
||||
function handleNPChange(np: T.NPSelection | undefined) {
|
||||
setEdited({ type: "NP", block: np });
|
||||
}
|
||||
function handleAPChange(ap: T.APSelection | undefined) {
|
||||
setEdited({ type: "AP", block: ap });
|
||||
}
|
||||
function handleReset() {
|
||||
setEdited(selectionToBlock(block));
|
||||
setEditing(false);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="text-right clickable"
|
||||
onClick={editing ? handleReset : () => setEditing(true)}
|
||||
>
|
||||
{!editing ? <EditIcon /> : <i className="fas fa-undo" />}
|
||||
</div>
|
||||
<div ref={parent} className="d-flex flex-column align-items-center">
|
||||
{editing && (
|
||||
<div style={{ maxWidth: "225px", marginBottom: "2rem" }}>
|
||||
{edited.type === "NP" ? (
|
||||
<NPPicker
|
||||
opts={opts}
|
||||
np={edited.block}
|
||||
onChange={handleNPChange}
|
||||
entryFeeder={entryFeeder}
|
||||
role="subject"
|
||||
counterPart={undefined}
|
||||
phraseIsComplete={false}
|
||||
negative={false}
|
||||
/>
|
||||
) : (
|
||||
<APPicker
|
||||
opts={opts}
|
||||
AP={edited.block}
|
||||
onChange={handleAPChange}
|
||||
entryFeeder={entryFeeder}
|
||||
phraseIsComplete={false}
|
||||
onRemove={() => null}
|
||||
negative={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{edited.block && (
|
||||
<BlockDiagram opts={opts}>{edited.block}</BlockDiagram>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditableBlock;
|
||||
export default EditableBlock;
|
||||
|
|
|
@ -1,36 +1,123 @@
|
|||
import Carousel from "../../components/Carousel";
|
||||
import {
|
||||
defaultTextOptions as opts,
|
||||
InlinePs,
|
||||
RootsAndStems,
|
||||
getVerbInfo,
|
||||
getAbilityRootsAndStems,
|
||||
removeFVarients,
|
||||
ensureNonComboVerbInfo,
|
||||
defaultTextOptions as opts,
|
||||
InlinePs,
|
||||
RootsAndStems,
|
||||
getVerbInfo,
|
||||
getAbilityRootsAndStems,
|
||||
removeFVarients,
|
||||
ensureNonComboVerbInfo,
|
||||
} from "@lingdocs/ps-react";
|
||||
|
||||
export default function GoingStems() {
|
||||
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":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"},
|
||||
{"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}>
|
||||
{{
|
||||
...removeFVarients(item.entry),
|
||||
e: undefined,
|
||||
}}
|
||||
</InlinePs>,
|
||||
body: <RootsAndStems
|
||||
textOptions={opts}
|
||||
info={rs}
|
||||
/>,
|
||||
};
|
||||
}}/>
|
||||
}
|
||||
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: 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",
|
||||
},
|
||||
{
|
||||
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} />,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
comparePs,
|
||||
} from "../../lib/game-utils";
|
||||
import { comparePs } from "../../lib/game-utils";
|
||||
import GameCore from "../GameCore";
|
||||
import {
|
||||
Types as T,
|
||||
Examples,
|
||||
defaultTextOptions as opts,
|
||||
renderEP,
|
||||
compileEP,
|
||||
flattenLengths,
|
||||
InlinePs,
|
||||
grammarUnits,
|
||||
Types as T,
|
||||
Examples,
|
||||
defaultTextOptions as opts,
|
||||
renderEP,
|
||||
compileEP,
|
||||
flattenLengths,
|
||||
InlinePs,
|
||||
grammarUnits,
|
||||
} from "@lingdocs/ps-react";
|
||||
import { randomEPSPool } from "./makeRandomEPS";
|
||||
|
||||
|
@ -21,168 +19,215 @@ const amount = 12;
|
|||
const timeLimit = 100;
|
||||
|
||||
type Question = {
|
||||
EPS: T.EPSelectionComplete,
|
||||
phrase: { ps: T.PsString[], e?: string[] },
|
||||
equative: T.EquativeRendered,
|
||||
EPS: T.EPSelectionComplete;
|
||||
phrase: { ps: T.PsString[]; e?: string[] };
|
||||
equative: T.EquativeRendered;
|
||||
};
|
||||
|
||||
export default function EquativeGame({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: T.EquativeTense | "allTenses" }) {
|
||||
const epsPool = randomEPSPool(level);
|
||||
function getQuestion(): Question {
|
||||
const EPS = epsPool();
|
||||
const EP = renderEP(EPS);
|
||||
const compiled = compileEP(
|
||||
EP,
|
||||
true,
|
||||
{ equative: true, kidsSection: true },
|
||||
);
|
||||
const phrase = {
|
||||
ps: compiled.ps,
|
||||
e: compiled.e,
|
||||
};
|
||||
return {
|
||||
EPS,
|
||||
phrase,
|
||||
equative: getEqFromRendered(EP),
|
||||
};
|
||||
export default function EquativeGame({
|
||||
inChapter,
|
||||
id,
|
||||
link,
|
||||
level,
|
||||
}: {
|
||||
inChapter: boolean;
|
||||
id: string;
|
||||
link: string;
|
||||
level: T.EquativeTense | "allTenses";
|
||||
}) {
|
||||
const epsPool = randomEPSPool(level);
|
||||
function getQuestion(): Question {
|
||||
const EPS = epsPool();
|
||||
const EP = renderEP(EPS);
|
||||
const compiled = compileEP(EP, true, { equative: true, kidsSection: true });
|
||||
const phrase = {
|
||||
ps: compiled.ps,
|
||||
e: compiled.e,
|
||||
};
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
const [answer, setAnswer] = useState<string>("");
|
||||
const [withBa, setWithBa] = useState<boolean>(false);
|
||||
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(value);
|
||||
}
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const correct = comparePs(answer, question.equative.ps)
|
||||
&& (withBa === question.equative.hasBa);
|
||||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(correct);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (level === "allTenses") setWithBa(false);
|
||||
}, [question]);
|
||||
|
||||
return <div>
|
||||
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<Examples lineHeight={1} opts={opts}>
|
||||
{/* @ts-ignore TODO: REMOVE AS P_INFLE */}
|
||||
{modExs(question.phrase.ps, withBa)[0]}
|
||||
</Examples>
|
||||
{question.phrase.e && question.phrase.e.map((e, i) => (
|
||||
<div key={e+i} className="text-muted">{e}</div>
|
||||
))}
|
||||
<div>{humanReadableTense(question.EPS.equative.tense)} equative</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-check mt-1">
|
||||
<input
|
||||
id="baCheckbox"
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={withBa}
|
||||
onChange={e => setWithBa(e.target.checked)}
|
||||
/>
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-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-2">
|
||||
{/* <div> */}
|
||||
<button className="btn btn-primary" type="submit">submit ↵</button>
|
||||
{/* </div> */}
|
||||
{/* <div className="text-muted small text-center mt-2">
|
||||
return {
|
||||
EPS,
|
||||
phrase,
|
||||
equative: getEqFromRendered(EP),
|
||||
};
|
||||
}
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
const [answer, setAnswer] = useState<string>("");
|
||||
const [withBa, setWithBa] = useState<boolean>(false);
|
||||
const handleInput = ({
|
||||
target: { value },
|
||||
}: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(value);
|
||||
};
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const correct =
|
||||
comparePs(answer, question.equative.ps) &&
|
||||
withBa === question.equative.hasBa;
|
||||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(correct);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (level === "allTenses") setWithBa(false);
|
||||
}, [question]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<Examples lineHeight={1} opts={opts}>
|
||||
{/* @ts-ignore TODO: REMOVE AS P_INFLE */}
|
||||
{modExs(question.phrase.ps, withBa)[0]}
|
||||
</Examples>
|
||||
{question.phrase.e &&
|
||||
question.phrase.e.map((e, i) => (
|
||||
<div key={e + i} className="text-muted">
|
||||
{e}
|
||||
</div>
|
||||
))}
|
||||
<div>{humanReadableTense(question.EPS.equative.tense)} equative</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-check mt-1">
|
||||
<input
|
||||
id="baCheckbox"
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={withBa}
|
||||
onChange={(e) => setWithBa(e.target.checked)}
|
||||
/>
|
||||
<label className="form-check-label text-muted" htmlFor="baCheckbox">
|
||||
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the{" "}
|
||||
<span style={{ color: kidsColor }}>kids' section</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-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-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
|
||||
</div> */}
|
||||
</div>
|
||||
</form>
|
||||
</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>;
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</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 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}>{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 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(" ___ ");
|
||||
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 {
|
||||
return tense === "allProduce"
|
||||
? ""
|
||||
: tense === "pastSubjunctive"
|
||||
? "past subjunctive"
|
||||
: tense === "wouldBe"
|
||||
? `"would be"`
|
||||
: tense === "wouldHaveBeen"
|
||||
? `"would have been"`
|
||||
: tense;
|
||||
return tense === "allProduce"
|
||||
? ""
|
||||
: tense === "pastSubjunctive"
|
||||
? "past subjunctive"
|
||||
: tense === "wouldBe"
|
||||
? `"would be"`
|
||||
: tense === "wouldHaveBeen"
|
||||
? `"would have been"`
|
||||
: tense;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getEqFromRendered(e: T.EPRendered): T.EquativeRendered {
|
||||
const eblock = e.blocks[0].find(x => x.block.type === "equative");
|
||||
if (!eblock || eblock.block.type !== "equative") throw new Error("Error getting equative block");
|
||||
return eblock.block.equative;
|
||||
}
|
||||
const eblock = e.blocks[0].find((x) => x.block.type === "equative");
|
||||
if (!eblock || eblock.block.type !== "equative")
|
||||
throw new Error("Error getting equative block");
|
||||
return eblock.block.equative;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import GameCore from "../GameCore";
|
||||
import {
|
||||
Types as T,
|
||||
renderNPSelection,
|
||||
getEnglishFromRendered,
|
||||
getPashtoFromRendered,
|
||||
renderAPSelection,
|
||||
InlinePs,
|
||||
defaultTextOptions as opts,
|
||||
concatPsString,
|
||||
Types as T,
|
||||
renderNPSelection,
|
||||
getEnglishFromRendered,
|
||||
getPashtoFromRendered,
|
||||
renderAPSelection,
|
||||
InlinePs,
|
||||
defaultTextOptions as opts,
|
||||
concatPsString,
|
||||
} from "@lingdocs/ps-react";
|
||||
import { makeNPAdjGenerator } from "../../lib/block-generators/np-adj-generator";
|
||||
import { useState } from "react";
|
||||
|
@ -19,141 +19,183 @@ const amount = 16;
|
|||
const timeLimit = 270;
|
||||
|
||||
type Question = {
|
||||
selection: T.NPSelection | T.APSelection,
|
||||
answer: T.PsString[],
|
||||
english: string,
|
||||
selection: T.NPSelection | T.APSelection;
|
||||
answer: T.PsString[];
|
||||
english: string;
|
||||
};
|
||||
|
||||
type Level = "hints" | "no-hints" | "sandwiches";
|
||||
|
||||
const NPAdjWriting: GameSubCore<Level> = ({ inChapter, id, link, level }: {
|
||||
inChapter: boolean,
|
||||
id: string,
|
||||
link: string,
|
||||
level: Level,
|
||||
const NPAdjWriting: GameSubCore<Level> = ({
|
||||
inChapter,
|
||||
id,
|
||||
link,
|
||||
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 {
|
||||
const np = npPool();
|
||||
const selection: T.NPSelection | T.APSelection = level === "sandwiches"
|
||||
? makeSandwich(np) : np;
|
||||
const rendered: T.Rendered<T.NPSelection> | T.Rendered<T.APSelection> = selection.type === "AP"
|
||||
? renderAPSelection(selection, 0) // WOULD BE CLEANER IF THIS WAS JUST A PURE SANDWICH, NOT AT AP
|
||||
: renderNPSelection(np, false, false, "subject", "none", false);
|
||||
const answer = getPashtoFromRendered(rendered, false);
|
||||
return {
|
||||
selection,
|
||||
answer,
|
||||
english: getEnglishFromRendered(rendered) || "ERROR",
|
||||
};
|
||||
function getQuestion(): Question {
|
||||
const np = npPool();
|
||||
const selection: T.NPSelection | T.APSelection =
|
||||
level === "sandwiches" ? makeSandwich(np) : np;
|
||||
const rendered: T.Rendered<T.NPSelection> | T.Rendered<T.APSelection> =
|
||||
selection.type === "AP"
|
||||
? renderAPSelection(selection, 0) // WOULD BE CLEANER IF THIS WAS JUST A PURE SANDWICH, NOT AT AP
|
||||
: renderNPSelection(np, false, false, "subject", "none", false);
|
||||
const answer = getPashtoFromRendered(rendered, false);
|
||||
return {
|
||||
selection,
|
||||
answer,
|
||||
english: getEnglishFromRendered(rendered) || "ERROR",
|
||||
};
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
const [answer, setAnswer] = useState<string>("");
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const correct = comparePs(answer, question.answer);
|
||||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(correct);
|
||||
}
|
||||
if (!(
|
||||
(question.selection.type === "AP" && question.selection.selection.type === "sandwich" && question.selection.selection.inside.selection.type === "noun")
|
||||
||
|
||||
(question.selection.type === "NP" && question.selection.selection.type === "noun")
|
||||
)) {
|
||||
throw new Error("QUESTION ERROR - BAD SELECTION")
|
||||
}
|
||||
const nounSelection: T.NounSelection = question.selection.type === "AP"
|
||||
? question.selection.selection.inside.selection as T.NounSelection // ts being dumb
|
||||
: question.selection.selection;
|
||||
const adjEntry: T.AdjectiveEntry | undefined = nounSelection.adjectives[0]?.entry;
|
||||
if (!adjEntry) {
|
||||
throw new Error("QUESTION ERROR - MISSING ADJECTIVE");
|
||||
}
|
||||
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(value);
|
||||
}
|
||||
const sandwich = question.selection.type === "AP"
|
||||
? question.selection.selection
|
||||
: undefined;
|
||||
return <div>
|
||||
<div className="my-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<div className="d-flex flex-row justify-content-center" style={{ gap: "1rem"}}>
|
||||
<WordCard
|
||||
showHint={level === "hints"}
|
||||
entry={adjEntry}
|
||||
selection={undefined}
|
||||
/>
|
||||
<WordCard
|
||||
showHint={level === "hints"}
|
||||
entry={nounSelection.entry}
|
||||
selection={nounSelection}
|
||||
/>
|
||||
</div>
|
||||
{sandwich && <div className="mt-2">
|
||||
<InlinePs opts={opts}>
|
||||
{concatPsString(sandwich.before, " ... ", sandwich.after)}
|
||||
</InlinePs>
|
||||
</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>
|
||||
}
|
||||
|
||||
function Instructions() {
|
||||
return <div>
|
||||
<p className="lead">Write the {level === "sandwiches" ? "sandwich including the" : ""} adjective and noun together with the proper inflections.</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
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>) {
|
||||
const [answer, setAnswer] = useState<string>("");
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const correct = comparePs(answer, question.answer);
|
||||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(correct);
|
||||
};
|
||||
if (
|
||||
!(
|
||||
(question.selection.type === "AP" &&
|
||||
question.selection.selection.type === "sandwich" &&
|
||||
question.selection.selection.inside.selection.type === "noun") ||
|
||||
(question.selection.type === "NP" &&
|
||||
question.selection.selection.type === "noun")
|
||||
)
|
||||
) {
|
||||
throw new Error("QUESTION ERROR - BAD SELECTION");
|
||||
}
|
||||
const nounSelection: T.NounSelection =
|
||||
question.selection.type === "AP"
|
||||
? (question.selection.selection.inside.selection as T.NounSelection) // ts being dumb
|
||||
: question.selection.selection;
|
||||
const adjEntry: T.AdjectiveEntry | undefined =
|
||||
nounSelection.adjectives[0]?.entry;
|
||||
if (!adjEntry) {
|
||||
throw new Error("QUESTION ERROR - MISSING ADJECTIVE");
|
||||
}
|
||||
const handleInput = ({
|
||||
target: { value },
|
||||
}: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(value);
|
||||
};
|
||||
const sandwich =
|
||||
question.selection.type === "AP"
|
||||
? question.selection.selection
|
||||
: undefined;
|
||||
return (
|
||||
<div>
|
||||
<div className="my-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<div
|
||||
className="d-flex flex-row justify-content-center"
|
||||
style={{ gap: "1rem" }}
|
||||
>
|
||||
<WordCard
|
||||
showHint={level === "hints"}
|
||||
entry={adjEntry}
|
||||
selection={undefined}
|
||||
/>
|
||||
<WordCard
|
||||
showHint={level === "hints"}
|
||||
entry={nounSelection.entry}
|
||||
selection={nounSelection}
|
||||
/>
|
||||
</div>
|
||||
{sandwich && (
|
||||
<div className="mt-2">
|
||||
<InlinePs
|
||||
opts={opts}
|
||||
ps={concatPsString(sandwich.before, " ... ", sandwich.after)}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
function Instructions() {
|
||||
return (
|
||||
<div>
|
||||
<p className="lead">
|
||||
Write the {level === "sandwiches" ? "sandwich including the" : ""}{" "}
|
||||
adjective and noun together with the proper inflections.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
// TODO: extract this reduces for the or, or different variations, used in VerbGame (etc.?)
|
||||
return <div>
|
||||
<div>
|
||||
{question.answer.reduce(((accum, curr, i): JSX.Element[] => (
|
||||
[
|
||||
...accum,
|
||||
...i > 0 ? [<span className="text-muted"> or </span>] : [],
|
||||
<span key={i}>{curr.p} - {curr.f}</span>,
|
||||
]
|
||||
)), [] as JSX.Element[])}
|
||||
</div>
|
||||
</div>;
|
||||
function DisplayCorrectAnswer({
|
||||
question,
|
||||
}: {
|
||||
question: Question;
|
||||
}): JSX.Element {
|
||||
// TODO: extract this reduces for the or, or different variations, used in VerbGame (etc.?)
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{question.answer.reduce(
|
||||
(accum, curr, i): JSX.Element[] => [
|
||||
...accum,
|
||||
...(i > 0 ? [<span className="text-muted"> or </span>] : []),
|
||||
<span key={i}>
|
||||
{curr.p} - {curr.f}
|
||||
</span>,
|
||||
],
|
||||
[] as JSX.Element[]
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NPAdjWriting;
|
||||
|
|
|
@ -270,8 +270,8 @@ const VerbGame: GameSubCore<PerfectGameLevel> = ({
|
|||
className="form-check-label text-muted"
|
||||
htmlFor="baCheckbox"
|
||||
>
|
||||
with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs>{" "}
|
||||
in the <span style={{ color: kidsColor }}>kids' section</span>
|
||||
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in
|
||||
the <span style={{ color: kidsColor }}>kids' section</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
@ -352,7 +352,7 @@ function DisplayCorrectAnswer({
|
|||
</div>
|
||||
<div>
|
||||
<strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a{" "}
|
||||
<InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids'
|
||||
<InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the kids'
|
||||
section.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -45,7 +45,7 @@ export const nouns = wordQuery("nouns", [
|
|||
"gaawanDay",
|
||||
"sakhtee",
|
||||
"dostee",
|
||||
"aRtiyaa",
|
||||
"aRtyaa",
|
||||
"DaakTar",
|
||||
"laas",
|
||||
"waadu",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import GameCore from "../GameCore";
|
||||
import {
|
||||
humanReadableVerbForm,
|
||||
Types as T,
|
||||
InlinePs,
|
||||
grammarUnits,
|
||||
defaultTextOptions as opts,
|
||||
humanReadableVerbForm,
|
||||
Types as T,
|
||||
InlinePs,
|
||||
grammarUnits,
|
||||
defaultTextOptions as opts,
|
||||
} from "@lingdocs/ps-react";
|
||||
import { makePool } from "../../lib/pool";
|
||||
import { CSSProperties, useEffect, useState } from "react";
|
||||
|
@ -14,347 +14,439 @@ import { isImperativeTense } from "@lingdocs/ps-react/dist/lib/src/type-predicat
|
|||
const amount = 12;
|
||||
const timeLimit = 125;
|
||||
|
||||
type StemRoot = "imperfective stem" | "perfective stem" | "imperfective root" | "perfective root";
|
||||
type Ending = "present" | "past" | "imperative";
|
||||
type StemRoot =
|
||||
| "imperfective stem"
|
||||
| "perfective stem"
|
||||
| "imperfective root"
|
||||
| "perfective root";
|
||||
type Ending = "present" | "past" | "imperative";
|
||||
type Formula = {
|
||||
ba: boolean,
|
||||
mu: boolean,
|
||||
stemRoot: StemRoot,
|
||||
ending: Ending,
|
||||
ba: boolean;
|
||||
mu: boolean;
|
||||
stemRoot: StemRoot;
|
||||
ending: Ending;
|
||||
};
|
||||
|
||||
type Question = {
|
||||
tense: T.VerbTense | T.ImperativeTense | "negative imperative",
|
||||
formula: Formula,
|
||||
}
|
||||
|
||||
const questions: Question[] = [
|
||||
{
|
||||
tense: "presentVerb",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "subjunctiveVerb",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "perfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "imperfectiveFuture",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "perfectiveFuture",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "perfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "imperfectivePast",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "imperfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "perfectivePast",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "perfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "habitualImperfectivePast",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "imperfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "habitualPerfectivePast",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "perfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "perfectiveImperative",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "perfective stem",
|
||||
ending: "imperative",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "imperfectiveImperative",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "imperative",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "negative imperative",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: true,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "imperative",
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
export default function VerbFormulas({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "all" }) {
|
||||
const questionsPool = makePool(questions);
|
||||
function getQuestion() {
|
||||
return questionsPool();
|
||||
}
|
||||
function Instructions() {
|
||||
return <p className="lead">Pick the formula for each verb tense</p>;
|
||||
}
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
tense: T.VerbTense | T.ImperativeTense | "negative imperative";
|
||||
formula: Formula;
|
||||
};
|
||||
|
||||
const questions: Question[] = [
|
||||
{
|
||||
tense: "presentVerb",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "subjunctiveVerb",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "perfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "imperfectiveFuture",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "perfectiveFuture",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "perfective stem",
|
||||
ending: "present",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "imperfectivePast",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "imperfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "perfectivePast",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "perfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "habitualImperfectivePast",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "imperfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "habitualPerfectivePast",
|
||||
formula: {
|
||||
ba: true,
|
||||
mu: false,
|
||||
stemRoot: "perfective root",
|
||||
ending: "past",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "perfectiveImperative",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "perfective stem",
|
||||
ending: "imperative",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "imperfectiveImperative",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: false,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "imperative",
|
||||
},
|
||||
},
|
||||
{
|
||||
tense: "negative imperative",
|
||||
formula: {
|
||||
ba: false,
|
||||
mu: true,
|
||||
stemRoot: "imperfective stem",
|
||||
ending: "imperative",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default function VerbFormulas({
|
||||
inChapter,
|
||||
id,
|
||||
link,
|
||||
level,
|
||||
}: {
|
||||
inChapter: boolean;
|
||||
id: string;
|
||||
link: string;
|
||||
level: "all";
|
||||
}) {
|
||||
const questionsPool = makePool(questions);
|
||||
function getQuestion() {
|
||||
return questionsPool();
|
||||
}
|
||||
function Instructions() {
|
||||
return <p className="lead">Pick the formula for each verb tense</p>;
|
||||
}
|
||||
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>) {
|
||||
const [ba, setBa] = useState<boolean>(false);
|
||||
const [mu, setMu] = useState<boolean>(false);
|
||||
const [stemRoot, setStemRoot] = useState<StemRoot | "">("");
|
||||
const [ending, setEnding] = useState<Ending | "">("");
|
||||
useEffect(() => {
|
||||
setBa(false);
|
||||
setStemRoot("");
|
||||
setEnding("");
|
||||
setMu(false);
|
||||
}, [question]);
|
||||
const canSubmit = !!(stemRoot && ending);
|
||||
const showMu = question.tense === "negative imperative" || isImperativeTense(question.tense);
|
||||
function handleSubmit() {
|
||||
const { formula } = question;
|
||||
callback(
|
||||
(ba === formula.ba)
|
||||
&&
|
||||
(mu === formula.mu)
|
||||
&&
|
||||
(stemRoot === formula.stemRoot)
|
||||
&&
|
||||
(ending === formula.ending)
|
||||
);
|
||||
}
|
||||
return <div>
|
||||
<div className="my-4" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<p className="lead">
|
||||
{humanReadableT(question.tense)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center mb-2">
|
||||
<BaPicker value={ba} onChange={setBa} />
|
||||
{showMu && <MuPicker value={mu} onChange={setMu} />}
|
||||
<RootsAndStemsPicker value={stemRoot} onChange={setStemRoot} />
|
||||
<EndingPicker value={ending} onChange={setEnding} />
|
||||
<button
|
||||
className="btn btn-primary my-2"
|
||||
disabled={!canSubmit}
|
||||
onClick={canSubmit ? handleSubmit : undefined}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
<samp>{printFormula({ ba, stemRoot, ending, mu })}</samp>
|
||||
const [ba, setBa] = useState<boolean>(false);
|
||||
const [mu, setMu] = useState<boolean>(false);
|
||||
const [stemRoot, setStemRoot] = useState<StemRoot | "">("");
|
||||
const [ending, setEnding] = useState<Ending | "">("");
|
||||
useEffect(() => {
|
||||
setBa(false);
|
||||
setStemRoot("");
|
||||
setEnding("");
|
||||
setMu(false);
|
||||
}, [question]);
|
||||
const canSubmit = !!(stemRoot && ending);
|
||||
const showMu =
|
||||
question.tense === "negative imperative" ||
|
||||
isImperativeTense(question.tense);
|
||||
function handleSubmit() {
|
||||
const { formula } = question;
|
||||
callback(
|
||||
ba === formula.ba &&
|
||||
mu === formula.mu &&
|
||||
stemRoot === formula.stemRoot &&
|
||||
ending === formula.ending
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="my-4" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<p className="lead">{humanReadableT(question.tense)}</p>
|
||||
</div>
|
||||
<div className="text-center mb-2">
|
||||
<BaPicker value={ba} onChange={setBa} />
|
||||
{showMu && <MuPicker value={mu} onChange={setMu} />}
|
||||
<RootsAndStemsPicker value={stemRoot} onChange={setStemRoot} />
|
||||
<EndingPicker value={ending} onChange={setEnding} />
|
||||
<button
|
||||
className="btn btn-primary my-2"
|
||||
disabled={!canSubmit}
|
||||
onClick={canSubmit ? handleSubmit : undefined}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
<samp>{printFormula({ ba, stemRoot, ending, mu })}</samp>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function humanReadableT(tense: T.VerbTense | T.ImperativeTense | "negative imperative"): string {
|
||||
if (tense === "negative imperative") {
|
||||
return tense;
|
||||
}
|
||||
return humanReadableVerbForm(tense);
|
||||
function humanReadableT(
|
||||
tense: T.VerbTense | T.ImperativeTense | "negative imperative"
|
||||
): string {
|
||||
if (tense === "negative imperative") {
|
||||
return tense;
|
||||
}
|
||||
return humanReadableVerbForm(tense);
|
||||
}
|
||||
|
||||
function printFormula({ mu, ba, stemRoot, ending }: {
|
||||
mu: boolean,
|
||||
ba: boolean,
|
||||
stemRoot: StemRoot | "",
|
||||
ending: Ending | "",
|
||||
function printFormula({
|
||||
mu,
|
||||
ba,
|
||||
stemRoot,
|
||||
ending,
|
||||
}: {
|
||||
mu: boolean;
|
||||
ba: boolean;
|
||||
stemRoot: StemRoot | "";
|
||||
ending: Ending | "";
|
||||
}): string {
|
||||
const elements = [
|
||||
mu ? "mu" : undefined,
|
||||
ba ? "ba" : undefined,
|
||||
stemRoot,
|
||||
ending ? `${ending} ending` : undefined,
|
||||
].filter(x => x) as string[];
|
||||
return elements.join(" + ");
|
||||
const elements = [
|
||||
mu ? "mu" : undefined,
|
||||
ba ? "ba" : undefined,
|
||||
stemRoot,
|
||||
ending ? `${ending} ending` : undefined,
|
||||
].filter((x) => x) as string[];
|
||||
return elements.join(" + ");
|
||||
}
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
return <div>
|
||||
<samp>{printFormula(question.formula)}</samp>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function EndingPicker({ onChange, value }: { value: Ending | "", onChange: (e: Ending | "") => void }) {
|
||||
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>
|
||||
function DisplayCorrectAnswer({
|
||||
question,
|
||||
}: {
|
||||
question: Question;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<samp>{printFormula(question.formula)}</samp>
|
||||
</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}>{{ p: "مه", f: "mú" }}</InlinePs>
|
||||
</label>
|
||||
function EndingPicker({
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
value: Ending | "";
|
||||
onChange: (e: Ending | "") => void;
|
||||
}) {
|
||||
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 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={{
|
||||
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} 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",
|
||||
maxWidth: "400px",
|
||||
margin: "0 auto",
|
||||
margin: "0 auto",
|
||||
// backgroundImage: `url(${fadedTree})`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "50% 45%",
|
||||
backgroundSize: "50%",
|
||||
}}>
|
||||
<div style={{
|
||||
border: "2px solid black",
|
||||
padding: "1rem",
|
||||
margin: "0.25rem",
|
||||
}} className="container">
|
||||
<div className={rowClass + " align-items-center"}>
|
||||
<div className={colClass}>
|
||||
<i className="fas fa-video fa-lg" />
|
||||
</div>
|
||||
<div className={colClass}>
|
||||
<div className="d-flex flex-row justify-content-center align-items-center">
|
||||
<div>
|
||||
<i className="fas fa-camera fa-lg mx-3" />
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
backgroundSize: "50%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: "2px solid black",
|
||||
padding: "1rem",
|
||||
margin: "0.25rem",
|
||||
}}
|
||||
className="container"
|
||||
>
|
||||
<div className={rowClass + " align-items-center"}>
|
||||
<div className={colClass}>
|
||||
<i className="fas fa-video fa-lg" />
|
||||
</div>
|
||||
<div className={colClass}>
|
||||
<div className="d-flex flex-row justify-content-center align-items-center">
|
||||
<div>
|
||||
<i className="fas fa-camera fa-lg mx-3" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -276,8 +276,8 @@ const VerbGame: GameSubCore<VerbGameLevel> = ({
|
|||
onChange={(e) => setWithBa(e.target.checked)}
|
||||
/>
|
||||
<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>
|
||||
with <InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the{" "}
|
||||
<span style={{ color: kidsColor }}>kids' section</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="my-1" style={{ maxWidth: "200px", margin: "0 auto" }}>
|
||||
|
@ -400,7 +400,7 @@ function DisplayCorrectAnswer({
|
|||
</div>
|
||||
<div>
|
||||
<strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a{" "}
|
||||
<InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids'
|
||||
<InlinePs opts={opts} ps={grammarUnits.baParticle} /> in the kids'
|
||||
section.
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
* Removes ă and replaces with a
|
||||
*/
|
||||
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, "");
|
||||
}
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
import rawWords from "./raw-words";
|
||||
import {
|
||||
removeAccents,
|
||||
removeFVarients,
|
||||
typePredicates as tp,
|
||||
Types as T,
|
||||
removeAccents,
|
||||
removeFVarients,
|
||||
typePredicates as tp,
|
||||
Types as T,
|
||||
} from "@lingdocs/ps-react";
|
||||
import { categorize } from "../lib/categorize";
|
||||
import { removeAShort } from "../lib/misc-helpers";
|
||||
|
||||
import { removeAShort, removeAyn } from "../lib/misc-helpers";
|
||||
|
||||
// TODO: BIG ISSUE WITH THE LOC ADVERBS BEING LUMPED INTO THE ADVERBS!
|
||||
const words = categorize<T.Entry, {
|
||||
nouns: T.NounEntry[],
|
||||
adjectives: T.AdjectiveEntry[],
|
||||
verbs: T.VerbEntry[],
|
||||
adverbs: T.AdverbEntry[],
|
||||
locativeAdverbs: T.LocativeAdverbEntry[],
|
||||
}>(rawWords, {
|
||||
nouns: tp.isNounEntry,
|
||||
adjectives: tp.isAdjectiveEntry,
|
||||
verbs: tp.isVerbEntry,
|
||||
adverbs: tp.isAdverbEntry,
|
||||
locativeAdverbs: tp.isLocativeAdverbEntry,
|
||||
const words = categorize<
|
||||
T.Entry,
|
||||
{
|
||||
nouns: T.NounEntry[];
|
||||
adjectives: T.AdjectiveEntry[];
|
||||
verbs: T.VerbEntry[];
|
||||
adverbs: T.AdverbEntry[];
|
||||
locativeAdverbs: T.LocativeAdverbEntry[];
|
||||
}
|
||||
>(rawWords, {
|
||||
nouns: tp.isNounEntry,
|
||||
adjectives: tp.isAdjectiveEntry,
|
||||
verbs: tp.isVerbEntry,
|
||||
adverbs: tp.isAdverbEntry,
|
||||
locativeAdverbs: tp.isLocativeAdverbEntry,
|
||||
});
|
||||
|
||||
export default words;
|
||||
|
@ -29,40 +31,55 @@ export default words;
|
|||
export const { nouns, adjectives, verbs, adverbs, locativeAdverbs } = words;
|
||||
|
||||
export function wordQuery(category: "nouns", w: string[]): T.NounEntry[];
|
||||
export function wordQuery(category: "adjectives", w: string[]): T.AdjectiveEntry[];
|
||||
export function wordQuery(
|
||||
category: "adjectives",
|
||||
w: string[]
|
||||
): T.AdjectiveEntry[];
|
||||
export function wordQuery(category: "adverbs", w: string[]): T.AdverbEntry[];
|
||||
export function wordQuery(category: "locativeAdverbs", w: string[]): T.LocativeAdverbEntry[];
|
||||
export function wordQuery(
|
||||
category: "locativeAdverbs",
|
||||
w: string[]
|
||||
): T.LocativeAdverbEntry[];
|
||||
export function wordQuery(category: "verbs", w: string[]): T.VerbEntry[];
|
||||
export function wordQuery(
|
||||
category: "nouns" | "adjectives" | "adverbs" | "locativeAdverbs" | "verbs",
|
||||
w: string[],
|
||||
): T.NounEntry[] | T.AdjectiveEntry[] | T.AdverbEntry[] | T.LocativeAdverbEntry[] | T.VerbEntry[] {
|
||||
function queryRemoveAccents(s: string): string {
|
||||
return removeAShort(removeAccents(s));
|
||||
}
|
||||
if (category === "verbs") {
|
||||
return w.map(word => {
|
||||
const l = words[category];
|
||||
const found = l.find(x => vMatches(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;
|
||||
category: "nouns" | "adjectives" | "adverbs" | "locativeAdverbs" | "verbs",
|
||||
w: string[]
|
||||
):
|
||||
| T.NounEntry[]
|
||||
| T.AdjectiveEntry[]
|
||||
| T.AdverbEntry[]
|
||||
| T.LocativeAdverbEntry[]
|
||||
| T.VerbEntry[] {
|
||||
function queryRemoveAccents(s: string): string {
|
||||
return removeAyn(removeAShort(removeAccents(s)));
|
||||
}
|
||||
if (category === "verbs") {
|
||||
return w.map((word) => {
|
||||
const l = words[category];
|
||||
const found = l.find((x) => vMatches(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(
|
||||
|
|
|
@ -1597,10 +1597,10 @@
|
|||
rambda "^6.7.0"
|
||||
react-select "^5.2.2"
|
||||
|
||||
"@lingdocs/ps-react@^7.7.3":
|
||||
version "7.7.3"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.7.3.tgz#353b6ddabdfabb4eae45a1d8554adb360f7238fc"
|
||||
integrity sha512-TSK5kgyCEHTmJRH+9XfchVPUVvsjOfsLlrZlW4PL95LhvBD1dh5CazZDQlPV99CLZGz3jVf9VKpiQP7RdGYTrg==
|
||||
"@lingdocs/ps-react@^7.7.6":
|
||||
version "7.7.6"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs/ps-react/-/ps-react-7.7.6.tgz#07b6d30a326ca567ad469bbc338275d456fdc1a1"
|
||||
integrity sha512-pE8Fn8/9fSaJHSvcJiFxQOJ8M3OoI/PireM1+NSko6HvDN6Y7QIoVp785rnWJm3ZTHfq/7NzQrqfGIaMoGD/9w==
|
||||
dependencies:
|
||||
"@formkit/auto-animate" "^1.0.0-beta.3"
|
||||
classnames "^2.5.1"
|
||||
|
|
Loading…
Reference in New Issue