2021-03-09 12:39:13 +00:00
|
|
|
|
/**
|
|
|
|
|
* Copyright (c) 2021 lingdocs.com
|
|
|
|
|
*
|
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import ConjugationViewer from "./components/ConjugationViewer";
|
|
|
|
|
import verbs from "./verbs";
|
|
|
|
|
import Pashto from "./components/Pashto";
|
|
|
|
|
import Phonetics from "./components/Phonetics";
|
2021-06-24 08:22:19 +00:00
|
|
|
|
import InlinePs from "./components/InlinePs";
|
2021-03-09 12:39:13 +00:00
|
|
|
|
import { conjugateVerb } from "./lib/verb-conjugation";
|
|
|
|
|
import { getVerbInfo } from "./lib/verb-info";
|
|
|
|
|
import ButtonSelect from "./components/ButtonSelect";
|
|
|
|
|
import {
|
|
|
|
|
clamp
|
|
|
|
|
} from "./lib/p-text-helpers";
|
|
|
|
|
import {
|
|
|
|
|
randomNumber,
|
|
|
|
|
} from "./lib/misc-helpers";
|
|
|
|
|
import {
|
|
|
|
|
Modal
|
|
|
|
|
} from "react-bootstrap";
|
|
|
|
|
import * as T from "./types";
|
|
|
|
|
import defualtTextOptions from "./lib/default-text-options";
|
|
|
|
|
|
|
|
|
|
type VerbType = "simple" | "stative compound" | "dynamic compound";
|
|
|
|
|
const verbTypes: VerbType[] = [
|
|
|
|
|
"simple",
|
|
|
|
|
"stative compound",
|
|
|
|
|
"dynamic compound",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const transitivities: T.Transitivity[] = [
|
|
|
|
|
"transitive",
|
|
|
|
|
"intransitive",
|
|
|
|
|
"grammatically transitive",
|
|
|
|
|
];
|
|
|
|
|
|
2021-07-04 09:55:26 +00:00
|
|
|
|
const allVerbs = verbs.map((v: { entry: T.DictionaryEntry, complement?: T.DictionaryEntry }) => ({
|
2021-03-09 12:39:13 +00:00
|
|
|
|
verb: v,
|
|
|
|
|
info: getVerbInfo(v.entry, v.complement),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [verbTs, setVerbTs] = useState<number>(0);
|
|
|
|
|
const [verbTypeShowing, setVerbTypeShowing] = useState<VerbType>("simple");
|
|
|
|
|
const [regularIrregular, setRegularIrregular] = useState<"regular" | "irregular">("regular");
|
|
|
|
|
const [transitivityShowing, setTransitivityShowing] = useState<T.Transitivity>("intransitive");
|
|
|
|
|
const [showingTextOptions, setShowingTextOptions] = useState<boolean>(false);
|
|
|
|
|
const [textOptions, setTextOptions] = useState<T.TextOptions>(defualtTextOptions);
|
|
|
|
|
const [aayTailType, setAayTailType] = useState<T.AayTail>("aay");
|
|
|
|
|
const [theme, setTheme] = useState<"light" | "dark">("light");
|
|
|
|
|
// const onlyGrammTrans = (arr: Transitivity[]) => (
|
|
|
|
|
// arr.length === 1 && arr[0] === "grammatically transitive"
|
|
|
|
|
// );
|
|
|
|
|
// const ensureSimpleVerbTypeSelected = () => {
|
|
|
|
|
// if (!verbTypesShowing.includes["simple"]) {
|
|
|
|
|
// setVerbTypesShowing([...verbTypesShowing, "simple"]);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const verbTsRaw = localStorage.getItem("verbTs");
|
|
|
|
|
const verbTypeShowing = localStorage.getItem("verbTypeShowing") as undefined | VerbType;
|
|
|
|
|
const regularIrregular = localStorage.getItem("regularIrregular") as "regular" | "irregular";
|
|
|
|
|
const transitivitiyShowing = localStorage.getItem("transitivityShowing") as undefined | T.Transitivity;
|
|
|
|
|
const theme = localStorage.getItem("theme");
|
|
|
|
|
const textOptionst = localStorage.getItem("textOptions");
|
|
|
|
|
const aayTailType = localStorage.getItem("aayType");
|
|
|
|
|
if (regularIrregular) {
|
|
|
|
|
setRegularIrregular(regularIrregular);
|
|
|
|
|
}
|
|
|
|
|
if (verbTsRaw) {
|
|
|
|
|
setVerbTs(JSON.parse(verbTsRaw));
|
|
|
|
|
}
|
|
|
|
|
if (verbTypeShowing) {
|
|
|
|
|
setVerbTypeShowing(verbTypeShowing);
|
|
|
|
|
}
|
|
|
|
|
if (transitivitiyShowing) {
|
|
|
|
|
setTransitivityShowing(transitivitiyShowing);
|
|
|
|
|
}
|
|
|
|
|
if (theme) {
|
|
|
|
|
setTheme(theme as "light" | "dark");
|
|
|
|
|
}
|
|
|
|
|
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
|
|
|
setTheme("dark");
|
|
|
|
|
}
|
|
|
|
|
if (textOptionst) {
|
|
|
|
|
setTextOptions(JSON.parse(textOptionst) as T.TextOptions);
|
|
|
|
|
}
|
|
|
|
|
if (aayTailType) {
|
|
|
|
|
setAayTailType(aayTailType as T.AayTail);
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
localStorage.setItem("verbTs", verbTs.toString());
|
|
|
|
|
localStorage.setItem("regularIrregular", regularIrregular);
|
|
|
|
|
localStorage.setItem("verbTypeShowing", verbTypeShowing);
|
|
|
|
|
localStorage.setItem("transitivityShowing", transitivityShowing);
|
|
|
|
|
localStorage.setItem("textOptions", JSON.stringify(textOptions));
|
|
|
|
|
localStorage.setItem("theme", theme);
|
|
|
|
|
localStorage.setItem("aayType", aayTailType);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
document.documentElement.setAttribute("data-theme", theme);
|
|
|
|
|
}, [theme])
|
|
|
|
|
|
|
|
|
|
const handleVerbIndexChange = (e: any) => {
|
|
|
|
|
console.log("changing to", e.target.value);
|
|
|
|
|
setVerbTs(parseInt(e.target.value));
|
|
|
|
|
}
|
|
|
|
|
const handleTypeSelection = (e: any) => {
|
|
|
|
|
const type = e.target.value as VerbType;
|
|
|
|
|
if (type === "dynamic compound") {
|
|
|
|
|
setTransitivityShowing("transitive");
|
|
|
|
|
}
|
|
|
|
|
if (type === "stative compound" && transitivityShowing === "grammatically transitive") {
|
|
|
|
|
setTransitivityShowing("transitive");
|
|
|
|
|
}
|
|
|
|
|
setVerbTypeShowing(type);
|
|
|
|
|
}
|
|
|
|
|
const handleTransitivitySelection = (e: any) => {
|
|
|
|
|
const transitivity = e.target.value as T.Transitivity;
|
|
|
|
|
if (transitivity === "grammatically transitive") {
|
|
|
|
|
setVerbTypeShowing("simple");
|
|
|
|
|
}
|
|
|
|
|
setTransitivityShowing(e.target.value as T.Transitivity);
|
|
|
|
|
}
|
|
|
|
|
const isRegularVerb = (entry: T.DictionaryEntry): boolean => (
|
|
|
|
|
!entry.l && !entry.psp && !entry.ssp && !entry.prp && !entry.pprtp && !entry.noOo && !entry.sepOo
|
|
|
|
|
);
|
|
|
|
|
const verbsAvailable = allVerbs.filter((verb) => (
|
|
|
|
|
(
|
|
|
|
|
(verb.info.type === "transitive or grammatically transitive simple" && verbTypeShowing === "simple") && (transitivityShowing === "transitive" || transitivityShowing === "grammatically transitive")
|
|
|
|
|
) ||
|
|
|
|
|
((
|
|
|
|
|
verbTypeShowing === verb.info.type ||
|
|
|
|
|
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or stative compound") ||
|
|
|
|
|
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or stative compound") ||
|
|
|
|
|
(verbTypeShowing === "dynamic compound" && verb.info.type === "dynamic or generative stative compound") ||
|
|
|
|
|
(verbTypeShowing === "stative compound" && verb.info.type === "dynamic or generative stative compound")
|
|
|
|
|
)
|
|
|
|
|
&& (
|
|
|
|
|
transitivityShowing === verb.info.transitivity
|
|
|
|
|
))
|
|
|
|
|
)).filter((verb) => {
|
|
|
|
|
if (verbTypeShowing !== "simple") {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return regularIrregular === "regular"
|
|
|
|
|
? isRegularVerb(verb.verb.entry)
|
|
|
|
|
: !isRegularVerb(verb.verb.entry);
|
|
|
|
|
}).sort((a, b) => a.verb.entry.p.localeCompare(b.verb.entry.p, "ps"));
|
|
|
|
|
|
|
|
|
|
const v = (() => {
|
|
|
|
|
const vFound = verbsAvailable.find(v => v.verb.entry.ts === verbTs);
|
|
|
|
|
if (vFound) return vFound;
|
|
|
|
|
if (verbsAvailable.length === 0) return undefined;
|
|
|
|
|
const vTopOfList = verbsAvailable[0];
|
|
|
|
|
setVerbTs(vTopOfList.verb.entry.ts);
|
|
|
|
|
return vTopOfList;
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
const pickRandomVerb = () => {
|
|
|
|
|
let newIndex: number;
|
|
|
|
|
do {
|
|
|
|
|
newIndex = randomNumber(0, verbsAvailable.length);
|
|
|
|
|
} while(verbsAvailable[newIndex].verb.entry.ts === verbTs);
|
|
|
|
|
setVerbTs(verbsAvailable[newIndex].verb.entry.ts);
|
|
|
|
|
};
|
|
|
|
|
const makeVerbLabel = (entry: T.DictionaryEntry): string => (
|
|
|
|
|
`${entry.p} - ${clamp(entry.e, 20)}`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const conjugation = v
|
|
|
|
|
? conjugateVerb(v.verb.entry, aayTailType, v.verb.complement)
|
|
|
|
|
: undefined;
|
2021-06-24 08:22:19 +00:00
|
|
|
|
// if (v) {
|
|
|
|
|
// console.log("Verb chosen:");
|
|
|
|
|
// console.log(v.verb);
|
|
|
|
|
// console.log("Conjugation of verb:")
|
|
|
|
|
// console.log(conjugation);
|
|
|
|
|
// }
|
2021-03-09 12:39:13 +00:00
|
|
|
|
return <>
|
|
|
|
|
<main className="flex-shrink-0 mb-4">
|
2021-04-17 10:40:34 +00:00
|
|
|
|
<div className="container" style={{ maxWidth: "800px" }}>
|
2021-03-09 12:39:13 +00:00
|
|
|
|
<div style={{ position: "absolute", top: "1.5rem", right: "1.5rem", display: "flex", flexDirection: "row" }}>
|
|
|
|
|
<div
|
|
|
|
|
className="clickable mr-3"
|
|
|
|
|
onClick={() => setShowingTextOptions(true)}
|
|
|
|
|
>
|
|
|
|
|
<i className="fa-lg fas fa-cog" />
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
className="clickable"
|
|
|
|
|
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
|
|
|
|
>
|
|
|
|
|
<i className={`fa-lg fas fa-${theme === "light" ? "sun" : "moon"}`} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-center" style={{ marginTop: "3rem", marginBottom: "1rem" }}>
|
|
|
|
|
<h1 className="display-4 mt-2">Pashto Verb Explorer</h1>
|
|
|
|
|
<p>by <a href="https://www.lingdocs.com">LingDocs</a></p>
|
|
|
|
|
<p className="lead my-4">
|
|
|
|
|
Each form is made from one simple <samp>formula</samp> which <a href="https://www.lingdocs.com/blog/pashto-verbs-master-chart">works for all verbs</a>. 👨🔬
|
|
|
|
|
</p>
|
|
|
|
|
<p>Choose a verb 👇, look at its roots and stems 🌳, see how all the forms are made and what they mean. 🤓</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}>
|
|
|
|
|
<div className="card-body">
|
|
|
|
|
<div className="row">
|
|
|
|
|
<div className="col-sm-6">
|
|
|
|
|
{(v && conjugation) ?
|
|
|
|
|
<div>
|
|
|
|
|
<div className="mb-1">Select a verb:</div>
|
|
|
|
|
<div className="input-group">
|
|
|
|
|
<select className="custom-select" value={verbTs} onChange={handleVerbIndexChange}>
|
|
|
|
|
{verbsAvailable.length
|
|
|
|
|
? verbsAvailable.map((v, i) => (
|
|
|
|
|
<option value={v.verb.entry.ts} key={i} dir="ltr">
|
|
|
|
|
{"\u200E"}{makeVerbLabel(v.verb.entry)}
|
|
|
|
|
</option>
|
|
|
|
|
))
|
|
|
|
|
: <option>Select a verb type</option>
|
|
|
|
|
}
|
|
|
|
|
</select>
|
|
|
|
|
<div className="input-group-append">
|
|
|
|
|
<button className="btn btn-secondary" onClick={pickRandomVerb}>
|
|
|
|
|
<i className="fas fa-random" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="my-3">
|
|
|
|
|
<div>
|
|
|
|
|
<strong>
|
|
|
|
|
<Pashto opts={textOptions}>{v.verb.entry}</Pashto>
|
|
|
|
|
{` `}-{` `}
|
|
|
|
|
<Phonetics opts={textOptions}>{v.verb.entry}</Phonetics>
|
|
|
|
|
</strong>
|
|
|
|
|
{` `}
|
|
|
|
|
<em>{v.verb.entry.c}</em>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="ml-3">{v.verb.entry.e}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
: <div className="alert alert-warning mb-4" role="alert">
|
|
|
|
|
No such verbs available...
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="col-sm-6">
|
|
|
|
|
<h6>Verb type:</h6>
|
|
|
|
|
<div onChange={handleTypeSelection}>
|
|
|
|
|
{verbTypes.map((type) => (
|
|
|
|
|
<div key={type} className="form-check">
|
|
|
|
|
<input
|
|
|
|
|
className="form-check-input"
|
|
|
|
|
type="radio"
|
|
|
|
|
name="verb-type"
|
|
|
|
|
checked={verbTypeShowing === type}
|
|
|
|
|
value={type}
|
|
|
|
|
/>
|
|
|
|
|
<label className="form-check-label">
|
|
|
|
|
{type}
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
{verbTypeShowing === "simple" &&
|
|
|
|
|
<div className="mt-2">
|
|
|
|
|
<ButtonSelect
|
|
|
|
|
small
|
|
|
|
|
options={[
|
|
|
|
|
{ label: "regular", value: "regular" },
|
|
|
|
|
{ label: "irregular", value: "irregular" },
|
|
|
|
|
]}
|
|
|
|
|
value={regularIrregular}
|
|
|
|
|
handleChange={(p) => {
|
|
|
|
|
setRegularIrregular(p as "regular" | "irregular");
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
<h6 className="mt-2">Transitivity:</h6>
|
|
|
|
|
<div onChange={handleTransitivitySelection}>
|
|
|
|
|
{transitivities.map((transitivity) => (
|
|
|
|
|
<div key={transitivity} className="form-check">
|
|
|
|
|
<input
|
|
|
|
|
className="form-check-input"
|
|
|
|
|
type="radio"
|
|
|
|
|
name="transitivity"
|
|
|
|
|
checked={transitivityShowing === transitivity}
|
|
|
|
|
value={transitivity}
|
|
|
|
|
/>
|
|
|
|
|
<label className="form-check-label">
|
|
|
|
|
{transitivity}
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{conjugation && <ConjugationViewer
|
|
|
|
|
conjugation={conjugation}
|
|
|
|
|
textOptions={textOptions}
|
2021-07-04 09:55:26 +00:00
|
|
|
|
ec={v ? v.verb.entry.ec : undefined}
|
2021-07-05 08:21:57 +00:00
|
|
|
|
ep={v ? v.verb.entry.ep : undefined}
|
2021-03-09 12:39:13 +00:00
|
|
|
|
/>}
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
<Modal show={showingTextOptions} onHide={() => setShowingTextOptions(false)}>
|
|
|
|
|
<Modal.Header closeButton>
|
|
|
|
|
<Modal.Title>Settings</Modal.Title>
|
|
|
|
|
</Modal.Header>
|
|
|
|
|
<Modal.Body>
|
|
|
|
|
<h6>Pashto Spelling</h6>
|
|
|
|
|
<ButtonSelect
|
|
|
|
|
options={[
|
|
|
|
|
{ label: "🇦🇫 Afghan", value: "Afghan" },
|
|
|
|
|
{ label: "🇵🇰 Pakistani", value: "Pakistani" },
|
|
|
|
|
]}
|
|
|
|
|
value={textOptions.spelling}
|
|
|
|
|
handleChange={(p) => {
|
|
|
|
|
setTextOptions({
|
|
|
|
|
...textOptions,
|
|
|
|
|
spelling: p as "Afghan" | "Pakistani",
|
|
|
|
|
});
|
|
|
|
|
if (p === "Pakistani") setAayTailType("ey");
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{textOptions.spelling !== "Pakistani" && <>
|
|
|
|
|
<h6 className="mt-3">Non-Inflecting Tail Spelling</h6>
|
|
|
|
|
<ButtonSelect
|
|
|
|
|
options={[
|
|
|
|
|
{ label: <InlinePs opts={textOptions}>{{ p: "ی", f: "ey" }}</InlinePs>, value: "ey" },
|
|
|
|
|
{ label: <InlinePs opts={textOptions}>{{ p: "ای", f: "aay" }}</InlinePs>, value: "aay" },
|
|
|
|
|
]}
|
|
|
|
|
value={aayTailType}
|
|
|
|
|
handleChange={(p) => setAayTailType(p as "ey" | "aay")}
|
|
|
|
|
/>
|
|
|
|
|
</>}
|
|
|
|
|
<h6 className="mt-3">Diacritics</h6>
|
|
|
|
|
<ButtonSelect
|
|
|
|
|
options={[
|
|
|
|
|
{ label: "On", value: "true" },
|
|
|
|
|
{ label: "Off", value: "false" },
|
|
|
|
|
]}
|
|
|
|
|
value={textOptions.diacritics.toString()}
|
|
|
|
|
handleChange={(p) => setTextOptions({ ...textOptions, diacritics: p === "true" })}
|
|
|
|
|
/>
|
|
|
|
|
<h6 className="mt-3">Pashto Text Size</h6>
|
|
|
|
|
<ButtonSelect
|
|
|
|
|
options={[
|
|
|
|
|
{ label: "Normal", value: "normal" },
|
|
|
|
|
{ label: "Large", value: "larger" },
|
|
|
|
|
{ label: "X-Large", value: "largest" },
|
|
|
|
|
]}
|
|
|
|
|
value={textOptions.pTextSize}
|
|
|
|
|
handleChange={(p) => setTextOptions({ ...textOptions, pTextSize: p as "normal" | "larger" | "largest" })}
|
|
|
|
|
/>
|
|
|
|
|
<h6 className="mt-3">Phonetics</h6>
|
|
|
|
|
<ButtonSelect
|
|
|
|
|
options={[
|
|
|
|
|
{ label: "LingDocs", value: "lingdocs" },
|
|
|
|
|
{ label: "IPA", value: "ipa" },
|
|
|
|
|
{ label: "ALAC", value: "alalc" },
|
|
|
|
|
{ label: "None", value: "none" },
|
|
|
|
|
]}
|
|
|
|
|
value={textOptions.phonetics}
|
|
|
|
|
handleChange={(p) => setTextOptions({ ...textOptions, phonetics: p as "lingdocs" | "ipa" | "none" | "alalc" })}
|
|
|
|
|
/>
|
|
|
|
|
</Modal.Body>
|
|
|
|
|
<Modal.Footer>
|
|
|
|
|
<button type="button" className="btn btn-primary clb" onClick={() => setShowingTextOptions(false)}>
|
|
|
|
|
Close
|
|
|
|
|
</button>
|
|
|
|
|
</Modal.Footer>
|
|
|
|
|
</Modal>
|
|
|
|
|
<footer className="footer mt-auto py-3" style={{ backgroundColor: "#f5f5f5" }}>
|
|
|
|
|
<div className="container">
|
|
|
|
|
<span className="text-muted">Copyright © 2020 <a href="https://www.lingdocs.com">lingdocs.com</a> all rights reserverd</span>
|
|
|
|
|
</div>
|
|
|
|
|
</footer>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default App;
|