Compare commits
2 Commits
e40b383276
...
c5a238ab05
Author | SHA1 | Date |
---|---|---|
adueck | c5a238ab05 | |
adueck | 6451cee925 |
|
@ -30,6 +30,7 @@ jobs:
|
||||||
yarn install-r
|
yarn install-r
|
||||||
yarn build-library
|
yarn build-library
|
||||||
yarn test --silent
|
yarn test --silent
|
||||||
|
yarn check-all-inflections
|
||||||
cp .npmrc src/lib
|
cp .npmrc src/lib
|
||||||
cp .npmrc src/components
|
cp .npmrc src/components
|
||||||
cd src/lib
|
cd src/lib
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "pashto-inflector",
|
"name": "pashto-inflector",
|
||||||
"version": "7.4.1",
|
"version": "7.5.1",
|
||||||
"author": "lingdocs.com",
|
"author": "lingdocs.com",
|
||||||
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
|
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
|
||||||
"homepage": "https://verbs.lingdocs.com",
|
"homepage": "https://verbs.lingdocs.com",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@lingdocs/ps-react",
|
"name": "@lingdocs/ps-react",
|
||||||
"version": "7.4.1",
|
"version": "7.5.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lingdocs/ps-react",
|
"name": "@lingdocs/ps-react",
|
||||||
"version": "7.4.1",
|
"version": "7.5.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formkit/auto-animate": "^1.0.0-beta.3",
|
"@formkit/auto-animate": "^1.0.0-beta.3",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@lingdocs/ps-react",
|
"name": "@lingdocs/ps-react",
|
||||||
"version": "7.4.1",
|
"version": "7.5.1",
|
||||||
"description": "Pashto inflector library module with React components",
|
"description": "Pashto inflector library module with React components",
|
||||||
"main": "dist/components/library.js",
|
"main": "dist/components/library.js",
|
||||||
"module": "dist/components/library.js",
|
"module": "dist/components/library.js",
|
||||||
|
|
|
@ -3,29 +3,39 @@ import * as T from "../../types";
|
||||||
import AdjectivePicker from "./np-picker/AdjectivePicker";
|
import AdjectivePicker from "./np-picker/AdjectivePicker";
|
||||||
import LocativeAdverbPicker from "./ep-explorer/eq-comp-picker/LocativeAdverbPicker";
|
import LocativeAdverbPicker from "./ep-explorer/eq-comp-picker/LocativeAdverbPicker";
|
||||||
import SandwichPicker from "./np-picker/SandwichPicker";
|
import SandwichPicker from "./np-picker/SandwichPicker";
|
||||||
const compTypes: T.ComplementType[] = ["adjective", "loc. adv.", "sandwich", "comp. noun"];
|
const compTypes: T.ComplementType[] = [
|
||||||
|
"adjective",
|
||||||
|
"loc. adv.",
|
||||||
|
"sandwich",
|
||||||
|
"comp. noun",
|
||||||
|
];
|
||||||
|
|
||||||
function selectionTypeToCompType(s: Exclude<T.ComplementType, "comp. noun"> | "noun"): T.ComplementType {
|
function selectionTypeToCompType(
|
||||||
|
s: Exclude<T.ComplementType, "comp. noun"> | "noun"
|
||||||
|
): T.ComplementType {
|
||||||
if (s === "noun") return "comp. noun";
|
if (s === "noun") return "comp. noun";
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ComplementPicker(props: {
|
function ComplementPicker(props: {
|
||||||
phraseIsComplete: boolean,
|
phraseIsComplete: boolean;
|
||||||
onChange: (comp: T.ComplementSelection | undefined) => void,
|
onChange: (comp: T.ComplementSelection | undefined) => void;
|
||||||
comp: T.ComplementSelection | undefined,
|
comp: T.ComplementSelection | undefined;
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
cantClear?: boolean,
|
cantClear?: boolean;
|
||||||
heading?: JSX.Element | string,
|
heading?: JSX.Element | string;
|
||||||
entryFeeder: T.EntryFeeder,
|
entryFeeder: T.EntryFeeder;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [compType, setCompType] = useState<T.ComplementType | undefined>(props.comp
|
const [compType, setCompType] = useState<T.ComplementType | undefined>(
|
||||||
? selectionTypeToCompType(props.comp.selection.type)
|
props.comp ? selectionTypeToCompType(props.comp.selection.type) : undefined
|
||||||
: undefined);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCompType(props.comp
|
setCompType(
|
||||||
|
props.comp
|
||||||
? selectionTypeToCompType(props.comp.selection.type)
|
? selectionTypeToCompType(props.comp.selection.type)
|
||||||
: undefined);
|
: undefined
|
||||||
|
);
|
||||||
}, [props.comp]);
|
}, [props.comp]);
|
||||||
function handleClear() {
|
function handleClear() {
|
||||||
setCompType(undefined);
|
setCompType(undefined);
|
||||||
|
@ -39,26 +49,32 @@ function ComplementPicker(props: {
|
||||||
setCompType(undefined);
|
setCompType(undefined);
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
}
|
}
|
||||||
const clearButton = (compType && !props.cantClear)
|
const clearButton =
|
||||||
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
|
compType && !props.cantClear ? (
|
||||||
: <div></div>;
|
<button className="btn btn-sm btn-light mb-2" onClick={handleClear}>
|
||||||
return <>
|
X
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div></div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className="d-flex flex-row justify-content-between">
|
<div className="d-flex flex-row justify-content-between">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div>
|
<div>
|
||||||
{typeof props.heading === "string"
|
{typeof props.heading === "string" ? (
|
||||||
? <div className="h5 text-center">{props.heading}</div>
|
<div className="h5 text-center">{props.heading}</div>
|
||||||
: props.heading}
|
) : (
|
||||||
|
props.heading
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>{clearButton}</div>
|
||||||
{clearButton}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{!compType && (
|
||||||
{!compType && <div className="text-center">
|
<div className="text-center">
|
||||||
<div className="h6 mr-3">
|
<div className="h6 mr-3">Choose Complement</div>
|
||||||
Choose Complement
|
{compTypes.map((cpt) => (
|
||||||
</div>
|
<div key={cpt} className="mb-2">
|
||||||
{compTypes.map((cpt) => <div key={cpt} className="mb-2">
|
|
||||||
<button
|
<button
|
||||||
key={cpt}
|
key={cpt}
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -67,41 +83,70 @@ function ComplementPicker(props: {
|
||||||
>
|
>
|
||||||
{cpt}
|
{cpt}
|
||||||
</button>
|
</button>
|
||||||
</div>)}
|
</div>
|
||||||
</div>}
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ minWidth: "9rem" }}>
|
<div style={{ minWidth: "9rem" }}>
|
||||||
{compType === "adjective" ?
|
{compType === "adjective" ? (
|
||||||
<AdjectivePicker
|
<AdjectivePicker
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
adjective={props.comp?.selection.type === "adjective" ? props.comp.selection : undefined}
|
adjective={
|
||||||
|
props.comp?.selection.type === "adjective"
|
||||||
|
? props.comp.selection
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
onChange={(a) => props.onChange(a ? { type: "complement", selection: a } : undefined)}
|
onChange={(a) =>
|
||||||
|
props.onChange(
|
||||||
|
a ? { type: "complement", selection: a } : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
: compType === "loc. adv."
|
) : compType === "loc. adv." ? (
|
||||||
? <LocativeAdverbPicker
|
<LocativeAdverbPicker
|
||||||
entryFeeder={props.entryFeeder.locativeAdverbs}
|
entryFeeder={props.entryFeeder.locativeAdverbs}
|
||||||
adjective={props.comp?.selection.type === "loc. adv." ? props.comp.selection : undefined}
|
adjective={
|
||||||
|
props.comp?.selection.type === "loc. adv."
|
||||||
|
? props.comp.selection
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
onChange={(a) => props.onChange(a ? { type: "complement", selection: a } : undefined)}
|
onChange={(a) =>
|
||||||
|
props.onChange(
|
||||||
|
a ? { type: "complement", selection: a } : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
: compType === "sandwich"
|
) : compType === "sandwich" ? (
|
||||||
? <SandwichPicker
|
<SandwichPicker
|
||||||
onChange={(a) => props.onChange(a ? { type: "complement", selection: a } : undefined)}
|
onChange={(a) =>
|
||||||
|
props.onChange(
|
||||||
|
a ? { type: "complement", selection: a } : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
sandwich={props.comp?.selection.type === "sandwich" ? props.comp.selection : undefined}
|
sandwich={
|
||||||
|
props.comp?.selection.type === "sandwich"
|
||||||
|
? props.comp.selection
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
onExit={handleSandwichExit}
|
onExit={handleSandwichExit}
|
||||||
// TODO: get phraseIsComplete working here
|
// TODO: get phraseIsComplete working here
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
: compType === "comp. noun"
|
) : compType === "comp. noun" ? (
|
||||||
? <div style={{ maxWidth: "9rem" }}>
|
<div style={{ maxWidth: "9rem" }}>
|
||||||
Sorry, can't choose complement nouns yet 🚧
|
Sorry, can't choose complement nouns yet 🚧
|
||||||
</div>
|
</div>
|
||||||
: null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ComplementPicker;
|
export default ComplementPicker;
|
|
@ -25,27 +25,31 @@ export const customStyles: StylesConfig = {
|
||||||
...base,
|
...base,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}),
|
}),
|
||||||
}
|
};
|
||||||
|
|
||||||
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
entryFeeder: T.EntryFeederSingleType<E>,
|
entryFeeder: T.EntryFeederSingleType<E>;
|
||||||
value: E | undefined,
|
value: E | undefined;
|
||||||
onChange: (value: E | undefined) => void,
|
onChange: (value: E | undefined) => void;
|
||||||
name: string | undefined,
|
name: string | undefined;
|
||||||
isVerbSelect?: boolean,
|
isVerbSelect?: boolean;
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
style?: StyleHTMLAttributes<HTMLDivElement>,
|
style?: StyleHTMLAttributes<HTMLDivElement>;
|
||||||
placeholder?: string,
|
placeholder?: string;
|
||||||
}) {
|
}) {
|
||||||
const divStyle = props.style || { width: "13rem" };
|
const divStyle = props.style || { width: "13rem" };
|
||||||
const placeholder = "placeholder" in props
|
const placeholder =
|
||||||
|
"placeholder" in props
|
||||||
? props.placeholder
|
? props.placeholder
|
||||||
: "search" in props.entryFeeder
|
: "search" in props.entryFeeder
|
||||||
? "Search Pashto"
|
? "Search Pashto"
|
||||||
: "Search…";
|
: "Search…";
|
||||||
function makeOption(e: E | T.DictionaryEntry) {
|
function makeOption(e: E | T.DictionaryEntry) {
|
||||||
if ("entry" in e) {
|
if ("entry" in e) {
|
||||||
return (props.isVerbSelect ? makeVerbSelectOption : makeSelectOption)(e, props.opts);
|
return (props.isVerbSelect ? makeVerbSelectOption : makeSelectOption)(
|
||||||
|
e,
|
||||||
|
props.opts
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return makeSelectOption(e, props.opts);
|
return makeSelectOption(e, props.opts);
|
||||||
}
|
}
|
||||||
|
@ -54,10 +58,14 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
const search = props.entryFeeder.search;
|
const search = props.entryFeeder.search;
|
||||||
const getByTs = props.entryFeeder.getByTs;
|
const getByTs = props.entryFeeder.getByTs;
|
||||||
const options = (searchString: string) =>
|
const options = (searchString: string) =>
|
||||||
new Promise<{ value: string, label: string | JSX.Element }[]>(resolve => {
|
new Promise<{ value: string; label: string | JSX.Element }[]>(
|
||||||
|
(resolve) => {
|
||||||
resolve(search(searchString).map(makeOption));
|
resolve(search(searchString).map(makeOption));
|
||||||
});
|
}
|
||||||
const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
|
);
|
||||||
|
const onChange = (
|
||||||
|
v: { label: string | JSX.Element; value: string } | null
|
||||||
|
) => {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
return;
|
return;
|
||||||
|
@ -65,8 +73,9 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
const s = getByTs(parseInt(v.value));
|
const s = getByTs(parseInt(v.value));
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
props.onChange(s);
|
props.onChange(s);
|
||||||
}
|
};
|
||||||
return <div style={divStyle}>
|
return (
|
||||||
|
<div style={divStyle}>
|
||||||
<AsyncSelect
|
<AsyncSelect
|
||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
|
@ -78,51 +87,94 @@ function EntrySelect<E extends T.DictionaryEntry | T.VerbEntry>(props: {
|
||||||
loadOptions={options}
|
loadOptions={options}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const entries = props.entryFeeder;
|
const entries = props.entryFeeder;
|
||||||
const options = entries
|
const options = entries
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if ("entry" in a) {
|
if ("entry" in a) {
|
||||||
return a.entry.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS")
|
return a.entry.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS");
|
||||||
}
|
}
|
||||||
return a.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS");
|
return a.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS");
|
||||||
})
|
})
|
||||||
.map(makeOption);
|
.map(makeOption);
|
||||||
const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
|
const onChange = (
|
||||||
|
v: { label: string | JSX.Element; value: string } | null
|
||||||
|
) => {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const s = entries.find(e => (
|
const s = entries.find((e) =>
|
||||||
("entry" in e)
|
"entry" in e
|
||||||
? e.entry.ts.toString() === v.value
|
? e.entry.ts.toString() === v.value
|
||||||
: e.ts.toString() === v.value
|
: e.ts.toString() === v.value
|
||||||
));
|
);
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
props.onChange(s);
|
props.onChange(s);
|
||||||
}
|
};
|
||||||
return <div style={divStyle}>
|
return (
|
||||||
|
<div style={divStyle}>
|
||||||
<Select
|
<Select
|
||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
isSearchable={true}
|
isSearchable={true}
|
||||||
value={value || null}
|
value={value || null}
|
||||||
// @ts-ignore - gets messed up when using customStyles
|
// @ts-ignore - sadly gets messed up when using customStyles
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeterminerSelect(props: {
|
||||||
|
determiners: Readonly<T.Determiner[]>;
|
||||||
|
value: T.Determiner[];
|
||||||
|
onChange: (value: T.Determiner[] | undefined) => void;
|
||||||
|
name: string | undefined;
|
||||||
|
opts: T.TextOptions;
|
||||||
|
}) {
|
||||||
|
const placeholder = "Select determiner…";
|
||||||
|
const value = props.value ? props.value.map(makeDeterminerOption) : undefined;
|
||||||
|
const options = props.determiners.map(makeDeterminerOption);
|
||||||
|
const onChange = (
|
||||||
|
v: { label: string | JSX.Element; value: string }[] | null
|
||||||
|
) => {
|
||||||
|
if (!v) {
|
||||||
|
props.onChange(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dets: T.Determiner[] = v.map(
|
||||||
|
({ value }) => JSON.parse(value) as T.Determiner
|
||||||
|
);
|
||||||
|
props.onChange(dets);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
name={props.name}
|
||||||
|
styles={customStyles}
|
||||||
|
isSearchable={true}
|
||||||
|
value={value}
|
||||||
|
isMulti
|
||||||
|
// @ts-ignore - gets messed up when using customStyles
|
||||||
|
onChange={onChange}
|
||||||
|
className="mb-2"
|
||||||
|
options={options}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SandwichSelect<E extends T.Sandwich>(props: {
|
export function SandwichSelect<E extends T.Sandwich>(props: {
|
||||||
sandwiches: E[],
|
sandwiches: E[];
|
||||||
value: E | undefined,
|
value: E | undefined;
|
||||||
onChange: (value: E | undefined) => void,
|
onChange: (value: E | undefined) => void;
|
||||||
name: string | undefined,
|
name: string | undefined;
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
style?: StyleHTMLAttributes<HTMLDivElement>,
|
style?: StyleHTMLAttributes<HTMLDivElement>;
|
||||||
}) {
|
}) {
|
||||||
const divStyle = props.style || { width: "13rem" };
|
const divStyle = props.style || { width: "13rem" };
|
||||||
const placeholder = "Select Sandwich…";
|
const placeholder = "Select Sandwich…";
|
||||||
|
@ -135,22 +187,30 @@ export function SandwichSelect<E extends T.Sandwich>(props: {
|
||||||
// return a.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS");
|
// return a.p.localeCompare("p" in b ? b.p : b.entry.p, "af-PS");
|
||||||
// })
|
// })
|
||||||
.map(makeSandwichOption);
|
.map(makeSandwichOption);
|
||||||
const onChange = (v: { label: string | JSX.Element, value: string } | null) => {
|
const onChange = (
|
||||||
|
v: { label: string | JSX.Element; value: string } | null
|
||||||
|
) => {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const s = props.sandwiches.find(e => {
|
const s = props.sandwiches.find((e) => {
|
||||||
const sValue = JSON.parse(v.value) as T.Sandwich;
|
const sValue = JSON.parse(v.value) as T.Sandwich;
|
||||||
if (sValue.type !== "sandwich") throw new Error("error converting selected sandwich value to a sandwich");
|
if (sValue.type !== "sandwich")
|
||||||
return sandwichSideEq(e.before, sValue.before)
|
throw new Error(
|
||||||
&& sandwichSideEq(e.after, sValue.after)
|
"error converting selected sandwich value to a sandwich"
|
||||||
&& (e.e === sValue.e);
|
);
|
||||||
|
return (
|
||||||
|
sandwichSideEq(e.before, sValue.before) &&
|
||||||
|
sandwichSideEq(e.after, sValue.after) &&
|
||||||
|
e.e === sValue.e
|
||||||
|
);
|
||||||
});
|
});
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
props.onChange(s);
|
props.onChange(s);
|
||||||
}
|
};
|
||||||
return <div style={divStyle}>
|
return (
|
||||||
|
<div style={divStyle}>
|
||||||
<div>Sandwich Base</div>
|
<div>Sandwich Base</div>
|
||||||
<Select
|
<Select
|
||||||
styles={customStyles}
|
styles={customStyles}
|
||||||
|
@ -163,11 +223,15 @@ export function SandwichSelect<E extends T.Sandwich>(props: {
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sandwichSideEq(s1: T.PsString | undefined, s2: T.PsString | undefined): boolean {
|
function sandwichSideEq(
|
||||||
|
s1: T.PsString | undefined,
|
||||||
|
s2: T.PsString | undefined
|
||||||
|
): boolean {
|
||||||
if (s1 === undefined && s2 === undefined) {
|
if (s1 === undefined && s2 === undefined) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
if (typeof s1 === "object" && typeof s2 === "object" && s1.p === s2.p) {
|
if (typeof s1 === "object" && typeof s2 === "object" && s1.p === s2.p) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -175,9 +239,21 @@ function sandwichSideEq(s1: T.PsString | undefined, s2: T.PsString | undefined):
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeSandwichOption(s: T.Sandwich): { label: string, value: string } {
|
function makeDeterminerOption(d: T.Determiner): {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
} {
|
||||||
return {
|
return {
|
||||||
label: `${s.before ? s.before.p : ""} ... ${s.after ? s.after.p : ""} (${s.e})`,
|
label: `${d.p} - ${d.f}`,
|
||||||
|
value: JSON.stringify(d),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSandwichOption(s: T.Sandwich): { label: string; value: string } {
|
||||||
|
return {
|
||||||
|
label: `${s.before ? s.before.p : ""} ... ${s.after ? s.after.p : ""} (${
|
||||||
|
s.e
|
||||||
|
})`,
|
||||||
value: JSON.stringify(s),
|
value: JSON.stringify(s),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,22 +6,21 @@ type APType = "adverb" | "sandwich";
|
||||||
const types: APType[] = ["adverb", "sandwich"];
|
const types: APType[] = ["adverb", "sandwich"];
|
||||||
|
|
||||||
function APPicker(props: {
|
function APPicker(props: {
|
||||||
phraseIsComplete: boolean,
|
phraseIsComplete: boolean;
|
||||||
onChange: (comp: T.APSelection | undefined) => void,
|
onChange: (comp: T.APSelection | undefined) => void;
|
||||||
AP: T.APSelection | undefined,
|
AP: T.APSelection | undefined;
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
cantClear?: boolean,
|
cantClear?: boolean;
|
||||||
heading?: JSX.Element | string,
|
heading?: JSX.Element | string;
|
||||||
entryFeeder: T.EntryFeeder,
|
entryFeeder: T.EntryFeeder;
|
||||||
onRemove: () => void,
|
onRemove: () => void;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [type, setType] = useState<APType | undefined>(props.AP
|
const [type, setType] = useState<APType | undefined>(
|
||||||
? props.AP.selection.type
|
props.AP ? props.AP.selection.type : undefined
|
||||||
: undefined);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setType(props.AP
|
setType(props.AP ? props.AP.selection.type : undefined);
|
||||||
? props.AP.selection.type
|
|
||||||
: undefined);
|
|
||||||
}, [props.AP]);
|
}, [props.AP]);
|
||||||
function handleClear() {
|
function handleClear() {
|
||||||
setType(undefined);
|
setType(undefined);
|
||||||
|
@ -35,30 +34,38 @@ function APPicker(props: {
|
||||||
setType(undefined);
|
setType(undefined);
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
}
|
}
|
||||||
const clearButton = (type && !props.cantClear)
|
const clearButton =
|
||||||
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
|
type && !props.cantClear ? (
|
||||||
: (!props.cantClear)
|
<button className="btn btn-sm btn-light mb-2" onClick={handleClear}>
|
||||||
? <div>
|
X
|
||||||
<div className="clickable" onClick={props.onRemove}><i className="fas fa-trash" /></div>
|
</button>
|
||||||
|
) : !props.cantClear ? (
|
||||||
|
<div>
|
||||||
|
<div className="clickable" onClick={props.onRemove}>
|
||||||
|
<i className="fas fa-trash" />
|
||||||
</div>
|
</div>
|
||||||
: <div></div>;
|
</div>
|
||||||
return <>
|
) : (
|
||||||
|
<div></div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className="d-flex flex-row justify-content-between">
|
<div className="d-flex flex-row justify-content-between">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div>
|
<div>
|
||||||
{typeof props.heading === "string"
|
{typeof props.heading === "string" ? (
|
||||||
? <div className="h5 text-center">{props.heading}</div>
|
<div className="h5 text-center">{props.heading}</div>
|
||||||
: props.heading}
|
) : (
|
||||||
|
props.heading
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>{clearButton}</div>
|
||||||
{clearButton}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{!type && (
|
||||||
{!type && <div className="text-center">
|
<div className="text-center">
|
||||||
<div className="h6 mr-3">
|
<div className="h6 mr-3">Choose AP</div>
|
||||||
Choose AP
|
{types.map((apt) => (
|
||||||
</div>
|
<div key={apt} className="mb-2">
|
||||||
{types.map((apt) => <div key={apt} className="mb-2">
|
|
||||||
<button
|
<button
|
||||||
key={apt}
|
key={apt}
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -67,28 +74,44 @@ function APPicker(props: {
|
||||||
>
|
>
|
||||||
{apt}
|
{apt}
|
||||||
</button>
|
</button>
|
||||||
</div>)}
|
</div>
|
||||||
</div>}
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ minWidth: "9rem" }}>
|
<div style={{ minWidth: "9rem" }}>
|
||||||
{type === "adverb" ?
|
{type === "adverb" ? (
|
||||||
<AdverbPicker
|
<AdverbPicker
|
||||||
entryFeeder={props.entryFeeder.adverbs}
|
entryFeeder={props.entryFeeder.adverbs}
|
||||||
adjective={props.AP?.selection.type === "adverb" ? props.AP.selection : undefined}
|
adjective={
|
||||||
|
props.AP?.selection.type === "adverb"
|
||||||
|
? props.AP.selection
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
onChange={(a) => props.onChange(a ? { type: "AP", selection: a } : undefined)}
|
onChange={(a) =>
|
||||||
|
props.onChange(a ? { type: "AP", selection: a } : undefined)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
: type === "sandwich" ?
|
) : type === "sandwich" ? (
|
||||||
<SandwichPicker
|
<SandwichPicker
|
||||||
onChange={(a) => props.onChange(a ? { type: "AP", selection: a } : undefined)}
|
onChange={(a) =>
|
||||||
|
props.onChange(a ? { type: "AP", selection: a } : undefined)
|
||||||
|
}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
sandwich={props.AP?.selection.type === "sandwich" ? props.AP.selection : undefined}
|
sandwich={
|
||||||
|
props.AP?.selection.type === "sandwich"
|
||||||
|
? props.AP.selection
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
onExit={handleSandwichExit}
|
onExit={handleSandwichExit}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
: null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default APPicker;
|
export default APPicker;
|
|
@ -600,23 +600,29 @@ function Sandwich({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Demonstrative({
|
function Determiners({
|
||||||
opts,
|
opts,
|
||||||
script,
|
script,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
script: "p" | "f";
|
script: "p" | "f";
|
||||||
children: T.Rendered<T.DemonstrativeSelection> | undefined;
|
children: T.Rendered<T.DeterminersSelection> | undefined;
|
||||||
}) {
|
}) {
|
||||||
if (!children) {
|
if (!children || children.determiners.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Border padding={"1rem"}>{children.ps[script]}</Border>
|
<div className={`d-flex flex-row${script === "p" ? "-reverse" : ""}`}>
|
||||||
<div>DEM</div>
|
{children.determiners.map((d) => (
|
||||||
<SubText>{children.e}</SubText>
|
<div className="mx-1">
|
||||||
|
<Border padding={"1rem"}>{d.ps[0][script]}</Border>
|
||||||
|
<div>{"demonstrative" in d.determiner ? "DEM" : "DET"}</div>
|
||||||
|
<SubText>{d.e}</SubText>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -663,8 +669,8 @@ export function NPBlock({
|
||||||
np.selection.possesor &&
|
np.selection.possesor &&
|
||||||
!np.selection.possesor.shrunken
|
!np.selection.possesor.shrunken
|
||||||
);
|
);
|
||||||
const demWithoutNoun =
|
const detsWithoutNoun =
|
||||||
np.selection.demonstrative && !np.selection.demonstrative.withNoun;
|
np.selection.determiners && !np.selection.determiners.withNoun;
|
||||||
const elements = [
|
const elements = [
|
||||||
...(!inside
|
...(!inside
|
||||||
? [
|
? [
|
||||||
|
@ -675,12 +681,12 @@ export function NPBlock({
|
||||||
</Possesors>,
|
</Possesors>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
<Demonstrative opts={opts} script={script}>
|
<Determiners opts={opts} script={script}>
|
||||||
{np.selection.demonstrative ? np.selection.demonstrative : undefined}
|
{np.selection.determiners}
|
||||||
</Demonstrative>,
|
</Determiners>,
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
opacity: demWithoutNoun ? 0.5 : 1,
|
opacity: detsWithoutNoun ? 0.5 : 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Adjectives opts={opts} script={script}>
|
<Adjectives opts={opts} script={script}>
|
||||||
|
@ -689,7 +695,7 @@ export function NPBlock({
|
||||||
</div>,
|
</div>,
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
opacity: demWithoutNoun ? 0.5 : 1,
|
opacity: detsWithoutNoun ? 0.5 : 1,
|
||||||
}}
|
}}
|
||||||
className={np.selection.adjectives?.length ? "mx-1" : ""}
|
className={np.selection.adjectives?.length ? "mx-1" : ""}
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,20 +3,24 @@ import NPPicker from "../np-picker/NPPicker";
|
||||||
import EquativePicker from "./EquativePicker";
|
import EquativePicker from "./EquativePicker";
|
||||||
import ButtonSelect from "../ButtonSelect";
|
import ButtonSelect from "../ButtonSelect";
|
||||||
import ComplementPicker from "../ComplementPicker";
|
import ComplementPicker from "../ComplementPicker";
|
||||||
import epsReducer, { EpsReducerAction } from "../../../lib/src/phrase-building/eps-reducer";
|
import epsReducer, {
|
||||||
import {
|
EpsReducerAction,
|
||||||
useEffect,
|
} from "../../../lib/src/phrase-building/eps-reducer";
|
||||||
useRef,
|
import { useEffect, useRef } from "react";
|
||||||
} from "react";
|
|
||||||
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
|
import { completeEPSelection } from "../../../lib/src/phrase-building/render-ep";
|
||||||
import APPicker from "../../src/ap-picker/APPicker";
|
import APPicker from "../../src/ap-picker/APPicker";
|
||||||
import autoAnimate from "@formkit/auto-animate";
|
import autoAnimate from "@formkit/auto-animate";
|
||||||
|
|
||||||
function EPPicker({ opts, eps, onChange, entryFeeder }: {
|
function EPPicker({
|
||||||
opts: T.TextOptions,
|
opts,
|
||||||
eps: T.EPSelectionState,
|
eps,
|
||||||
onChange: (eps: T.EPSelectionState) => void,
|
onChange,
|
||||||
entryFeeder: T.EntryFeeder,
|
entryFeeder,
|
||||||
|
}: {
|
||||||
|
opts: T.TextOptions;
|
||||||
|
eps: T.EPSelectionState;
|
||||||
|
onChange: (eps: T.EPSelectionState) => void;
|
||||||
|
entryFeeder: T.EntryFeeder;
|
||||||
}) {
|
}) {
|
||||||
const parent = useRef<HTMLDivElement>(null);
|
const parent = useRef<HTMLDivElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -26,45 +30,84 @@ function EPPicker({ opts, eps, onChange, entryFeeder }: {
|
||||||
onChange(epsReducer(eps, action));
|
onChange(epsReducer(eps, action));
|
||||||
}
|
}
|
||||||
const phraseIsComplete = !!completeEPSelection(eps);
|
const phraseIsComplete = !!completeEPSelection(eps);
|
||||||
return <div>
|
return (
|
||||||
<div className="clickable h5" onClick={() => adjustEps({ type: "insert new AP" })}>+ AP</div>
|
<div>
|
||||||
<div ref={parent} className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
|
<div
|
||||||
|
className="clickable h5"
|
||||||
|
onClick={() => adjustEps({ type: "insert new AP" })}
|
||||||
|
>
|
||||||
|
+ AP
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={parent}
|
||||||
|
className="d-flex flex-row justify-content-around flex-wrap"
|
||||||
|
style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}
|
||||||
|
>
|
||||||
{eps.blocks.map(({ block, key }, i) => (
|
{eps.blocks.map(({ block, key }, i) => (
|
||||||
<div className="my-2 card block-card p-1 mx-1" key={key}>
|
<div className="my-2 card block-card p-1 mx-1" key={key}>
|
||||||
<div className="d-flex flex-row justify-content-between mb-1" style={{ height: "1rem" }}>
|
<div
|
||||||
{i > 0 ? <div
|
className="d-flex flex-row justify-content-between mb-1"
|
||||||
|
style={{ height: "1rem" }}
|
||||||
|
>
|
||||||
|
{i > 0 ? (
|
||||||
|
<div
|
||||||
className="small clickable ml-1"
|
className="small clickable ml-1"
|
||||||
onClick={() => adjustEps({ type: "shift block", payload: { index: i, direction: "back" }})}
|
onClick={() =>
|
||||||
|
adjustEps({
|
||||||
|
type: "shift block",
|
||||||
|
payload: { index: i, direction: "back" },
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="fas fa-chevron-left" />
|
<i className="fas fa-chevron-left" />
|
||||||
</div> : <div/>}
|
</div>
|
||||||
{i < eps.blocks.length - 1 ? <div
|
) : (
|
||||||
|
<div />
|
||||||
|
)}
|
||||||
|
{i < eps.blocks.length - 1 ? (
|
||||||
|
<div
|
||||||
className="small clickable mr-1"
|
className="small clickable mr-1"
|
||||||
onClick={() => adjustEps({ type: "shift block", payload: { index: i, direction: "forward" }})}
|
onClick={() =>
|
||||||
|
adjustEps({
|
||||||
|
type: "shift block",
|
||||||
|
payload: { index: i, direction: "forward" },
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="fas fa-chevron-right" />
|
<i className="fas fa-chevron-right" />
|
||||||
</div> : <div/>}
|
|
||||||
</div>
|
</div>
|
||||||
{block && block.type === "subjectSelection"
|
) : (
|
||||||
? <NPPicker
|
<div />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{block && block.type === "subjectSelection" ? (
|
||||||
|
<NPPicker
|
||||||
phraseIsComplete={phraseIsComplete}
|
phraseIsComplete={phraseIsComplete}
|
||||||
heading={<div className="h5 text-center">Subject</div>}
|
heading={<div className="h5 text-center">Subject</div>}
|
||||||
entryFeeder={entryFeeder}
|
entryFeeder={entryFeeder}
|
||||||
np={block.selection}
|
np={block.selection}
|
||||||
counterPart={undefined}
|
counterPart={undefined}
|
||||||
role="subject"
|
role="subject"
|
||||||
onChange={payload => adjustEps({ type: "set subject", payload })}
|
onChange={(payload) =>
|
||||||
|
adjustEps({ type: "set subject", payload })
|
||||||
|
}
|
||||||
opts={opts}
|
opts={opts}
|
||||||
|
negative={eps.equative.negative}
|
||||||
/>
|
/>
|
||||||
: <APPicker
|
) : (
|
||||||
|
<APPicker
|
||||||
|
negative={eps.equative.negative}
|
||||||
phraseIsComplete={phraseIsComplete}
|
phraseIsComplete={phraseIsComplete}
|
||||||
heading="AP"
|
heading="AP"
|
||||||
entryFeeder={entryFeeder}
|
entryFeeder={entryFeeder}
|
||||||
AP={block}
|
AP={block}
|
||||||
opts={opts}
|
opts={opts}
|
||||||
onChange={AP => adjustEps({ type: "set AP", payload: { index: i, AP } })}
|
onChange={(AP) =>
|
||||||
|
adjustEps({ type: "set AP", payload: { index: i, AP } })
|
||||||
|
}
|
||||||
onRemove={() => adjustEps({ type: "remove AP", payload: i })}
|
onRemove={() => adjustEps({ type: "remove AP", payload: i })}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="my-2 card block-card p-1">
|
<div className="my-2 card block-card p-1">
|
||||||
|
@ -77,35 +120,52 @@ function EPPicker({ opts, eps, onChange, entryFeeder }: {
|
||||||
{ value: "Complement", label: "Complement" },
|
{ value: "Complement", label: "Complement" },
|
||||||
]}
|
]}
|
||||||
value={eps.predicate.type}
|
value={eps.predicate.type}
|
||||||
handleChange={payload => adjustEps({ type: "set predicate type", payload })}
|
handleChange={(payload) =>
|
||||||
|
adjustEps({ type: "set predicate type", payload })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{eps.predicate.type === "NP" ? <NPPicker
|
{eps.predicate.type === "NP" ? (
|
||||||
|
<NPPicker
|
||||||
phraseIsComplete={phraseIsComplete}
|
phraseIsComplete={phraseIsComplete}
|
||||||
entryFeeder={entryFeeder}
|
entryFeeder={entryFeeder}
|
||||||
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
|
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
|
||||||
counterPart={undefined}
|
counterPart={undefined}
|
||||||
role="subject"
|
role="subject"
|
||||||
onChange={payload => adjustEps({ type: "set predicate NP", payload })}
|
onChange={(payload) =>
|
||||||
|
adjustEps({ type: "set predicate NP", payload })
|
||||||
|
}
|
||||||
opts={opts}
|
opts={opts}
|
||||||
/> : <ComplementPicker
|
negative={eps.equative.negative}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ComplementPicker
|
||||||
phraseIsComplete={phraseIsComplete}
|
phraseIsComplete={phraseIsComplete}
|
||||||
comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined}
|
comp={
|
||||||
onChange={payload => adjustEps({ type: "set predicate complement", payload })}
|
eps.predicate.type === "Complement"
|
||||||
|
? eps.predicate.Complement
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onChange={(payload) =>
|
||||||
|
adjustEps({ type: "set predicate complement", payload })
|
||||||
|
}
|
||||||
opts={opts}
|
opts={opts}
|
||||||
entryFeeder={entryFeeder}
|
entryFeeder={entryFeeder}
|
||||||
/>}
|
negative={eps.equative.negative}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<div className="h5 text-center clickable">Equative</div>
|
<div className="h5 text-center clickable">Equative</div>
|
||||||
<EquativePicker
|
<EquativePicker
|
||||||
equative={eps.equative}
|
equative={eps.equative}
|
||||||
onChange={payload => adjustEps({ type: "set equative", payload })}
|
onChange={(payload) => adjustEps({ type: "set equative", payload })}
|
||||||
hideNegative={false}
|
hideNegative={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EPPicker;
|
export default EPPicker;
|
|
@ -8,6 +8,7 @@ function AdjectiveManager(props: {
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
onChange: (adjs: T.AdjectiveSelection[]) => void;
|
onChange: (adjs: T.AdjectiveSelection[]) => void;
|
||||||
phraseIsComplete: boolean;
|
phraseIsComplete: boolean;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [adding, setAdding] = useState<boolean>(false);
|
const [adding, setAdding] = useState<boolean>(false);
|
||||||
function handleChange(i: number) {
|
function handleChange(i: number) {
|
||||||
|
@ -48,6 +49,7 @@ function AdjectiveManager(props: {
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
onChange={handleAddNew}
|
onChange={handleAddNew}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -70,6 +72,7 @@ function AdjectiveManager(props: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AdjectivePicker
|
<AdjectivePicker
|
||||||
|
negative={props.negative}
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
noTitle
|
noTitle
|
||||||
key={`adj${i}`}
|
key={`adj${i}`}
|
||||||
|
|
|
@ -5,12 +5,13 @@ import EntrySelect from "../EntrySelect";
|
||||||
import SandwichPicker from "./SandwichPicker";
|
import SandwichPicker from "./SandwichPicker";
|
||||||
|
|
||||||
function AdjectivePicker(props: {
|
function AdjectivePicker(props: {
|
||||||
entryFeeder: T.EntryFeeder,
|
entryFeeder: T.EntryFeeder;
|
||||||
adjective: T.AdjectiveSelection | undefined,
|
adjective: T.AdjectiveSelection | undefined;
|
||||||
onChange: (p: T.AdjectiveSelection | undefined) => void,
|
onChange: (p: T.AdjectiveSelection | undefined) => void;
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
noTitle?: boolean,
|
noTitle?: boolean;
|
||||||
phraseIsComplete: boolean,
|
phraseIsComplete: boolean;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [addingSandwich, setAddingSandwich] = useState<boolean>(false);
|
const [addingSandwich, setAddingSandwich] = useState<boolean>(false);
|
||||||
function onEntrySelect(entry: T.AdjectiveEntry | undefined) {
|
function onEntrySelect(entry: T.AdjectiveEntry | undefined) {
|
||||||
|
@ -19,7 +20,9 @@ function AdjectivePicker(props: {
|
||||||
}
|
}
|
||||||
props.onChange(makeAdjectiveSelection(entry));
|
props.onChange(makeAdjectiveSelection(entry));
|
||||||
}
|
}
|
||||||
function handleSandwichChange(s: T.SandwichSelection<T.Sandwich> | undefined) {
|
function handleSandwichChange(
|
||||||
|
s: T.SandwichSelection<T.Sandwich> | undefined
|
||||||
|
) {
|
||||||
if (!props.adjective) return;
|
if (!props.adjective) return;
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.adjective,
|
...props.adjective,
|
||||||
|
@ -33,8 +36,11 @@ function AdjectivePicker(props: {
|
||||||
setAddingSandwich(false);
|
setAddingSandwich(false);
|
||||||
props.onChange(undefined);
|
props.onChange(undefined);
|
||||||
}
|
}
|
||||||
return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
return (
|
||||||
{(props.adjective?.sandwich || addingSandwich) && <SandwichPicker
|
<div style={{ maxWidth: "225px", minWidth: "125px" }}>
|
||||||
|
{(props.adjective?.sandwich || addingSandwich) && (
|
||||||
|
<SandwichPicker
|
||||||
|
negative={props.negative}
|
||||||
onChange={handleSandwichChange}
|
onChange={handleSandwichChange}
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
sandwich={props.adjective?.sandwich}
|
sandwich={props.adjective?.sandwich}
|
||||||
|
@ -42,11 +48,14 @@ function AdjectivePicker(props: {
|
||||||
onExit={handleSandwichExit}
|
onExit={handleSandwichExit}
|
||||||
// TODO: not allowing shrinking any possesisives on sandwiches for now - need to work with the blocks and their special behaviour
|
// TODO: not allowing shrinking any possesisives on sandwiches for now - need to work with the blocks and their special behaviour
|
||||||
phraseIsComplete={false}
|
phraseIsComplete={false}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
<div className="d-flex flex-row justify-content-between align-items-baseline">
|
<div className="d-flex flex-row justify-content-between align-items-baseline">
|
||||||
{!props.noTitle && <div>
|
{!props.noTitle && (
|
||||||
|
<div>
|
||||||
<div className="h6">Adjective</div>
|
<div className="h6">Adjective</div>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
{/* not ready for sandwiches on adjectives */}
|
{/* not ready for sandwiches on adjectives */}
|
||||||
{/* {(!addingSandwich && props.adjective && !props.adjective?.sandwich)
|
{/* {(!addingSandwich && props.adjective && !props.adjective?.sandwich)
|
||||||
? <div className="clickable" onClick={() => setAddingSandwich(true)}>+ Sandwich</div>
|
? <div className="clickable" onClick={() => setAddingSandwich(true)}>+ Sandwich</div>
|
||||||
|
@ -62,7 +71,8 @@ function AdjectivePicker(props: {
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdjectivePicker;
|
export default AdjectivePicker;
|
|
@ -1,38 +1,52 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import classNames from "classnames";
|
import { DeterminerSelect } from "../EntrySelect";
|
||||||
|
|
||||||
export default function DemonstrativePicker({
|
export default function DeterminersPicker({
|
||||||
demonstrative,
|
determiners,
|
||||||
onChange,
|
onChange,
|
||||||
|
opts,
|
||||||
|
negative,
|
||||||
}: {
|
}: {
|
||||||
demonstrative: T.NounSelection["demonstrative"];
|
determiners: T.NounSelection["determiners"];
|
||||||
onChange: (dem: T.NounSelection["demonstrative"]) => void;
|
onChange: (dem: T.NounSelection["determiners"]) => void;
|
||||||
|
opts: T.TextOptions;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
function handleDChange(d: "daa" | "hagha" | "dagha") {
|
const hasDemonstrative =
|
||||||
if (!demonstrative) {
|
determiners &&
|
||||||
onChange({
|
determiners.determiners.some((d) => "demonstrative" in d.determiner);
|
||||||
type: "demonstrative",
|
function allowed(d: T.Determiner): boolean {
|
||||||
demonstrative: d,
|
if (d.p === "هیڅ" && !negative) {
|
||||||
withNoun: true,
|
return false;
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onChange({
|
|
||||||
...demonstrative,
|
|
||||||
demonstrative: d,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (hasDemonstrative && "demonstrative" in d) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
function handleWithNounChange(e: React.ChangeEvent<HTMLInputElement>) {
|
function handleWithNounChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
if (demonstrative) {
|
if (determiners) {
|
||||||
onChange({
|
onChange({
|
||||||
...demonstrative,
|
...determiners,
|
||||||
withNoun: e.target.checked,
|
withNoun: e.target.checked,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function handleDeterminerChange(value: T.Determiner[] | undefined) {
|
||||||
|
onChange({
|
||||||
|
type: "determiners",
|
||||||
|
withNoun: determiners ? determiners.withNoun : true,
|
||||||
|
determiners: value
|
||||||
|
? value.map((d) => ({
|
||||||
|
type: "determiner",
|
||||||
|
determiner: d,
|
||||||
|
}))
|
||||||
|
: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="d-flex flex-row justify-content-around py-1">
|
{/* <div className="d-flex flex-row justify-content-around py-1">
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
className={classNames("btn", "btn-outline-secondary", {
|
className={classNames("btn", "btn-outline-secondary", {
|
||||||
|
@ -63,20 +77,29 @@ export default function DemonstrativePicker({
|
||||||
هغه
|
هغه
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
|
<DeterminerSelect
|
||||||
|
determiners={T.determiners.filter(allowed)}
|
||||||
|
value={
|
||||||
|
determiners ? determiners.determiners.map((x) => x.determiner) : []
|
||||||
|
}
|
||||||
|
onChange={handleDeterminerChange}
|
||||||
|
name="determiner"
|
||||||
|
opts={opts}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="form-check"
|
className="form-check"
|
||||||
style={{
|
style={{
|
||||||
opacity: demonstrative ? 1 : 0.5,
|
opacity: determiners ? 1 : 0.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={demonstrative?.withNoun}
|
checked={determiners?.withNoun}
|
||||||
onChange={handleWithNounChange}
|
onChange={handleWithNounChange}
|
||||||
id="withNoun"
|
id="withNoun"
|
||||||
disabled={!demonstrative}
|
disabled={!hasDemonstrative}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label text-muted" htmlFor="withNoun">
|
<label className="form-check-label text-muted" htmlFor="withNoun">
|
||||||
with noun
|
with noun
|
|
@ -7,7 +7,7 @@ import InlinePs from "../InlinePs";
|
||||||
import EntrySelect from "../EntrySelect";
|
import EntrySelect from "../EntrySelect";
|
||||||
import AdjectiveManager from "./AdjectiveManager";
|
import AdjectiveManager from "./AdjectiveManager";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import DemonstrativePicker from "./DemonstrativePicker";
|
import DeterminersPicker from "./DeterminersPicker";
|
||||||
|
|
||||||
// const filterOptions = [
|
// const filterOptions = [
|
||||||
// {
|
// {
|
||||||
|
@ -62,9 +62,9 @@ function NPNounPicker(props: {
|
||||||
onChange: (p: T.NounSelection | undefined) => void;
|
onChange: (p: T.NounSelection | undefined) => void;
|
||||||
opts: T.TextOptions;
|
opts: T.TextOptions;
|
||||||
phraseIsComplete: boolean;
|
phraseIsComplete: boolean;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [addingDemonstrative, setAddingDemonstrative] =
|
const [addingDeterminers, setAddingDeterminers] = useState<boolean>(false);
|
||||||
useState<boolean>(false);
|
|
||||||
// const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined);
|
// const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined);
|
||||||
// const [showFilter, setShowFilter] = useState<boolean>(false)
|
// const [showFilter, setShowFilter] = useState<boolean>(false)
|
||||||
// const nounsFiltered = props.nouns
|
// const nounsFiltered = props.nouns
|
||||||
|
@ -88,13 +88,13 @@ function NPNounPicker(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleDemonstrativeChange(
|
function handleDeterminersChange(
|
||||||
demonstrative: undefined | T.NounSelection["demonstrative"]
|
determiners: undefined | T.NounSelection["determiners"]
|
||||||
) {
|
) {
|
||||||
if (props.noun) {
|
if (props.noun) {
|
||||||
props.onChange({
|
props.onChange({
|
||||||
...props.noun,
|
...props.noun,
|
||||||
demonstrative,
|
determiners,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,32 +105,34 @@ function NPNounPicker(props: {
|
||||||
minWidth: "125px",
|
minWidth: "125px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!addingDemonstrative && !props.noun?.demonstrative ? (
|
{!addingDeterminers && !props.noun?.determiners ? (
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
className="clickable text-muted"
|
className="clickable text-muted"
|
||||||
onClick={() => setAddingDemonstrative(true)}
|
onClick={() => setAddingDeterminers(true)}
|
||||||
>
|
>
|
||||||
+ Demonstrative
|
+ Determiners
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div className="d-flex flex-row justify-content-between mb-1">
|
<div className="d-flex flex-row justify-content-between mb-1">
|
||||||
<div>{!props.noun?.demonstrative ? "Add" : ""} Demonstrative</div>
|
<div>{!props.noun?.determiners ? "Add" : ""} Determiners</div>
|
||||||
<div
|
<div
|
||||||
className="clickable"
|
className="clickable"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDemonstrativeChange(undefined);
|
handleDeterminersChange(undefined);
|
||||||
setAddingDemonstrative(false);
|
setAddingDeterminers(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i className="fas fa-trash" />
|
<i className="fas fa-trash" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DemonstrativePicker
|
<DeterminersPicker
|
||||||
demonstrative={props.noun?.demonstrative}
|
determiners={props.noun?.determiners}
|
||||||
onChange={handleDemonstrativeChange}
|
onChange={handleDeterminersChange}
|
||||||
|
opts={props.opts}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -151,13 +153,14 @@ function NPNounPicker(props: {
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
opacity:
|
opacity:
|
||||||
props.noun?.demonstrative && !props.noun.demonstrative.withNoun
|
props.noun?.determiners && !props.noun.determiners.withNoun
|
||||||
? 0.5
|
? 0.5
|
||||||
: 1,
|
: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.noun && (
|
{props.noun && (
|
||||||
<AdjectiveManager
|
<AdjectiveManager
|
||||||
|
negative={props.negative}
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
adjectives={props.noun?.adjectives}
|
adjectives={props.noun?.adjectives}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
|
|
|
@ -25,6 +25,7 @@ function NPPicker(props: {
|
||||||
phraseIsComplete: boolean;
|
phraseIsComplete: boolean;
|
||||||
isShrunk?: boolean;
|
isShrunk?: boolean;
|
||||||
isRemoved?: boolean;
|
isRemoved?: boolean;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
if (
|
if (
|
||||||
props.is2ndPersonPicker &&
|
props.is2ndPersonPicker &&
|
||||||
|
@ -223,6 +224,7 @@ function NPPicker(props: {
|
||||||
role="possesor"
|
role="possesor"
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -250,6 +252,7 @@ function NPPicker(props: {
|
||||||
<NounPicker
|
<NounPicker
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
|
negative={props.negative}
|
||||||
noun={
|
noun={
|
||||||
props.np && props.np.selection.type === "noun"
|
props.np && props.np.selection.type === "noun"
|
||||||
? props.np.selection
|
? props.np.selection
|
||||||
|
|
|
@ -5,14 +5,17 @@ import { useEffect, useState } from "react";
|
||||||
import NPPicker from "./NPPicker";
|
import NPPicker from "./NPPicker";
|
||||||
|
|
||||||
function SandwichPicker(props: {
|
function SandwichPicker(props: {
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
onChange: (s: T.SandwichSelection<T.Sandwich> | undefined) => void,
|
onChange: (s: T.SandwichSelection<T.Sandwich> | undefined) => void;
|
||||||
sandwich: T.SandwichSelection<T.Sandwich> | undefined;
|
sandwich: T.SandwichSelection<T.Sandwich> | undefined;
|
||||||
entryFeeder: T.EntryFeeder,
|
entryFeeder: T.EntryFeeder;
|
||||||
phraseIsComplete: boolean,
|
phraseIsComplete: boolean;
|
||||||
onExit: () => void,
|
onExit: () => void;
|
||||||
|
negative: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [sandwichBase, setSandwichBase] = useState<T.Sandwich | undefined>(props.sandwich);
|
const [sandwichBase, setSandwichBase] = useState<T.Sandwich | undefined>(
|
||||||
|
props.sandwich
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSandwichBase(props.sandwich);
|
setSandwichBase(props.sandwich);
|
||||||
}, [props.sandwich]);
|
}, [props.sandwich]);
|
||||||
|
@ -43,7 +46,8 @@ function SandwichPicker(props: {
|
||||||
function handleExit() {
|
function handleExit() {
|
||||||
props.onExit();
|
props.onExit();
|
||||||
}
|
}
|
||||||
return <div>
|
return (
|
||||||
|
<div>
|
||||||
<div className="d-flex flex-row justify-content-between">
|
<div className="d-flex flex-row justify-content-between">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div className="text-center">🥪 Sandwich</div>
|
<div className="text-center">🥪 Sandwich</div>
|
||||||
|
@ -52,7 +56,8 @@ function SandwichPicker(props: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ border: "1px #6C757D solid", padding: "3px" }}>
|
<div style={{ border: "1px #6C757D solid", padding: "3px" }}>
|
||||||
{sandwichBase && <div className="mb-2" style={{ margin: "0 auto" }}>
|
{sandwichBase && (
|
||||||
|
<div className="mb-2" style={{ margin: "0 auto" }}>
|
||||||
<NPPicker
|
<NPPicker
|
||||||
onChange={handleNounChange}
|
onChange={handleNounChange}
|
||||||
np={props.sandwich ? props.sandwich.inside : undefined}
|
np={props.sandwich ? props.sandwich.inside : undefined}
|
||||||
|
@ -62,8 +67,10 @@ function SandwichPicker(props: {
|
||||||
cantClear={true}
|
cantClear={true}
|
||||||
entryFeeder={props.entryFeeder}
|
entryFeeder={props.entryFeeder}
|
||||||
phraseIsComplete={props.phraseIsComplete}
|
phraseIsComplete={props.phraseIsComplete}
|
||||||
|
negative={props.negative}
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
<SandwichSelect
|
<SandwichSelect
|
||||||
name="sandwich"
|
name="sandwich"
|
||||||
opts={props.opts}
|
opts={props.opts}
|
||||||
|
@ -72,7 +79,8 @@ function SandwichPicker(props: {
|
||||||
onChange={handleSandwichChange}
|
onChange={handleSandwichChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SandwichPicker;
|
export default SandwichPicker;
|
|
@ -44,7 +44,7 @@ function VPExplorer(props: {
|
||||||
props.loaded
|
props.loaded
|
||||||
? props.loaded
|
? props.loaded
|
||||||
: (savedVps) => makeVPSelectionState(props.verb, savedVps),
|
: (savedVps) => makeVPSelectionState(props.verb, savedVps),
|
||||||
"vpsState16",
|
"vpsState17",
|
||||||
flashMessage
|
flashMessage
|
||||||
);
|
);
|
||||||
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
|
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
|
||||||
|
|
|
@ -163,6 +163,7 @@ function VPPicker({
|
||||||
</div>
|
</div>
|
||||||
{!block || block.type === "AP" ? (
|
{!block || block.type === "AP" ? (
|
||||||
<APPicker
|
<APPicker
|
||||||
|
negative={vps.verb.negative}
|
||||||
phraseIsComplete={phraseIsComplete}
|
phraseIsComplete={phraseIsComplete}
|
||||||
heading="AP"
|
heading="AP"
|
||||||
entryFeeder={entryFeeder}
|
entryFeeder={entryFeeder}
|
||||||
|
@ -238,6 +239,7 @@ function VPPicker({
|
||||||
opts={opts}
|
opts={opts}
|
||||||
isShrunk={servantIsShrunk && roles.servant === "subject"}
|
isShrunk={servantIsShrunk && roles.servant === "subject"}
|
||||||
isRemoved={roles.king === "subject" && VPS?.form.removeKing}
|
isRemoved={roles.king === "subject" && VPS?.form.removeKing}
|
||||||
|
negative={VPS?.verb.negative || false}
|
||||||
/>
|
/>
|
||||||
) : vps.verb &&
|
) : vps.verb &&
|
||||||
block?.type === "objectSelection" &&
|
block?.type === "objectSelection" &&
|
||||||
|
@ -350,6 +352,7 @@ function VPPicker({
|
||||||
isRemoved={
|
isRemoved={
|
||||||
roles.king === "object" && VPS?.form.removeKing
|
roles.king === "object" && VPS?.form.removeKing
|
||||||
}
|
}
|
||||||
|
negative={VPS?.verb.negative || false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -372,6 +375,7 @@ function VPPicker({
|
||||||
}
|
}
|
||||||
opts={opts}
|
opts={opts}
|
||||||
entryFeeder={entryFeeder}
|
entryFeeder={entryFeeder}
|
||||||
|
negative={vps.verb.negative}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@lingdocs/inflect",
|
"name": "@lingdocs/inflect",
|
||||||
"version": "7.4.1",
|
"version": "7.5.1",
|
||||||
"description": "Pashto inflector library",
|
"description": "Pashto inflector library",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/lib/library.d.ts",
|
"types": "dist/lib/library.d.ts",
|
||||||
|
|
|
@ -48,7 +48,7 @@ type Plurals =
|
||||||
// const endingInHayOrAynRegex = /[^ا][هع]$/;
|
// const endingInHayOrAynRegex = /[^ا][هع]$/;
|
||||||
|
|
||||||
export function getInfsAndVocative(
|
export function getInfsAndVocative(
|
||||||
entryR: T.DictionaryEntryNoFVars,
|
entryR: T.DictionaryEntryNoFVars | T.Determiner,
|
||||||
plurals: Plurals
|
plurals: Plurals
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
personNumber,
|
personNumber,
|
||||||
personToGenNum,
|
personToGenNum,
|
||||||
} from "../misc-helpers";
|
} from "../misc-helpers";
|
||||||
|
import { monoidPsString } from "../fp-ps";
|
||||||
import { applySingleOrLengthOpts, fmapSingleOrLengthOpts } from "../fp-ps";
|
import { applySingleOrLengthOpts, fmapSingleOrLengthOpts } from "../fp-ps";
|
||||||
import {
|
import {
|
||||||
concatPsString,
|
concatPsString,
|
||||||
|
@ -448,7 +449,7 @@ function ensure3rdPast(
|
||||||
}
|
}
|
||||||
const abruptEnder = ["د", "ت", "ړ"].includes(rs[0].p.slice(-1));
|
const abruptEnder = ["د", "ت", "ړ"].includes(rs[0].p.slice(-1));
|
||||||
// short endings like ورسېد
|
// short endings like ورسېد
|
||||||
const ends = abruptEnder ? [{ p: "", f: "" }, ...ending] : ending;
|
const ends = abruptEnder ? [monoidPsString.empty, ...ending] : ending;
|
||||||
return verbEndingConcat(rs, ends);
|
return verbEndingConcat(rs, ends);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function inflectWord(word: T.DictionaryEntry): T.InflectorOutput {
|
||||||
// If it's a noun/adj, inflect accordingly
|
// If it's a noun/adj, inflect accordingly
|
||||||
// TODO: What about n. f. / adj. that end in ي ??
|
// TODO: What about n. f. / adj. that end in ي ??
|
||||||
const w = removeFVarients(word);
|
const w = removeFVarients(word);
|
||||||
if (w.c?.includes("doub.")) {
|
if (word.c?.includes("doub.")) {
|
||||||
const words = splitDoubleWord(w);
|
const words = splitDoubleWord(w);
|
||||||
// TODO: Make this work for non-unisex double words
|
// TODO: Make this work for non-unisex double words
|
||||||
// Right now this an extremely bad and complex way to do this
|
// Right now this an extremely bad and complex way to do this
|
||||||
|
|
|
@ -420,3 +420,123 @@ function arrayMove<X>(ar: X[], old_index: number, new_index: number): X[] {
|
||||||
arr.splice(new_i, 0, arr.splice(old_index, 1)[0]);
|
arr.splice(new_i, 0, arr.splice(old_index, 1)[0]);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This takes 8 helper functions to recursively go down and check all determiners
|
||||||
|
// - is this what LENSES would help with?
|
||||||
|
export function removeHeetsDet<B extends T.VPSBlock[] | T.EPSBlock[]>(
|
||||||
|
blocks: B
|
||||||
|
): B {
|
||||||
|
return blocks.map<T.VPSBlock | T.EPSBlock>((x) => ({
|
||||||
|
key: x.key,
|
||||||
|
block: removeHeetsDetFromBlock(x.block),
|
||||||
|
})) as B;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Could use lenses for this
|
||||||
|
function removeHeetsDetFromBlock<
|
||||||
|
B extends T.VPSBlock["block"] | T.EPSBlock["block"]
|
||||||
|
>(block: B): B {
|
||||||
|
if (!block) {
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
if (block.type === "AP") {
|
||||||
|
return removeHeetsDetFromAP(block) as B;
|
||||||
|
}
|
||||||
|
if (block.type === "complement") {
|
||||||
|
return removeHeetsFromComp(block) as B;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
selection:
|
||||||
|
typeof block.selection === "object"
|
||||||
|
? removeHeetsFromNP(block.selection)
|
||||||
|
: block.selection,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsDetFromAP(ap: T.APSelection): T.APSelection {
|
||||||
|
if (ap.selection.type === "adverb") {
|
||||||
|
return ap;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...ap,
|
||||||
|
selection: removeHeetsFromSandwich(ap.selection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsFromSandwich(
|
||||||
|
sand: T.SandwichSelection<T.Sandwich>
|
||||||
|
): T.SandwichSelection<T.Sandwich> {
|
||||||
|
return {
|
||||||
|
...sand,
|
||||||
|
inside: removeHeetsFromNP(sand.inside),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsFromAdjective(
|
||||||
|
adj: T.AdjectiveSelection
|
||||||
|
): T.AdjectiveSelection {
|
||||||
|
return {
|
||||||
|
...adj,
|
||||||
|
sandwich: adj.sandwich ? removeHeetsFromSandwich(adj.sandwich) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsFromComp(
|
||||||
|
comp: T.ComplementSelection
|
||||||
|
): T.ComplementSelection {
|
||||||
|
if (comp.selection.type === "adjective") {
|
||||||
|
return {
|
||||||
|
...comp,
|
||||||
|
selection: removeHeetsFromAdjective(comp.selection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (comp.selection.type === "noun") {
|
||||||
|
return {
|
||||||
|
...comp,
|
||||||
|
selection: removeHeetsFromNoun(comp.selection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (comp.selection.type === "sandwich") {
|
||||||
|
return {
|
||||||
|
...comp,
|
||||||
|
selection: removeHeetsFromSandwich(comp.selection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// should be only a loc. adv. left
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsFromNoun(n: T.NounSelection): T.NounSelection {
|
||||||
|
return {
|
||||||
|
...n,
|
||||||
|
adjectives: n.adjectives.map(removeHeetsFromAdjective),
|
||||||
|
...(n.determiners
|
||||||
|
? {
|
||||||
|
determiners: removeHeetsFromDets(n.determiners),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsFromNP(np: T.NPSelection): T.NPSelection {
|
||||||
|
if (np.selection.type === "noun") {
|
||||||
|
return {
|
||||||
|
...np,
|
||||||
|
selection: removeHeetsFromNoun(np.selection),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return np;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeetsFromDets(
|
||||||
|
dets: T.DeterminersSelection | undefined
|
||||||
|
): T.DeterminersSelection | undefined {
|
||||||
|
if (!dets) {
|
||||||
|
return dets;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...dets,
|
||||||
|
determiners: dets.determiners.filter((d) => d.determiner.p !== "هیڅ"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
specifyEquativeLength,
|
specifyEquativeLength,
|
||||||
} from "./blocks-utils";
|
} from "./blocks-utils";
|
||||||
import { blank, kidsBlank } from "../misc-helpers";
|
import { blank, kidsBlank } from "../misc-helpers";
|
||||||
import { monoidPsStringWVars } from "../fp-ps";
|
import { monoidPsString, monoidPsStringWVars } from "../fp-ps";
|
||||||
import { concatAll } from "fp-ts/lib/Monoid";
|
import { concatAll } from "fp-ts/lib/Monoid";
|
||||||
|
|
||||||
type BlankoutOptions = {
|
type BlankoutOptions = {
|
||||||
|
@ -275,7 +275,7 @@ function applyBlankOut(
|
||||||
return kidsBlank;
|
return kidsBlank;
|
||||||
}
|
}
|
||||||
if (blankOut?.negative && "block" in x && x.block.type === "negative") {
|
if (blankOut?.negative && "block" in x && x.block.type === "negative") {
|
||||||
return { p: "", f: "" };
|
return monoidPsString.empty;
|
||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
});
|
});
|
||||||
|
@ -314,7 +314,7 @@ function getPsFromPiece(
|
||||||
}
|
}
|
||||||
if (piece.block.type === "objectSelection") {
|
if (piece.block.type === "objectSelection") {
|
||||||
if (typeof piece.block.selection !== "object") {
|
if (typeof piece.block.selection !== "object") {
|
||||||
return [{ p: "", f: "" }];
|
return [monoidPsString.empty];
|
||||||
}
|
}
|
||||||
return getPashtoFromRendered(piece.block.selection, subjectPerson);
|
return getPashtoFromRendered(piece.block.selection, subjectPerson);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,73 @@
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import {
|
import { personGender, personNumber } from "../misc-helpers";
|
||||||
personGender,
|
|
||||||
personNumber,
|
|
||||||
} from "../misc-helpers";
|
|
||||||
import { isUnisexNounEntry } from "../type-predicates";
|
import { isUnisexNounEntry } from "../type-predicates";
|
||||||
import { checkForMiniPronounsError } from "./compile";
|
import { checkForMiniPronounsError } from "./compile";
|
||||||
import { adjustSubjectSelection, getSubjectSelection, insertNewAP, removeAP, setAP, shiftBlock } from "./blocks-utils";
|
import {
|
||||||
|
adjustSubjectSelection,
|
||||||
|
getSubjectSelection,
|
||||||
|
insertNewAP,
|
||||||
|
removeAP,
|
||||||
|
removeHeetsDet,
|
||||||
|
setAP,
|
||||||
|
shiftBlock,
|
||||||
|
} from "./blocks-utils";
|
||||||
|
|
||||||
export type EpsReducerAction = {
|
export type EpsReducerAction =
|
||||||
type: "set predicate type",
|
| {
|
||||||
payload: "NP" | "Complement",
|
type: "set predicate type";
|
||||||
} | {
|
payload: "NP" | "Complement";
|
||||||
type: "set subject",
|
|
||||||
payload: T.NPSelection | undefined,
|
|
||||||
} | {
|
|
||||||
type: "set predicate NP",
|
|
||||||
payload: T.NPSelection | undefined,
|
|
||||||
} | {
|
|
||||||
type: "set predicate complement",
|
|
||||||
payload: T.ComplementSelection | undefined,
|
|
||||||
} | {
|
|
||||||
type: "set omitSubject",
|
|
||||||
payload: "true" | "false",
|
|
||||||
} | {
|
|
||||||
type: "set equative",
|
|
||||||
payload: T.EquativeSelection,
|
|
||||||
} | {
|
|
||||||
type: "insert new AP",
|
|
||||||
} | {
|
|
||||||
type: "set AP",
|
|
||||||
payload: {
|
|
||||||
index: number,
|
|
||||||
AP: T.APSelection | undefined,
|
|
||||||
},
|
|
||||||
} | {
|
|
||||||
type: "remove AP",
|
|
||||||
payload: number,
|
|
||||||
} | {
|
|
||||||
type: "shift block",
|
|
||||||
payload: {
|
|
||||||
index: number,
|
|
||||||
direction: "back" | "forward",
|
|
||||||
},
|
|
||||||
} | {
|
|
||||||
type: "load EPS",
|
|
||||||
payload: T.EPSelectionState,
|
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "set subject";
|
||||||
|
payload: T.NPSelection | undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "set predicate NP";
|
||||||
|
payload: T.NPSelection | undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "set predicate complement";
|
||||||
|
payload: T.ComplementSelection | undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "set omitSubject";
|
||||||
|
payload: "true" | "false";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "set equative";
|
||||||
|
payload: T.EquativeSelection;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "insert new AP";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "set AP";
|
||||||
|
payload: {
|
||||||
|
index: number;
|
||||||
|
AP: T.APSelection | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "remove AP";
|
||||||
|
payload: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "shift block";
|
||||||
|
payload: {
|
||||||
|
index: number;
|
||||||
|
direction: "back" | "forward";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "load EPS";
|
||||||
|
payload: T.EPSelectionState;
|
||||||
|
};
|
||||||
|
|
||||||
export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAction, sendAlert?: (msg: string) => void): T.EPSelectionState {
|
export default function epsReducer(
|
||||||
|
eps: T.EPSelectionState,
|
||||||
|
action: EpsReducerAction,
|
||||||
|
sendAlert?: (msg: string) => void
|
||||||
|
): T.EPSelectionState {
|
||||||
if (action.type === "set predicate type") {
|
if (action.type === "set predicate type") {
|
||||||
return {
|
return {
|
||||||
...eps,
|
...eps,
|
||||||
|
@ -65,17 +85,26 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
||||||
blocks: adjustSubjectSelection(eps.blocks, subject),
|
blocks: adjustSubjectSelection(eps.blocks, subject),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (subject.selection.type === "pronoun" && eps.predicate.type === "NP" && eps.predicate.NP?.selection.type === "noun" && isUnisexNounEntry(eps.predicate.NP.selection.entry)) {
|
if (
|
||||||
|
subject.selection.type === "pronoun" &&
|
||||||
|
eps.predicate.type === "NP" &&
|
||||||
|
eps.predicate.NP?.selection.type === "noun" &&
|
||||||
|
isUnisexNounEntry(eps.predicate.NP.selection.entry)
|
||||||
|
) {
|
||||||
const predicate = eps.predicate.NP.selection;
|
const predicate = eps.predicate.NP.selection;
|
||||||
const adjusted = {
|
const adjusted = {
|
||||||
...predicate,
|
...predicate,
|
||||||
...predicate.numberCanChange ? {
|
...(predicate.numberCanChange
|
||||||
|
? {
|
||||||
number: personNumber(subject.selection.person),
|
number: personNumber(subject.selection.person),
|
||||||
} : {},
|
|
||||||
...predicate.genderCanChange ? {
|
|
||||||
gender: personGender(subject.selection.person),
|
|
||||||
} : {},
|
|
||||||
}
|
}
|
||||||
|
: {}),
|
||||||
|
...(predicate.genderCanChange
|
||||||
|
? {
|
||||||
|
gender: personGender(subject.selection.person),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
...eps,
|
...eps,
|
||||||
blocks: adjustSubjectSelection(eps.blocks, subject),
|
blocks: adjustSubjectSelection(eps.blocks, subject),
|
||||||
|
@ -106,10 +135,17 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const subject = getSubjectSelection(eps.blocks).selection;
|
const subject = getSubjectSelection(eps.blocks).selection;
|
||||||
if (subject?.selection.type === "pronoun" && selection.selection.type === "noun" && isUnisexNounEntry(selection.selection.entry)) {
|
if (
|
||||||
|
subject?.selection.type === "pronoun" &&
|
||||||
|
selection.selection.type === "noun" &&
|
||||||
|
isUnisexNounEntry(selection.selection.entry)
|
||||||
|
) {
|
||||||
const { gender, number } = selection.selection;
|
const { gender, number } = selection.selection;
|
||||||
const pronoun = subject.selection.person;
|
const pronoun = subject.selection.person;
|
||||||
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
|
const newPronoun = movePersonNumber(
|
||||||
|
movePersonGender(pronoun, gender),
|
||||||
|
number
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...eps,
|
...eps,
|
||||||
blocks: adjustSubjectSelection(eps.blocks, {
|
blocks: adjustSubjectSelection(eps.blocks, {
|
||||||
|
@ -153,8 +189,11 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
||||||
if (action.type === "set equative") {
|
if (action.type === "set equative") {
|
||||||
return {
|
return {
|
||||||
...eps,
|
...eps,
|
||||||
|
blocks: !action.payload.negative
|
||||||
|
? removeHeetsDet(eps.blocks)
|
||||||
|
: eps.blocks,
|
||||||
equative: action.payload,
|
equative: action.payload,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
if (action.type === "insert new AP") {
|
if (action.type === "insert new AP") {
|
||||||
return {
|
return {
|
||||||
|
@ -188,7 +227,11 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
||||||
throw new Error("unknown epsReducer action");
|
throw new Error("unknown epsReducer action");
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureMiniPronounsOk(old: T.EPSelectionState, eps: T.EPSelectionState, sendAlert?: (msg: string) => void): T.EPSelectionState {
|
function ensureMiniPronounsOk(
|
||||||
|
old: T.EPSelectionState,
|
||||||
|
eps: T.EPSelectionState,
|
||||||
|
sendAlert?: (msg: string) => void
|
||||||
|
): T.EPSelectionState {
|
||||||
const error = checkForMiniPronounsError(eps);
|
const error = checkForMiniPronounsError(eps);
|
||||||
if (error) {
|
if (error) {
|
||||||
if (sendAlert) sendAlert(error);
|
if (sendAlert) sendAlert(error);
|
||||||
|
@ -202,7 +245,7 @@ function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
|
||||||
if (gender === pGender) {
|
if (gender === pGender) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
return (gender === "masc") ? (p - 1) : (p + 1);
|
return gender === "masc" ? p - 1 : p + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
|
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
|
||||||
|
@ -210,7 +253,5 @@ function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
|
||||||
if (pNumber === number) {
|
if (pNumber === number) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
return (number === "plural")
|
return number === "plural" ? p + 6 : p - 6;
|
||||||
? (p + 6)
|
|
||||||
: (p - 6);
|
|
||||||
}
|
}
|
|
@ -81,6 +81,6 @@ export function makeNounSelection(
|
||||||
possesor: !complementType ? old?.possesor : undefined,
|
possesor: !complementType ? old?.possesor : undefined,
|
||||||
dynamicComplement: complementType === "dynamic",
|
dynamicComplement: complementType === "dynamic",
|
||||||
genStativeComplement: complementType === "generative stative",
|
genStativeComplement: complementType === "generative stative",
|
||||||
demonstrative: old?.demonstrative,
|
determiners: old?.determiners,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { isFirstPerson, isSecondPerson } from "../misc-helpers";
|
||||||
import * as T from "../../../types";
|
import * as T from "../../../types";
|
||||||
import { concatPsString } from "../p-text-helpers";
|
import { concatPsString } from "../p-text-helpers";
|
||||||
import { flattenLengths } from "./compile";
|
import { flattenLengths } from "./compile";
|
||||||
|
import { monoidPsStringWVars } from "../fp-ps";
|
||||||
|
import { concatAll } from "fp-ts/lib/Monoid";
|
||||||
|
|
||||||
function getBaseAndAdjectives({
|
function getBaseWDetsAndAdjs({
|
||||||
selection,
|
selection,
|
||||||
}: T.Rendered<
|
}: T.Rendered<
|
||||||
T.NPSelection | T.ComplementSelection | T.APSelection
|
T.NPSelection | T.ComplementSelection | T.APSelection
|
||||||
|
@ -11,45 +13,39 @@ function getBaseAndAdjectives({
|
||||||
if (selection.type === "sandwich") {
|
if (selection.type === "sandwich") {
|
||||||
return getSandwichPsBaseAndAdjectives(selection);
|
return getSandwichPsBaseAndAdjectives(selection);
|
||||||
}
|
}
|
||||||
const adjs = "adjectives" in selection && selection.adjectives;
|
const determiners = (
|
||||||
const demonstrativePs = ("demonstrative" in selection &&
|
("determiners" in selection && selection.determiners?.determiners) ||
|
||||||
selection.demonstrative?.ps) || { p: "", f: "" };
|
[]
|
||||||
if (!adjs) {
|
).map((x) => x.ps);
|
||||||
// TODO: does this ever get used??
|
const detWOutNoun =
|
||||||
return flattenLengths(selection.ps).map((x) =>
|
"determiners" in selection &&
|
||||||
concatPsString(demonstrativePs, x)
|
selection.determiners &&
|
||||||
|
!selection.determiners.withNoun;
|
||||||
|
const adjs = (("adjectives" in selection && selection.adjectives) || []).map(
|
||||||
|
(x) => x.ps
|
||||||
);
|
);
|
||||||
|
const base = flattenLengths(selection.ps);
|
||||||
|
return assemblePsWords([
|
||||||
|
...determiners,
|
||||||
|
...(detWOutNoun ? [] : [...adjs, base]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.demonstrative && !selection.demonstrative.withNoun) {
|
// TODO: perhaps use this for more things (a simple compileIntoText ?)
|
||||||
return [demonstrativePs];
|
function assemblePsWords(words: T.PsString[][]): T.PsString[] {
|
||||||
|
return concatAll(monoidPsStringWVars)([
|
||||||
|
...intersperse(words, [{ p: " ", f: " " }]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return flattenLengths(selection.ps).map((p) =>
|
function intersperse<T>(arr: T[], sep: T): T[] {
|
||||||
concatPsString(
|
return arr.reduce((a: T[], v: T) => [...a, v, sep], []).slice(0, -1);
|
||||||
demonstrativePs,
|
|
||||||
// demons ? " " : "",
|
|
||||||
adjs.reduce(
|
|
||||||
(accum, curr) => {
|
|
||||||
// TODO: with variations of adjs? {
|
|
||||||
return concatPsString(
|
|
||||||
accum,
|
|
||||||
accum.p === "" && accum.f === "" ? "" : "", //" ",
|
|
||||||
curr.ps[0]
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{ p: "", f: "" }
|
|
||||||
),
|
|
||||||
" ",
|
|
||||||
p
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSandwichPsBaseAndAdjectives(
|
function getSandwichPsBaseAndAdjectives(
|
||||||
s: T.Rendered<T.SandwichSelection<T.Sandwich>>
|
s: T.Rendered<T.SandwichSelection<T.Sandwich>>
|
||||||
): T.PsString[] {
|
): T.PsString[] {
|
||||||
const insideBase = getBaseAndAdjectives(s.inside);
|
const insideBase = getBaseWDetsAndAdjs(s.inside);
|
||||||
const willContractWithPronoun =
|
const willContractWithPronoun =
|
||||||
s.before &&
|
s.before &&
|
||||||
s.before.p === "د" &&
|
s.before.p === "د" &&
|
||||||
|
@ -118,7 +114,7 @@ export function getPashtoFromRendered(
|
||||||
| T.Rendered<T.APSelection>,
|
| T.Rendered<T.APSelection>,
|
||||||
subjectsPerson: false | T.Person
|
subjectsPerson: false | T.Person
|
||||||
): T.PsString[] {
|
): T.PsString[] {
|
||||||
const base = getBaseAndAdjectives(b);
|
const base = getBaseWDetsAndAdjs(b);
|
||||||
if (b.selection.type === "loc. adv." || b.selection.type === "adverb") {
|
if (b.selection.type === "loc. adv." || b.selection.type === "adverb") {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
@ -172,7 +168,7 @@ function addPossesor(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const wPossesor = existing.flatMap((ps) =>
|
const wPossesor = existing.flatMap((ps) =>
|
||||||
getBaseAndAdjectives(owner).map((v) =>
|
getBaseWDetsAndAdjs(owner).map((v) =>
|
||||||
owner.selection.type === "pronoun" &&
|
owner.selection.type === "pronoun" &&
|
||||||
subjectsPerson !== false &&
|
subjectsPerson !== false &&
|
||||||
willBeReflexive(subjectsPerson, owner.selection.person)
|
willBeReflexive(subjectsPerson, owner.selection.person)
|
||||||
|
@ -204,7 +200,7 @@ function addArticlesAndAdjs(
|
||||||
const adjs = !np.adjectives
|
const adjs = !np.adjectives
|
||||||
? ""
|
? ""
|
||||||
: np.adjectives.reduce((accum, curr): string => {
|
: np.adjectives.reduce((accum, curr): string => {
|
||||||
if (!curr.e) throw new Error("no english for adjective");
|
if (!curr.e) return "ADJ";
|
||||||
return accum + curr.e + " ";
|
return accum + curr.e + " ";
|
||||||
}, "");
|
}, "");
|
||||||
const genderTag = np.genderCanChange
|
const genderTag = np.genderCanChange
|
||||||
|
@ -212,10 +208,17 @@ function addArticlesAndAdjs(
|
||||||
? " (f.)"
|
? " (f.)"
|
||||||
: " (m.)"
|
: " (m.)"
|
||||||
: "";
|
: "";
|
||||||
const demonstrative = np.demonstrative ? ` ${np.demonstrative.e}` : "";
|
const moreThanOneDet = (np.determiners?.determiners.length || 0) > 1;
|
||||||
const demWithoutNoun = np.demonstrative && !np.demonstrative.withNoun;
|
const determiners =
|
||||||
return `${np.demonstrative ? "" : articles}${demonstrative}${
|
np.determiners && np.determiners.determiners
|
||||||
demWithoutNoun ? ` (${(adjs + word).trim()})` : adjs + word
|
? np.determiners.determiners
|
||||||
|
// @ts-ignore - weird, ts is not recognizing this as rendered
|
||||||
|
.map((x) => (moreThanOneDet ? `(${x.e})` : x.e))
|
||||||
|
.join(" ")
|
||||||
|
: "";
|
||||||
|
const detsWithoutNoun = np.determiners && !np.determiners.withNoun;
|
||||||
|
return `${np.determiners ? "" : articles}${determiners}${
|
||||||
|
detsWithoutNoun ? ` (${(adjs + word).trim()})` : adjs + word
|
||||||
}${genderTag}`;
|
}${genderTag}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { shortVerbEndConsonant } from "../parsing/misc";
|
||||||
import { removeL } from "../new-verb-engine/rs-helpers";
|
import { removeL } from "../new-verb-engine/rs-helpers";
|
||||||
import { applySingleOrLengthOpts } from "../fp-ps";
|
import { applySingleOrLengthOpts } from "../fp-ps";
|
||||||
import { accentOnNFromEnd } from "../accent-helpers";
|
import { accentOnNFromEnd } from "../accent-helpers";
|
||||||
|
import { getInfsAndVocative } from "../inflections-and-vocative";
|
||||||
|
|
||||||
// TODO: can have subject and objects in possesors!!
|
// TODO: can have subject and objects in possesors!!
|
||||||
|
|
||||||
|
@ -115,6 +116,20 @@ export function renderNounSelection(
|
||||||
return ps.length > 0 ? ps : [psStringFromEntry(n.entry)];
|
return ps.length > 0 ? ps : [psStringFromEntry(n.entry)];
|
||||||
})();
|
})();
|
||||||
const person = getPersonNumber(n.gender, n.number);
|
const person = getPersonNumber(n.gender, n.number);
|
||||||
|
const determiners: T.Rendered<T.DeterminersSelection> | undefined =
|
||||||
|
n.determiners
|
||||||
|
? {
|
||||||
|
...n.determiners,
|
||||||
|
determiners: n.determiners.determiners.map((determiner) =>
|
||||||
|
renderDeterminer({
|
||||||
|
determiner,
|
||||||
|
inflected,
|
||||||
|
number: n.number,
|
||||||
|
gender: n.gender,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
return {
|
return {
|
||||||
...n,
|
...n,
|
||||||
adjectives: n.adjectives.map((a) =>
|
adjectives: n.adjectives.map((a) =>
|
||||||
|
@ -131,64 +146,119 @@ export function renderNounSelection(
|
||||||
ps: pashto,
|
ps: pashto,
|
||||||
e: english,
|
e: english,
|
||||||
possesor: renderPossesor(n.possesor, role),
|
possesor: renderPossesor(n.possesor, role),
|
||||||
demonstrative: renderDemonstrative({
|
determiners,
|
||||||
demonstrative: n.demonstrative,
|
|
||||||
inflected,
|
|
||||||
plural: n.number === "plural",
|
|
||||||
gender: n.gender,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDemonstrative({
|
function renderDeterminer({
|
||||||
demonstrative,
|
determiner: { determiner },
|
||||||
inflected,
|
inflected,
|
||||||
plural,
|
number,
|
||||||
gender,
|
gender,
|
||||||
}: {
|
}: {
|
||||||
demonstrative: T.DemonstrativeSelection | undefined;
|
determiner: T.DeterminerSelection;
|
||||||
inflected: boolean;
|
inflected: boolean;
|
||||||
plural: boolean;
|
number: T.NounNumber;
|
||||||
gender: T.Gender;
|
gender: T.Gender;
|
||||||
}): T.Rendered<T.DemonstrativeSelection> | undefined {
|
}): T.Rendered<T.DeterminerSelection> {
|
||||||
if (!demonstrative) {
|
if (determiner.p === "دا") {
|
||||||
return undefined;
|
const ps = inflected ? { p: "دې", f: "de" } : { p: "دا", f: "daa" };
|
||||||
|
return {
|
||||||
|
type: "determiner",
|
||||||
|
determiner,
|
||||||
|
inflected,
|
||||||
|
number,
|
||||||
|
gender,
|
||||||
|
ps: [ps],
|
||||||
|
e: number === "plural" ? "these" : "this",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const ps =
|
if (determiner.p === "دغه") {
|
||||||
demonstrative.demonstrative === "daa"
|
const ps = inflected
|
||||||
? inflected
|
? number === "plural"
|
||||||
? { p: "دې", f: "de" }
|
|
||||||
: { p: "دا", f: "daa" }
|
|
||||||
: demonstrative.demonstrative === "dagha"
|
|
||||||
? inflected
|
|
||||||
? plural
|
|
||||||
? { p: "دغو", f: "dágho" }
|
? { p: "دغو", f: "dágho" }
|
||||||
: gender === "masc"
|
: gender === "masc"
|
||||||
? { p: "دغه", f: "dághu" }
|
? { p: "دغه", f: "dághu" }
|
||||||
: { p: "دغې", f: "dághe" }
|
: { p: "دغې", f: "dághe" }
|
||||||
: { p: "دغه", f: "dágha" }
|
: { p: "دغه", f: "dágha" };
|
||||||
: inflected
|
return {
|
||||||
? plural
|
type: "determiner",
|
||||||
|
determiner,
|
||||||
|
inflected,
|
||||||
|
number,
|
||||||
|
gender,
|
||||||
|
ps: [ps],
|
||||||
|
e: number === "plural" ? "these" : "this",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (determiner.p === "هغه") {
|
||||||
|
const ps = inflected
|
||||||
|
? number === "plural"
|
||||||
? { p: "هغو", f: "hágho" }
|
? { p: "هغو", f: "hágho" }
|
||||||
: gender === "masc"
|
: gender === "masc"
|
||||||
? { p: "هغه", f: "hághu" }
|
? { p: "هغه", f: "hághu" }
|
||||||
: { p: "هغې", f: "hághe" }
|
: { p: "هغې", f: "hághe" }
|
||||||
: { p: "هغه", f: "hágha" };
|
: { p: "هغه", f: "hágha" };
|
||||||
const e =
|
|
||||||
demonstrative.demonstrative === "hagha"
|
|
||||||
? plural
|
|
||||||
? "those"
|
|
||||||
: "that"
|
|
||||||
: plural
|
|
||||||
? "these"
|
|
||||||
: "this";
|
|
||||||
return {
|
return {
|
||||||
...demonstrative,
|
type: "determiner",
|
||||||
ps,
|
determiner,
|
||||||
|
inflected,
|
||||||
|
number,
|
||||||
|
gender,
|
||||||
|
ps: [ps],
|
||||||
|
e: number === "plural" ? "those" : "that",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const e =
|
||||||
|
determiner.f === "Tol"
|
||||||
|
? "all/the whole"
|
||||||
|
: determiner.f === "bul"
|
||||||
|
? "other/another"
|
||||||
|
: determiner.f === "har"
|
||||||
|
? "every/each"
|
||||||
|
: determiner.f === "koom"
|
||||||
|
? "some/which"
|
||||||
|
: determiner.f === "heets"
|
||||||
|
? "no"
|
||||||
|
: determiner.f === "dáase"
|
||||||
|
? number === "plural"
|
||||||
|
? "such/like these"
|
||||||
|
: "such/like this"
|
||||||
|
: determiner.f === "daghase"
|
||||||
|
? `just such/just like ${number === "plural" ? "these" : "this"}`
|
||||||
|
: determiner.f === "hase"
|
||||||
|
? `such/like ${number === "plural" ? "those" : "that"}`
|
||||||
|
: number === "plural"
|
||||||
|
? "just such/just like these"
|
||||||
|
: "just such/just like this";
|
||||||
|
return {
|
||||||
|
type: "determiner",
|
||||||
|
determiner,
|
||||||
|
inflected,
|
||||||
|
number,
|
||||||
|
gender,
|
||||||
|
ps: inflectDeterminer(determiner, inflected, gender, number),
|
||||||
e,
|
e,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inflectDeterminer(
|
||||||
|
determiner: T.Determiner,
|
||||||
|
inflected: boolean,
|
||||||
|
gender: T.Gender,
|
||||||
|
number: T.NounNumber
|
||||||
|
): T.PsString[] {
|
||||||
|
const infs = getInfsAndVocative(determiner, undefined);
|
||||||
|
if (!infs || !infs.inflections) {
|
||||||
|
return [{ p: determiner.p, f: determiner.f }];
|
||||||
|
}
|
||||||
|
const inf = getBasicInf(infs.inflections, gender, number, inflected);
|
||||||
|
if (!inf) {
|
||||||
|
return [{ p: determiner.p, f: determiner.f }];
|
||||||
|
}
|
||||||
|
return inf;
|
||||||
|
}
|
||||||
|
|
||||||
function renderPronounSelection(
|
function renderPronounSelection(
|
||||||
p: T.PronounSelection,
|
p: T.PronounSelection,
|
||||||
inflected: boolean,
|
inflected: boolean,
|
||||||
|
@ -269,6 +339,20 @@ function renderPossesor(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBasicInf(
|
||||||
|
infs: T.Inflections,
|
||||||
|
gender: T.Gender,
|
||||||
|
number: T.NounNumber,
|
||||||
|
inflected: boolean
|
||||||
|
): T.PsString[] | false {
|
||||||
|
const inflectionNumber = (inflected ? 1 : 0) + (number === "plural" ? 1 : 0);
|
||||||
|
if (gender in infs) {
|
||||||
|
// @ts-ignore
|
||||||
|
return infs[gender][inflectionNumber];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function getInf(
|
function getInf(
|
||||||
infs: T.InflectorOutput,
|
infs: T.InflectorOutput,
|
||||||
t: "plural" | "arabicPlural" | "inflections",
|
t: "plural" | "arabicPlural" | "inflections",
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
getSubjectSelection,
|
getSubjectSelection,
|
||||||
insertNewAP,
|
insertNewAP,
|
||||||
removeAP,
|
removeAP,
|
||||||
|
removeHeetsDet,
|
||||||
setAP,
|
setAP,
|
||||||
shiftBlock,
|
shiftBlock,
|
||||||
} from "./blocks-utils";
|
} from "./blocks-utils";
|
||||||
|
@ -19,6 +20,7 @@ import {
|
||||||
changeTransitivity,
|
changeTransitivity,
|
||||||
makeVPSelectionState,
|
makeVPSelectionState,
|
||||||
} from "./verb-selection";
|
} from "./verb-selection";
|
||||||
|
import { mapGen } from "../fp-ps";
|
||||||
|
|
||||||
export type VpsReducerAction =
|
export type VpsReducerAction =
|
||||||
| {
|
| {
|
||||||
|
@ -203,11 +205,13 @@ export function vpsReducer(
|
||||||
}
|
}
|
||||||
if (action.type === "set negativity") {
|
if (action.type === "set negativity") {
|
||||||
if (!vps.verb) return vps;
|
if (!vps.verb) return vps;
|
||||||
|
const negative = action.payload === "true";
|
||||||
return {
|
return {
|
||||||
...vps,
|
...vps,
|
||||||
|
blocks: !negative ? removeHeetsDet(vps.blocks) : vps.blocks,
|
||||||
verb: {
|
verb: {
|
||||||
...vps.verb,
|
...vps.verb,
|
||||||
negative: action.payload === "true",
|
negative,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,14 +62,23 @@ export function isNounOrAdjEntry(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInflectableEntry(
|
export function isInflectableEntry(
|
||||||
e: T.Entry | T.DictionaryEntry | T.DictionaryEntryNoFVars
|
e: T.Entry | T.DictionaryEntry | T.DictionaryEntryNoFVars | T.Determiner
|
||||||
): e is T.InflectableEntry {
|
): e is T.InflectableEntry {
|
||||||
if ("entry" in e) {
|
if ("entry" in e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isDeterminer(e)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return isNounEntry(e) || isAdjectiveEntry(e) || isNumberEntry(e);
|
return isNounEntry(e) || isAdjectiveEntry(e) || isNumberEntry(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isDeterminer(
|
||||||
|
e: T.Entry | T.DictionaryEntry | T.Determiner
|
||||||
|
): e is T.Determiner {
|
||||||
|
return "type" in e && e.type === "det";
|
||||||
|
}
|
||||||
|
|
||||||
export function isNumberEntry(
|
export function isNumberEntry(
|
||||||
e: T.Entry | T.DictionaryEntry
|
e: T.Entry | T.DictionaryEntry
|
||||||
): e is T.NumberEntry {
|
): e is T.NumberEntry {
|
||||||
|
|
51
src/types.ts
51
src/types.ts
|
@ -899,13 +899,35 @@ export type NounSelection = {
|
||||||
genStativeComplement?: boolean;
|
genStativeComplement?: boolean;
|
||||||
adjectives: AdjectiveSelection[];
|
adjectives: AdjectiveSelection[];
|
||||||
possesor: undefined | PossesorSelection;
|
possesor: undefined | PossesorSelection;
|
||||||
demonstrative: undefined | DemonstrativeSelection;
|
determiners?: DeterminersSelection;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DemonstrativeSelection = {
|
export type DeterminersSelection = {
|
||||||
type: "demonstrative";
|
type: "determiners";
|
||||||
demonstrative: "daa" | "hagha" | "dagha";
|
|
||||||
withNoun: boolean;
|
withNoun: boolean;
|
||||||
|
determiners: DeterminerSelection[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const determiners = [
|
||||||
|
{ p: "دا", f: "daa", type: "det", demonstrative: true },
|
||||||
|
{ p: "دغه", f: "dágha", type: "det", demonstrative: true },
|
||||||
|
{ p: "هغه", f: "hágha", type: "det", demonstrative: true },
|
||||||
|
{ p: "کوم", f: "koom", type: "det" },
|
||||||
|
{ p: "داسې", f: "dáase", type: "det" },
|
||||||
|
{ p: "دغسې", f: "daghase", type: "det" },
|
||||||
|
{ p: "هسې", f: "hase", type: "det" },
|
||||||
|
{ p: "هغسې", f: "hagháse", type: "det" },
|
||||||
|
{ p: "هر", f: "har", type: "det" },
|
||||||
|
{ p: "ټول", f: "Tol", type: "det" },
|
||||||
|
{ p: "بل", f: "bul", type: "det" },
|
||||||
|
{ p: "هیڅ", f: "heets", type: "det", noInf: true },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type Determiner = (typeof determiners)[number];
|
||||||
|
|
||||||
|
export type DeterminerSelection = {
|
||||||
|
type: "determiner";
|
||||||
|
determiner: Determiner;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AdverbSelection = {
|
export type AdverbSelection = {
|
||||||
|
@ -970,7 +992,8 @@ export type Rendered<
|
||||||
| AdjectiveSelection
|
| AdjectiveSelection
|
||||||
| SandwichSelection<Sandwich>
|
| SandwichSelection<Sandwich>
|
||||||
| ComplementSelection
|
| ComplementSelection
|
||||||
| DemonstrativeSelection
|
| DeterminersSelection
|
||||||
|
| DeterminerSelection
|
||||||
| ComplementSelection["selection"]
|
| ComplementSelection["selection"]
|
||||||
| UnselectedComplementSelection
|
| UnselectedComplementSelection
|
||||||
| undefined
|
| undefined
|
||||||
|
@ -1020,13 +1043,21 @@ export type Rendered<
|
||||||
inflected: boolean;
|
inflected: boolean;
|
||||||
person: Person;
|
person: Person;
|
||||||
}
|
}
|
||||||
: T extends DemonstrativeSelection
|
: T extends DeterminersSelection
|
||||||
? {
|
? {
|
||||||
type: "demonstrative";
|
type: "determiners";
|
||||||
demonstrative: DemonstrativeSelection["demonstrative"];
|
|
||||||
withNoun: boolean;
|
withNoun: boolean;
|
||||||
ps: PsString;
|
determiners: Rendered<DeterminerSelection>[];
|
||||||
|
}
|
||||||
|
: T extends DeterminerSelection
|
||||||
|
? {
|
||||||
|
type: "determiner";
|
||||||
|
determiner: DeterminerSelection["determiner"];
|
||||||
|
ps: PsString[];
|
||||||
e: string;
|
e: string;
|
||||||
|
inflected: boolean;
|
||||||
|
number: NounNumber;
|
||||||
|
gender: Gender;
|
||||||
}
|
}
|
||||||
: T extends ComplementSelection
|
: T extends ComplementSelection
|
||||||
? {
|
? {
|
||||||
|
@ -1078,7 +1109,7 @@ export type Rendered<
|
||||||
shrunken: boolean;
|
shrunken: boolean;
|
||||||
np: Rendered<NPSelection>;
|
np: Rendered<NPSelection>;
|
||||||
};
|
};
|
||||||
demonstrative?: Rendered<DemonstrativeSelection>;
|
determiners?: Rendered<DeterminersSelection>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EPSelectionState = {
|
export type EPSelectionState = {
|
||||||
|
|
Loading…
Reference in New Issue