Compare commits

..

No commits in common. "26142b1ccb583d52a3b1c86c18b271645f27f8e0" and "46fd6e66e84fd1677717065862b3c33bf1b4ce8a" have entirely different histories.

19 changed files with 757 additions and 989 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "pashto-inflector", "name": "pashto-inflector",
"version": "7.3.1", "version": "7.2.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pashto-inflector", "name": "pashto-inflector",
"version": "7.3.1", "version": "7.2.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "pashto-inflector", "name": "pashto-inflector",
"version": "7.3.1", "version": "7.2.2",
"author": "lingdocs.com", "author": "lingdocs.com",
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations", "description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
"homepage": "https://verbs.lingdocs.com", "homepage": "https://verbs.lingdocs.com",

View File

@ -20,7 +20,7 @@ import Hider from "./components/src/Hider";
import InflectionDemo from "./demo-components/InflectionDemo"; import InflectionDemo from "./demo-components/InflectionDemo";
import SpellingDemo from "./demo-components/SpellingDemo"; import SpellingDemo from "./demo-components/SpellingDemo";
import ParserDemo from "./demo-components/ParserDemo"; import ParserDemo from "./demo-components/ParserDemo";
// import InflectionTable from "./components/src/InflectionsTable"; import InflectionTable from "./components/src/InflectionsTable";
function App() { function App() {
const [showingTextOptions, setShowingTextOptions] = useStickyState<boolean>( const [showingTextOptions, setShowingTextOptions] = useStickyState<boolean>(

View File

@ -1,12 +1,12 @@
{ {
"name": "@lingdocs/ps-react", "name": "@lingdocs/ps-react",
"version": "7.3.1", "version": "7.2.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lingdocs/ps-react", "name": "@lingdocs/ps-react",
"version": "7.3.1", "version": "7.2.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^1.0.0-beta.3", "@formkit/auto-animate": "^1.0.0-beta.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/ps-react", "name": "@lingdocs/ps-react",
"version": "7.3.1", "version": "7.2.2",
"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",

View File

@ -600,27 +600,6 @@ function Sandwich({
); );
} }
function Demonstrative({
opts,
script,
children,
}: {
opts: T.TextOptions;
script: "p" | "f";
children: T.Rendered<T.DemonstrativeSelection> | undefined;
}) {
if (!children) {
return null;
}
return (
<div className="text-center">
<Border padding={"1rem"}>{children.ps[script]}</Border>
<div>DEM</div>
<SubText>{children.e}</SubText>
</div>
);
}
function CompNounBlock({ function CompNounBlock({
opts, opts,
noun, noun,
@ -633,7 +612,7 @@ function CompNounBlock({
return ( return (
<div className="text-center"> <div className="text-center">
<Border <Border
// extraClassName={`${!inside && hasPossesor} ? "pt-2" : ""`} extraClassName={`!inside && hasPossesor ? "pt-2" : ""`}
padding={"1rem"} padding={"1rem"}
> >
{flattenLengths(noun.ps)[0][script]} {flattenLengths(noun.ps)[0][script]}
@ -673,9 +652,6 @@ export function NPBlock({
</Possesors>, </Possesors>,
] ]
: []), : []),
<Demonstrative opts={opts} script={script}>
{np.selection.demonstrative ? np.selection.demonstrative : undefined}
</Demonstrative>,
<Adjectives opts={opts} script={script}> <Adjectives opts={opts} script={script}>
{np.selection.adjectives} {np.selection.adjectives}
</Adjectives>, </Adjectives>,
@ -683,15 +659,12 @@ export function NPBlock({
{" "} {" "}
{flattenLengths(np.selection.ps)[0][script]} {flattenLengths(np.selection.ps)[0][script]}
</div>, </div>,
].filter((x) => { ];
// @ts-ignore
return x !== " ";
});
const el = script === "p" ? elements.reverse() : elements; const el = script === "p" ? elements.reverse() : elements;
return ( return (
<div className="text-center"> <div className="text-center">
<Border <Border
// extraClassName={`!inside && hasPossesor ? "pt-2" : ""`} extraClassName={`!inside && hasPossesor ? "pt-2" : ""`}
padding={ padding={
inside inside
? "0.3rem" ? "0.3rem"
@ -786,9 +759,6 @@ function Adjectives({
return null; return null;
} }
const c = script === "p" ? children.reverse() : children; const c = script === "p" ? children.reverse() : children;
if (c.length === 0) {
return null;
}
return ( return (
<em className="mr-1"> <em className="mr-1">
{c.map((a) => a.ps[0][script]).join(" ")} {c.map((a) => a.ps[0][script]).join(" ")}

View File

@ -1,98 +1,153 @@
import * as T from "../../../types"; import * as T from "../../../types";
import { useState } from "react"; import { useState } from "react";
import AdjectivePicker from "./AdjectivePicker"; import AdjectivePicker from "./AdjectivePicker";
import classNames from "classnames";
function AdjectiveManager(props: { function AdjectiveManager(props: {
adjectives: T.AdjectiveSelection[]; adjectives: T.AdjectiveSelection[],
entryFeeder: T.EntryFeeder; entryFeeder: T.EntryFeeder,
opts: T.TextOptions; opts: T.TextOptions,
onChange: (adjs: T.AdjectiveSelection[]) => void; demonstrative: T.NounSelection["demonstrative"],
phraseIsComplete: boolean; onChange: (adjs: T.AdjectiveSelection[]) => void,
onDemonstrativeChange: (dem: T.NounSelection["demonstrative"]) => void,
phraseIsComplete: boolean,
}) { }) {
const [adding, setAdding] = useState<boolean>(false); const [adding, setAdding] = useState<boolean>(false);
function handleChange(i: number) { const [addingDemonstrative, setAddingDemonstrative] = useState<boolean>(false);
return (a: T.AdjectiveSelection | undefined) => { function handleChange(i: number) {
if (a === undefined) return; return (a: T.AdjectiveSelection | undefined) => {
const updated = [...props.adjectives]; if (a === undefined) return;
updated[i] = a; const updated = [...props.adjectives];
props.onChange(updated.filter((x): x is T.AdjectiveSelection => !!x)); updated[i] = a;
}; props.onChange(
} updated.filter((x): x is T.AdjectiveSelection => !!x)
function deleteAdj(i: number) { );
return () => { };
props.onChange(remove(props.adjectives, i)); }
}; function deleteAdj(i: number) {
} return () => {
function handleAddNew(a: T.AdjectiveSelection | undefined) { props.onChange(remove(props.adjectives, i));
if (a === undefined) return; };
setAdding(false); }
props.onChange([a, ...props.adjectives]); function handleAddNew(a: T.AdjectiveSelection | undefined) {
} if (a === undefined) return;
// const flippedList = [...props.adjectives]; setAdding(false);
// flippedList.reverse(); props.onChange([
// console.log(props.adjectives); a,
return ( ...props.adjectives,
<div className="mb-1"> ]);
{adding && ( }
<div> // const flippedList = [...props.adjectives];
<div className="d-flex flex-row justify-content-between mb-1"> // flippedList.reverse();
<div>Add Adjective</div> // console.log(props.adjectives);
<div className="clickable" onClick={() => setAdding(false)}> return <div className="mb-1">
<i className="fas fa-trash" /> {adding && <div>
</div> <div className="d-flex flex-row justify-content-between mb-1">
</div> <div>Add Adjective</div>
<AdjectivePicker <div className="clickable" onClick={() => setAdding(false)}>
phraseIsComplete={props.phraseIsComplete} <i className="fas fa-trash" />
noTitle
adjective={undefined}
entryFeeder={props.entryFeeder}
opts={props.opts}
onChange={handleAddNew}
/>
</div>
)}
{props.adjectives.map((adj, i) => (
<div key={i}>
<div className="d-flex flex-row justify-content-between">
<div>Adjective</div>
<div className="d-flex flex-row align-items-baseline">
{!!props.adjectives.length && !adding && (
<div>
<div className="clickable h6" onClick={() => setAdding(true)}>
+ Adj.
</div>
</div> </div>
)}
<div onClick={deleteAdj(i)} className="ml-4">
<div className="fas fa-trash" />
</div>
</div> </div>
</div> <AdjectivePicker
<AdjectivePicker phraseIsComplete={props.phraseIsComplete}
phraseIsComplete={props.phraseIsComplete} noTitle
noTitle adjective={undefined}
key={`adj${i}`} entryFeeder={props.entryFeeder}
adjective={adj} opts={props.opts}
entryFeeder={props.entryFeeder} onChange={handleAddNew}
opts={props.opts} />
onChange={handleChange(i)} </div>}
/> {addingDemonstrative && <div>
<div className="d-flex flex-row justify-content-between mb-1">
<div>Add Demonstrative</div>
<div className="clickable" onClick={() => {
setAddingDemonstrative(false);
props.onDemonstrativeChange(undefined);
}}>
<i className="fas fa-trash" />
</div>
</div>
<DemonstrativePicker
demonstrative={props.demonstrative}
onChange={props.onDemonstrativeChange}
/>
</div>}
{props.adjectives.map((adj, i) => <div key={i}>
<div className="d-flex flex-row justify-content-between">
<div>Adjective</div>
<div className="d-flex flex-row align-items-baseline">
{!!props.adjectives.length && !adding && <div>
<div className="clickable h6" onClick={() => setAdding(true)}>+ Adj.</div>
</div>}
<div onClick={deleteAdj(i)} className="ml-4">
<div className="fas fa-trash" />
</div>
</div>
</div>
<AdjectivePicker
phraseIsComplete={props.phraseIsComplete}
noTitle
key={`adj${i}`}
adjective={adj}
entryFeeder={props.entryFeeder}
opts={props.opts}
onChange={handleChange(i)}
/>
</div>)}
{!adding && !props.adjectives.length && <div className="h6 clickable" style={{ float: "right" }}>
<div className="clickable" onClick={() => setAdding(true)}>+ Adj.</div>
</div>}
{/* {!addingDemonstrative && !props.demonstrative && <h6 className="clickable mr-2" style={{ float: "right" }}>
<div className="clickable" onClick={() => setAddingDemonstrative(true)}>+ Demons.</div>
</h6>} */}
</div>;
}
function DemonstrativePicker({ demonstrative, onChange }: {
demonstrative: T.NounSelection["demonstrative"],
onChange: (dem: T.NounSelection["demonstrative"]) => void,
}) {
function handleDChange(d: "daa" | "hagha" | "dagha") {
if (!demonstrative) {
onChange({
type: "demonstrative",
demonstrative: d,
hideNoun: false,
});
} else {
onChange({
...demonstrative,
demonstrative: d,
});
}
}
return <div className="d-flex flex-row justify-content-around py-1">
<div>
<button
className={classNames("btn", "btn-outline-secondary", { active: demonstrative?.demonstrative === "daa" })}
onClick={() => handleDChange("daa")}
>دا</button>
</div> </div>
))} <div>
{!adding && !props.adjectives.length && ( <button
<div className="h6 clickable" style={{ float: "right" }}> className={classNames("btn", "btn-outline-secondary", { active: demonstrative?.demonstrative === "hagha" })}
<div className="clickable" onClick={() => setAdding(true)}> onClick={() => handleDChange("hagha")}
+ Adj. >هغه</button>
</div>
</div> </div>
)} <div>
</div> <button
); className={classNames("btn", "btn-outline-secondary", { active: demonstrative?.demonstrative === "dagha" })}
onClick={() => handleDChange("dagha")}
>دغه</button>
</div>
</div>;
} }
function remove<X>(arr: X[], i: number): X[] { function remove<X>(arr: X[], i: number): X[] {
return [...arr.slice(0, i), ...arr.slice(i + 1)]; return [
...arr.slice(0, i),
...arr.slice(i + 1),
];
} }
export default AdjectiveManager; export default AdjectiveManager;

View File

@ -1,59 +0,0 @@
import * as T from "../../../types";
import classNames from "classnames";
export default function DemonstrativePicker({
demonstrative,
onChange,
}: {
demonstrative: T.NounSelection["demonstrative"];
onChange: (dem: T.NounSelection["demonstrative"]) => void;
}) {
function handleDChange(d: "daa" | "hagha" | "dagha") {
if (!demonstrative) {
onChange({
type: "demonstrative",
demonstrative: d,
hideNoun: false,
});
} else {
onChange({
...demonstrative,
demonstrative: d,
});
}
}
return (
<div className="d-flex flex-row justify-content-around py-1">
<div>
<button
className={classNames("btn", "btn-outline-secondary", {
active: demonstrative?.demonstrative === "daa",
})}
onClick={() => handleDChange("daa")}
>
دا
</button>
</div>
<div>
<button
className={classNames("btn", "btn-outline-secondary", {
active: demonstrative?.demonstrative === "dagha",
})}
onClick={() => handleDChange("dagha")}
>
دغه
</button>
</div>
<div>
<button
className={classNames("btn", "btn-outline-secondary", {
active: demonstrative?.demonstrative === "hagha",
})}
onClick={() => handleDChange("hagha")}
>
هغه
</button>
</div>
</div>
);
}

View File

@ -6,8 +6,6 @@ import InlinePs from "../InlinePs";
// import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates"; // import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates";
import EntrySelect from "../EntrySelect"; import EntrySelect from "../EntrySelect";
import AdjectiveManager from "./AdjectiveManager"; import AdjectiveManager from "./AdjectiveManager";
import { useState } from "react";
import DemonstrativePicker from "./DemonstrativePicker";
// const filterOptions = [ // const filterOptions = [
// { // {
@ -63,8 +61,6 @@ function NPNounPicker(props: {
opts: T.TextOptions; opts: T.TextOptions;
phraseIsComplete: boolean; phraseIsComplete: boolean;
}) { }) {
const [addingDemonstrative, setAddingDemonstrative] =
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,7 +84,7 @@ function NPNounPicker(props: {
}); });
} }
} }
function handleDemonstrativeChange( function handleDemonstrativeUpdate(
demonstrative: undefined | T.NounSelection["demonstrative"] demonstrative: undefined | T.NounSelection["demonstrative"]
) { ) {
if (props.noun) { if (props.noun) {
@ -100,36 +96,6 @@ function NPNounPicker(props: {
} }
return ( return (
<div style={{ maxWidth: "225px", minWidth: "125px" }}> <div style={{ maxWidth: "225px", minWidth: "125px" }}>
{!addingDemonstrative && !props.noun?.demonstrative ? (
<div>
<span
className="clickable text-muted"
onClick={() => setAddingDemonstrative(true)}
>
+ Demonstrative
</span>
</div>
) : (
<div>
<div className="d-flex flex-row justify-content-between mb-1">
<div>{!props.noun?.demonstrative ? "Add" : ""} Demonstrative</div>
<div
className="clickable"
onClick={() => {
handleDemonstrativeChange(undefined);
setAddingDemonstrative(false);
}}
>
<i className="fas fa-trash" />
</div>
</div>
<DemonstrativePicker
demonstrative={props.noun?.demonstrative}
onChange={handleDemonstrativeChange}
/>
</div>
)}
{/* {showFilter && <div className="mb-2 text-center"> {/* {showFilter && <div className="mb-2 text-center">
<div className="d-flex flex-row justify-content-between"> <div className="d-flex flex-row justify-content-between">
<div className="text-small mb-1">Filter by inflection pattern</div> <div className="text-small mb-1">Filter by inflection pattern</div>
@ -147,9 +113,11 @@ function NPNounPicker(props: {
<AdjectiveManager <AdjectiveManager
phraseIsComplete={props.phraseIsComplete} phraseIsComplete={props.phraseIsComplete}
adjectives={props.noun?.adjectives} adjectives={props.noun?.adjectives}
demonstrative={props.noun.demonstrative}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
opts={props.opts} opts={props.opts}
onChange={handelAdjectivesUpdate} onChange={handelAdjectivesUpdate}
onDemonstrativeChange={handleDemonstrativeUpdate}
/> />
)} )}
<div className="h6">Noun</div> <div className="h6">Noun</div>

View File

@ -1,371 +1,272 @@
import PronounPicker from "./NPPronounPicker"; import PronounPicker from "./NPPronounPicker";
import NounPicker from "./NPNounPicker"; import NounPicker from "./NPNounPicker";
import ParticiplePicker from "./NPParticiplePicker"; import ParticiplePicker from "./NPParticiplePicker";
import { randomPerson } from "../../../lib/src/np-tools"; import {
randomPerson,
} from "../../../lib/src/np-tools";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import * as T from "../../../types"; import * as T from "../../../types";
import { isSecondPerson } from "../../../lib/src/misc-helpers"; import { isSecondPerson } from "../../../lib/src/misc-helpers";
const npTypes: T.NPType[] = ["pronoun", "noun", "participle"]; const npTypes: T.NPType[] = ["pronoun", "noun", "participle"];
// TODO: BUG WITH PICKING IMPERATIVE FORMS OUTSIDE OF SECOND PERSON!
export const shrunkenBackground = "rgba(255, 206, 43, 0.15)"; export const shrunkenBackground = "rgba(255, 206, 43, 0.15)";
function NPPicker(props: { function NPPicker(props: {
heading?: JSX.Element | string; heading?: JSX.Element | string,
onChange: (nps: T.NPSelection | undefined) => void; onChange: (nps: T.NPSelection | undefined) => void,
np: T.NPSelection | undefined; np: T.NPSelection | undefined,
counterPart: T.NPSelection | T.VerbObject | undefined; counterPart: T.NPSelection | T.VerbObject | undefined,
role: "subject" | "object" | "ergative" | "possesor"; role: "subject" | "object" | "ergative" | "possesor",
opts: T.TextOptions; opts: T.TextOptions,
cantClear?: boolean; cantClear?: boolean,
is2ndPersonPicker?: boolean; is2ndPersonPicker?: boolean,
entryFeeder: T.EntryFeeder; entryFeeder: T.EntryFeeder,
phraseIsComplete: boolean; phraseIsComplete: boolean,
isShrunk?: boolean; isShrunk?: boolean,
isRemoved?: boolean; isRemoved?: boolean,
}) { }) {
if ( if (props.is2ndPersonPicker && ((props.np?.selection.type !== "pronoun") || !isSecondPerson(props.np.selection.person))) {
props.is2ndPersonPicker && throw new Error("can't use 2ndPerson NPPicker without a pronoun");
(props.np?.selection.type !== "pronoun" ||
!isSecondPerson(props.np.selection.person))
) {
throw new Error("can't use 2ndPerson NPPicker without a pronoun");
}
const [addingPoss, setAddingPoss] = useState<boolean>(false);
const [npType, setNpType] = useState<T.NPType | undefined>(
props.np ? props.np.selection.type : undefined
);
const onChange = (np: T.NPSelection | undefined) => {
props.onChange(ensureSingleShrink(props.np, np));
if (
(np?.selection.type === "noun" || np?.selection.type === "participle") &&
np.selection.possesor
) {
setAddingPoss(true);
} }
setAddingPoss(false); const [addingPoss, setAddingPoss] = useState<boolean>(false);
}; const [npType, setNpType] = useState<T.NPType | undefined>(props.np ? props.np.selection.type : undefined);
useEffect(() => { const onChange = (np: T.NPSelection | undefined) => {
setNpType(props.np ? props.np.selection.type : undefined); props.onChange(ensureSingleShrink(props.np, np));
}, [props.np]); if ((np?.selection.type === "noun" || np?.selection.type === "participle") && np.selection.possesor) {
function handleClear() { setAddingPoss(true);
if ( }
props.np && setAddingPoss(false);
props.np.selection.type === "noun" && }
props.np.selection.dynamicComplement useEffect(() => {
) setNpType(props.np ? props.np.selection.type : undefined);
return; }, [props.np]);
setNpType(undefined); function handleClear() {
onChange(undefined); if (props.np && props.np.selection.type === "noun" && props.np.selection.dynamicComplement) return;
} setNpType(undefined);
function handleNPTypeChange(ntp: T.NPType) {
if (ntp === "pronoun") {
const person = randomPerson({ counterPart: props.counterPart });
const pronoun: T.PronounSelection = {
type: "pronoun",
person,
distance: "far",
};
setNpType(ntp);
onChange({ type: "NP", selection: pronoun });
} else {
if (props.np) {
onChange(undefined); onChange(undefined);
}
setNpType(ntp);
} }
} function handleNPTypeChange(ntp: T.NPType) {
// TODO: REMOVE if (ntp === "pronoun") {
function handlePossesiveChange(p: T.NPSelection | undefined) { const person = randomPerson({ counterPart: props.counterPart });
if (!props.np || props.np.selection.type === "pronoun") return; const pronoun: T.PronounSelection = {
if (!p) { type: "pronoun",
onChange({ person,
type: "NP", distance: "far",
selection: { };
...props.np.selection, setNpType(ntp);
possesor: undefined, onChange({ type: "NP", selection: pronoun });
}, } else {
}); if (props.np) {
return; onChange(undefined);
}
setNpType(ntp);
}
} }
const isNewPosesser = checkForNewPossesor(p, props.np.selection.possesor); // TODO: REMOVE
const possesor: T.PossesorSelection = { function handlePossesiveChange(p: T.NPSelection | undefined) {
np: p, if (!props.np || props.np.selection.type === "pronoun") return;
shrunken: if (!p) {
!isNewPosesser && props.np.selection.possesor onChange({
? props.np.selection.possesor.shrunken type: "NP",
: false, selection: {
}; ...props.np.selection,
onChange({ possesor: undefined,
type: "NP", },
selection: { });
...props.np.selection, return;
possesor, }
}, const isNewPosesser = checkForNewPossesor(p, props.np.selection.possesor);
}); const possesor: T.PossesorSelection = {
} np: p,
function handleToggleShrunken() { shrunken: (!isNewPosesser && props.np.selection.possesor) ? props.np.selection.possesor.shrunken : false,
if ( };
!props.np || onChange({
props.np.selection.type === "pronoun" || type: "NP",
!props.np.selection.possesor || selection: {
!props.phraseIsComplete ...props.np.selection,
) possesor,
return; },
onChange({ });
type: "NP", }
selection: { function handleToggleShrunken() {
...props.np.selection, if (!props.np || props.np.selection.type === "pronoun" || !props.np.selection.possesor || !props.phraseIsComplete) return;
possesor: { onChange({
...props.np.selection.possesor, type: "NP",
shrunken: !props.np.selection.possesor.shrunken, selection: {
}, ...props.np.selection,
}, possesor: {
}); ...props.np.selection.possesor,
} shrunken: !props.np.selection.possesor.shrunken,
const isDynamicComplement = },
props.np && },
props.np.selection.type === "noun" && });
props.np.selection.dynamicComplement; }
const clearButton = const isDynamicComplement = props.np && props.np.selection.type === "noun" && props.np.selection.dynamicComplement;
!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement ? ( const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement)
<button className="btn btn-sm btn-light mb-2" onClick={handleClear}> ? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
X : <div></div>;
</button> const possesiveLabel = props.np?.selection.type === "participle" ? "Subj/Obj" : "Possesor";
) : ( return <div style={{
<div></div>
);
const possesiveLabel =
props.np?.selection.type === "participle" ? "Subj/Obj" : "Possesor";
return (
<div
style={{
opacity: props.isRemoved ? 0.5 : 1, opacity: props.isRemoved ? 0.5 : 1,
}} }}>
> <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}
) : ( </div>
props.heading <div>
)} {npType && clearButton}
</div>
</div> </div>
<div>{npType && clearButton}</div> <div style={{ minWidth: "9rem" }}>
</div> {!npType && <div className="text-center">
<div style={{ minWidth: "9rem" }}> <div className="h6 mr-3">
{!npType && ( Choose NP
<div className="text-center"> </div>
<div className="h6 mr-3">Choose NP</div> {npTypes.map((npt) => <div key={npt} className="mb-2">
{npTypes.map((npt) => ( <button
<div key={npt} className="mb-2"> key={npt}
<button type="button"
key={npt} className="mr-2 btn btn-sm btn-outline-secondary"
type="button" onClick={() => handleNPTypeChange(npt)}
className="mr-2 btn btn-sm btn-outline-secondary" >
onClick={() => handleNPTypeChange(npt)} {npt}
> </button>
{npt} </div>)}
</button> </div>}
</div> {(props.np && props.np.selection.type !== "pronoun" && (props.np.selection.possesor || addingPoss)) && <div className="mb-3" style={{
))}
</div>
)}
{props.np &&
props.np.selection.type !== "pronoun" &&
(props.np.selection.possesor || addingPoss) && (
<div
className="mb-3"
style={{
paddingLeft: "0.65rem", paddingLeft: "0.65rem",
borderLeft: "2px solid grey", borderLeft: "2px solid grey",
background: background: (props.np.selection.possesor?.shrunken && !props.isShrunk) ? shrunkenBackground : "inherit",
props.np.selection.possesor?.shrunken && !props.isShrunk }}>
? shrunkenBackground <div className="d-flex flex-row text-muted mb-2">
: "inherit", <div>{possesiveLabel}:</div>
}} {(props.np.selection.possesor && !props.isShrunk && props.phraseIsComplete) && <div className="clickable ml-3 mr-2" onClick={handleToggleShrunken}>
> {!props.np.selection.possesor.shrunken ? "🪄" : "👶"}
<div className="d-flex flex-row text-muted mb-2"> </div>}
<div>{possesiveLabel}:</div> <div className="clickable ml-2" onClick={() => {
{props.np.selection.possesor && setAddingPoss(false);
!props.isShrunk && handlePossesiveChange(undefined);
props.phraseIsComplete && ( }}>
<div <i className="fas fa-trash" />
className="clickable ml-3 mr-2"
onClick={handleToggleShrunken}
>
{!props.np.selection.possesor.shrunken ? "🪄" : "👶"}
</div> </div>
)}
<div
className="clickable ml-2"
onClick={() => {
setAddingPoss(false);
handlePossesiveChange(undefined);
}}
>
<i className="fas fa-trash" />
</div> </div>
</div> <NPPicker
<NPPicker phraseIsComplete={props.phraseIsComplete}
phraseIsComplete={props.phraseIsComplete} onChange={handlePossesiveChange}
onChange={handlePossesiveChange} counterPart={undefined}
counterPart={undefined} cantClear
cantClear np={props.np.selection.possesor ? props.np.selection.possesor.np : undefined}
np={ role="possesor"
props.np.selection.possesor opts={props.opts}
? props.np.selection.possesor.np entryFeeder={props.entryFeeder}
: undefined />
} </div>}
role="possesor" {(npType === "noun" || npType === "participle") && props.np && !addingPoss && <div>
opts={props.opts} <span className="clickable text-muted" onClick={() => setAddingPoss(true)}>+ {possesiveLabel}</span>
entryFeeder={props.entryFeeder} </div>}
/> {(npType === "pronoun" && props.np?.selection.type === "pronoun")
</div> ? <PronounPicker
)} role={props.role}
{(npType === "noun" || npType === "participle") && pronoun={props.np.selection}
props.np && onChange={(p) => onChange({ type: "NP", selection: p })}
!addingPoss && ( is2ndPersonPicker={props.is2ndPersonPicker}
<div> opts={props.opts}
<span />
className="clickable text-muted" : npType === "noun"
onClick={() => setAddingPoss(true)} ? <NounPicker
> phraseIsComplete={props.phraseIsComplete}
+ {possesiveLabel} entryFeeder={props.entryFeeder}
</span> noun={(props.np && props.np.selection.type === "noun") ? props.np.selection : undefined}
</div> onChange={(s) => onChange(s ? { type: "NP", selection: s } : undefined)}
)} opts={props.opts}
{npType === "pronoun" && props.np?.selection.type === "pronoun" ? ( />
<PronounPicker : npType === "participle"
role={props.role} ? <ParticiplePicker
pronoun={props.np.selection} entryFeeder={props.entryFeeder.verbs}
onChange={(p) => onChange({ type: "NP", selection: p })} participle={(props.np && props.np.selection.type === "participle") ? props.np.selection : undefined}
is2ndPersonPicker={props.is2ndPersonPicker} onChange={(s) => onChange(s ? { type: "NP", selection: s } : undefined)}
opts={props.opts} opts={props.opts}
/> />
) : npType === "noun" ? ( : null
<NounPicker
phraseIsComplete={props.phraseIsComplete}
entryFeeder={props.entryFeeder}
noun={
props.np && props.np.selection.type === "noun"
? props.np.selection
: undefined
} }
onChange={(s) => </div>
onChange(s ? { type: "NP", selection: s } : undefined) </div>;
}
opts={props.opts}
/>
) : npType === "participle" ? (
<ParticiplePicker
entryFeeder={props.entryFeeder.verbs}
participle={
props.np && props.np.selection.type === "participle"
? props.np.selection
: undefined
}
onChange={(s) =>
onChange(s ? { type: "NP", selection: s } : undefined)
}
opts={props.opts}
/>
) : null}
</div>
</div>
);
} }
function ensureSingleShrink( function ensureSingleShrink(old: T.NPSelection | undefined, s: T.NPSelection | undefined): T.NPSelection | undefined {
old: T.NPSelection | undefined, if (!s) return s;
s: T.NPSelection | undefined function countShrinks(np: T.NPSelection): number {
): T.NPSelection | undefined { if (np.selection.type === "pronoun") return 0;
if (!s) return s; if (!np.selection.possesor) return 0;
function countShrinks(np: T.NPSelection): number { return (np.selection.possesor.shrunken ? 1 : 0) + countShrinks(np.selection.possesor.np);
if (np.selection.type === "pronoun") return 0;
if (!np.selection.possesor) return 0;
return (
(np.selection.possesor.shrunken ? 1 : 0) +
countShrinks(np.selection.possesor.np)
);
}
function keepNewShrink(old: T.NPSelection, n: T.NPSelection): T.NPSelection {
if (n.selection.type === "pronoun") return n;
if (
old.selection.type === "pronoun" ||
!n.selection.possesor ||
!old.selection.possesor
)
return n;
if (n.selection.possesor.shrunken && !old.selection.possesor.shrunken) {
return {
type: "NP",
selection: {
...n.selection,
possesor: {
...n.selection.possesor,
np: removeShrinks(n.selection.possesor.np),
},
},
};
} }
return { function keepNewShrink(old: T.NPSelection, n: T.NPSelection): T.NPSelection {
type: "NP", if (n.selection.type === "pronoun") return n;
selection: { if (old.selection.type === "pronoun" || !n.selection.possesor || !old.selection.possesor) return n;
...n.selection, if (n.selection.possesor.shrunken && !old.selection.possesor.shrunken) {
possesor: { return {
shrunken: false, type: "NP",
np: keepNewShrink(old.selection.possesor.np, n.selection.possesor.np), selection: {
}, ...n.selection,
}, possesor: {
}; ...n.selection.possesor,
} np: removeShrinks(n.selection.possesor.np),
function removeShrinks(n: T.NPSelection): T.NPSelection { },
if (n.selection.type === "pronoun") return n; },
if (!n.selection.possesor) return n; };
return { }
type: "NP", return {
selection: { type: "NP",
...n.selection, selection:{
possesor: { ...n.selection,
shrunken: false, possesor: {
np: removeShrinks(n.selection.possesor.np), shrunken: false,
}, np: keepNewShrink(old.selection.possesor.np, n.selection.possesor.np),
}, },
}; },
} };
if (!old) return s; }
if (s.selection.type === "pronoun") return s; function removeShrinks(n: T.NPSelection): T.NPSelection {
if (!s.selection.possesor) return s; if (n.selection.type === "pronoun") return n;
const numOfShrinks = countShrinks(s); if (!n.selection.possesor) return n;
if (numOfShrinks < 2) return s; return {
return keepNewShrink(old, s); type: "NP",
selection: {
...n.selection,
possesor: {
shrunken: false,
np: removeShrinks(n.selection.possesor.np),
},
},
};
}
if (!old) return s;
if (s.selection.type === "pronoun") return s;
if (!s.selection.possesor) return s;
const numOfShrinks = countShrinks(s);
if (numOfShrinks < 2) return s;
return keepNewShrink(old, s);
} }
function checkForNewPossesor( function checkForNewPossesor(n: T.NPSelection | undefined, old: T.PossesorSelection | undefined): boolean {
n: T.NPSelection | undefined, if (!old || !n) {
old: T.PossesorSelection | undefined return true;
): boolean { }
if (!old || !n) { if (n.type !== old.np.type) {
return true; return true;
} }
if (n.type !== old.np.type) { if (n.selection.type === "pronoun") return false;
return true; if (n.selection.type === "noun" && old.np.selection.type === "noun") {
} return n.selection.entry.ts !== old.np.selection.entry.ts;
if (n.selection.type === "pronoun") return false; }
if (n.selection.type === "noun" && old.np.selection.type === "noun") { if (n.selection.type === "participle" && old.np.selection.type === "participle") {
return n.selection.entry.ts !== old.np.selection.entry.ts; return n.selection.verb.entry.ts !== old.np.selection.verb.entry.ts;
} }
if ( return false;
n.selection.type === "participle" &&
old.np.selection.type === "participle"
) {
return n.selection.verb.entry.ts !== old.np.selection.verb.entry.ts;
}
return false;
} }
export default NPPicker; export default NPPicker;

View File

@ -81,6 +81,7 @@ function InflectionDemo({ opts }: { opts: T.TextOptions }) {
const v = e.target.value; const v = e.target.value;
const value = v === "all" ? v : (Number(v) as T.InflectionPattern); const value = v === "all" ? v : (Number(v) as T.InflectionPattern);
setPattern(value); setPattern(value);
console.log({ word });
if (word && !tp.isPattern(value)(word)) { if (word && !tp.isPattern(value)(word)) {
setWord(undefined); setWord(undefined);
} }

View File

@ -1,16 +1,17 @@
import React from "react"; import React from 'react';
import ReactDOM from "react-dom"; import ReactDOM from 'react-dom';
import "./index.css"; import './index.css';
import App from "./App"; import App from './App';
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from './reportWebVitals';
import "@fortawesome/fontawesome-free/css/all.css"; import "@fortawesome/fontawesome-free/css/all.css";
import "bootstrap/dist/css/bootstrap.css"; import 'bootstrap/dist/css/bootstrap.css';
import "./App.css"; import './App.css';
ReactDOM.render( ReactDOM.render(
// <React.StrictMode> <React.StrictMode>
<App />, // </React.StrictMode>, <App />
document.getElementById("root") </React.StrictMode>,
document.getElementById('root')
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/inflect", "name": "@lingdocs/inflect",
"version": "7.3.1", "version": "7.2.2",
"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",

View File

@ -26,7 +26,7 @@ import {
isNounEntry, isNounEntry,
isNumberEntry, isNumberEntry,
} from "./type-predicates"; } from "./type-predicates";
import { semigroupPsString } from "../src/fp-ps"; import { semigroupPsString } from "../dist/lib/src/fp-ps";
const concatPs = semigroupPsString.concat; const concatPs = semigroupPsString.concat;

View File

@ -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, demonstrative: undefined,
}; };
} }

View File

@ -12,26 +12,19 @@ function getBaseAndAdjectives({
return getSandwichPsBaseAndAdjectives(selection); return getSandwichPsBaseAndAdjectives(selection);
} }
const adjs = "adjectives" in selection && selection.adjectives; const adjs = "adjectives" in selection && selection.adjectives;
const demons = ("demonstrative" in selection &&
selection.demonstrative?.ps) || { p: "", f: "" };
if (!adjs) { if (!adjs) {
// TODO: does this ever get used?? return flattenLengths(selection.ps);
return flattenLengths(selection.ps).map((x) => concatPsString(demons, x));
} }
return flattenLengths(selection.ps).map((p) => return flattenLengths(selection.ps).map((p) =>
concatPsString( concatPsString(
demons,
// demons ? " " : "",
adjs.reduce( adjs.reduce(
(accum, curr) => { (accum, curr) =>
// TODO: with variations of adjs? { // TODO: with variations of adjs?
return concatPsString( concatPsString(
accum, accum,
accum.p === "" && accum.f === "" ? "" : "", //" ", accum.p === "" && accum.f === "" ? "" : " ",
curr.ps[0] curr.ps[0]
); ),
},
{ p: "", f: "" } { p: "", f: "" }
), ),
" ", " ",
@ -206,10 +199,7 @@ function addArticlesAndAdjs(
? " (f.)" ? " (f.)"
: " (m.)" : " (m.)"
: ""; : "";
const demonstrative = np.demonstrative ? ` ${np.demonstrative.e}` : ""; return `${articles}${adjs}${word}${genderTag}`;
return `${
np.demonstrative ? "" : articles
}${demonstrative}${adjs}${word}${genderTag}`;
} catch (e) { } catch (e) {
return undefined; return undefined;
} }

View File

@ -131,61 +131,34 @@ export function renderNounSelection(
ps: pashto, ps: pashto,
e: english, e: english,
possesor: renderPossesor(n.possesor, role), possesor: renderPossesor(n.possesor, role),
demonstrative: renderDemonstrative({ demonstrative: renderDemonstrative(
demonstrative: n.demonstrative, n.demonstrative,
inflected, inflected && n.number === "plural"
plural: n.number === "plural", ),
gender: n.gender,
}),
}; };
} }
function renderDemonstrative({ function renderDemonstrative(
demonstrative, demonstrative: T.DemonstrativeSelection | undefined,
inflected, plurInflected: boolean
plural, ): T.Rendered<T.DemonstrativeSelection> | undefined {
gender,
}: {
demonstrative: T.DemonstrativeSelection | undefined;
inflected: boolean;
plural: boolean;
gender: T.Gender;
}): T.Rendered<T.DemonstrativeSelection> | undefined {
if (!demonstrative) { if (!demonstrative) {
return undefined; return undefined;
} }
const ps =
demonstrative.demonstrative === "daa"
? inflected
? { p: "دې", f: "de" }
: { p: "دا", f: "daa" }
: demonstrative.demonstrative === "dagha"
? inflected
? plural
? { p: "دغو", f: "dágho" }
: gender === "masc"
? { p: "دغه", f: "dághu" }
: { p: "دغې", f: "dághe" }
: { p: "دغه", f: "dágha" }
: inflected
? plural
? { p: "هغو", f: "hágho" }
: gender === "masc"
? { p: "هغه", f: "hághu" }
: { p: "هغې", f: "hághe" }
: { p: "هغه", f: "hágha" };
const e =
demonstrative.demonstrative === "hagha"
? plural
? "those"
: "that"
: plural
? "these"
: "this";
return { return {
...demonstrative, ...demonstrative,
ps, ps:
e, demonstrative.demonstrative === "daa"
? plurInflected
? { p: "دې", f: "de" }
: { p: "دا", f: "daa" }
: demonstrative.demonstrative === "dagha"
? plurInflected
? { p: "دغه", f: "dágha" }
: { p: "دغو", f: "dágho" }
: plurInflected
? { p: "هغه", f: "hágha" }
: { p: "هغو", f: "hágho" },
}; };
} }

View File

@ -1,364 +1,333 @@
import * as T from "../../../types"; import * as T from "../../../types";
import { isInvalidSubjObjCombo } from "./vp-tools"; import {
isInvalidSubjObjCombo,
} from "./vp-tools";
import { switchSubjObj } from "./vp-tools"; import { switchSubjObj } from "./vp-tools";
import { ensure2ndPersSubjPronounAndNoConflict } from "./vp-tools"; import { ensure2ndPersSubjPronounAndNoConflict } from "./vp-tools";
import { isPerfectTense, isImperativeTense } from "../type-predicates"; import {
isPerfectTense,
isImperativeTense,
} from "../type-predicates";
import { checkForMiniPronounsError } from "./compile"; import { checkForMiniPronounsError } from "./compile";
import { import {
adjustObjectSelection, adjustObjectSelection,
adjustSubjectSelection, adjustSubjectSelection,
getObjectSelection, getObjectSelection,
getSubjectSelection, getSubjectSelection,
insertNewAP, insertNewAP,
removeAP, removeAP,
setAP, setAP,
shiftBlock, shiftBlock,
} from "./blocks-utils"; } from "./blocks-utils";
import { import { changeStatDyn, changeTransitivity, makeVPSelectionState } from "./verb-selection";
changeStatDyn,
changeTransitivity,
makeVPSelectionState,
} from "./verb-selection";
export type VpsReducerAction = export type VpsReducerAction = {
| { type: "load vps",
type: "load vps"; payload: T.VPSelectionState,
payload: T.VPSelectionState; } | {
} type: "set subject",
| { payload: {
type: "set subject"; subject: T.NPSelection | undefined,
payload: { skipPronounConflictCheck?: boolean,
subject: T.NPSelection | undefined; },
skipPronounConflictCheck?: boolean; } | {
}; type: "set object",
} payload: T.NPSelection | undefined,
| { } | {
type: "set object"; type: "swap subj/obj",
payload: T.NPSelection | undefined; } | {
} type: "set form",
| { payload: T.FormVersion,
type: "swap subj/obj"; } | {
} type: "set voice",
| { payload: "active" | "passive",
type: "set form"; } | {
payload: T.FormVersion; type: "set transitivity",
} payload: "transitive" | "grammatically transitive",
| { } | {
type: "set voice"; type: "set statDyn",
payload: "active" | "passive"; payload: "stative" | "dynamic",
} } | {
| { type: "set negativity",
type: "set transitivity"; payload: "true" | "false",
payload: "transitive" | "grammatically transitive"; } | {
} type: "set tense",
| { payload: T.VerbTense | T.PerfectTense | T.ImperativeTense | undefined,
type: "set statDyn"; } | {
payload: "stative" | "dynamic"; type: "set tense category",
} payload: "basic" | "modal" | "perfect" | "imperative",
| { } | {
type: "set negativity"; type: "toggle servant shrink",
payload: "true" | "false"; } | {
} type: "toggle king remove",
| { } |{
type: "set tense"; type: "set verb",
payload: T.VerbTense | T.PerfectTense | T.ImperativeTense | undefined; payload: T.VerbEntry,
} } | {
| { type: "insert new AP",
type: "set tense category"; } | {
payload: "basic" | "modal" | "perfect" | "imperative"; type: "set AP",
} payload: {
| { index: number,
type: "toggle servant shrink"; AP: T.APSelection | undefined,
} },
| { } | {
type: "toggle king remove"; type: "remove AP",
} payload: number,
| { } | {
type: "set verb"; type: "shift block",
payload: T.VerbEntry; payload: {
} index: number,
| { direction: "back" | "forward",
type: "insert new AP"; },
} } | {
| { type: "set externalComplement",
type: "set AP"; payload: T.ComplementSelection | undefined,
payload: {
index: number;
AP: T.APSelection | undefined;
};
}
| {
type: "remove AP";
payload: number;
}
| {
type: "shift block";
payload: {
index: number;
direction: "back" | "forward";
};
}
| {
type: "set externalComplement";
payload: T.ComplementSelection | undefined;
};
export function vpsReducer(
vps: T.VPSelectionState,
action: VpsReducerAction,
sendAlert?: (msg: string) => void
): T.VPSelectionState {
function doReduce(): T.VPSelectionState {
if (action.type === "load vps") {
return action.payload;
}
if (action.type === "set subject") {
const { subject, skipPronounConflictCheck } = action.payload;
const object = getObjectSelection(vps.blocks).selection;
if (!skipPronounConflictCheck && hasPronounConflict(subject, object)) {
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
blocks: adjustSubjectSelection(vps.blocks, action.payload.subject),
};
}
if (action.type === "set object") {
if (!vps.verb) return vps;
const objectB = getObjectSelection(vps.blocks).selection;
const subjectB = getSubjectSelection(vps.blocks).selection;
if (objectB === "none" || typeof objectB === "number") {
return vps;
}
const object = action.payload;
// check for pronoun conflict
if (hasPronounConflict(subjectB, object)) {
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
blocks: adjustObjectSelection(vps.blocks, object),
};
}
if (action.type === "swap subj/obj") {
if (vps.verb?.isCompound === "dynamic") return vps;
return switchSubjObj(vps);
}
if (action.type === "set form") {
return {
...vps,
form: action.payload,
};
}
if (action.type === "set voice") {
if (vps.verb && vps.verb.canChangeVoice) {
const subject = getSubjectSelection(vps.blocks).selection;
const object = getObjectSelection(vps.blocks).selection;
const voice = action.payload;
if (voice === "passive" && vps.verb.tenseCategory === "imperative") {
return vps;
}
if (voice === "passive") {
return {
...vps,
blocks: adjustObjectSelection(
adjustSubjectSelection(
vps.blocks,
typeof object === "object" ? object : undefined
),
"none"
),
verb: {
...vps.verb,
voice,
// tenseCategory: vps.verb.tenseCategory === "modal" ? "basic" : vps.verb.tenseCategory,
},
};
} else {
return {
...vps,
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, undefined),
typeof subject === "object" ? subject : undefined
),
verb: {
...vps.verb,
voice,
},
};
}
} else {
return vps;
}
}
if (action.type === "set transitivity") {
if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps;
return changeTransitivity(vps, action.payload);
}
if (action.type === "set statDyn") {
if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps;
return changeStatDyn(vps, action.payload);
}
if (action.type === "set negativity") {
if (!vps.verb) return vps;
return {
...vps,
verb: {
...vps.verb,
negative: action.payload === "true",
},
};
}
if (action.type === "set tense") {
const tense = action.payload;
if (!(vps.verb && tense)) return vps;
if (isPerfectTense(tense)) {
return {
...vps,
verb: {
...vps.verb,
perfectTense: tense,
tenseCategory: "perfect",
},
};
} else if (isImperativeTense(tense)) {
return {
...vps,
verb: {
...vps.verb,
imperativeTense: tense,
tenseCategory: "imperative",
},
};
} else {
return {
...vps,
verb: {
...vps.verb,
verbTense: tense,
tenseCategory:
vps.verb.tenseCategory === "perfect"
? "basic"
: vps.verb.tenseCategory,
},
};
}
}
if (action.type === "set tense category") {
if (!vps.verb) return vps;
const category = action.payload;
if (category === "imperative") {
return ensure2ndPersSubjPronounAndNoConflict({
...vps,
verb: {
...vps.verb,
voice: "active",
tenseCategory: category,
},
});
}
if (category === "modal") {
return {
...vps,
verb: {
...vps.verb,
tenseCategory: category,
},
};
}
return {
...vps,
verb: {
...vps.verb,
tenseCategory: category,
},
};
}
if (action.type === "toggle servant shrink") {
return {
...vps,
form: {
...vps.form,
shrinkServant: !vps.form.shrinkServant,
},
};
}
if (action.type === "toggle king remove") {
return {
...vps,
form: {
...vps.form,
removeKing: !vps.form.removeKing,
},
};
}
if (action.type === "set verb") {
return makeVPSelectionState(action.payload, vps);
}
if (action.type === "insert new AP") {
return {
...vps,
blocks: insertNewAP(vps.blocks),
};
}
if (action.type === "set AP") {
const { index, AP } = action.payload;
return {
...vps,
blocks: setAP(vps.blocks, index, AP),
};
}
if (action.type === "remove AP") {
return {
...vps,
blocks: removeAP(vps.blocks, action.payload),
};
}
if (action.type === "shift block") {
const { index, direction } = action.payload;
return {
...vps,
blocks: shiftBlock(vps.blocks, index, direction),
};
}
if (action.type === "set externalComplement") {
const selection = action.payload;
return {
...vps,
externalComplement:
selection === undefined
? // TODO: this is a bit messy
// when using the ComplementPicker with an EP - undefined means it hasn't been selected
// when using the ComplementPicker with a VP - undefined means there can be no complement
{ type: "complement", selection: { type: "unselected" } }
: selection,
};
}
throw new Error("unknown vpsReducer state");
}
const modified = doReduce();
const err = checkForMiniPronounsError(modified);
if (err) {
if (sendAlert) sendAlert(err);
return vps;
}
return modified;
} }
function hasPronounConflict( export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, sendAlert?: (msg: string) => void): T.VPSelectionState {
subject: T.NPSelection | undefined, function doReduce(): T.VPSelectionState {
object: undefined | T.VerbObject if (action.type === "load vps") {
): boolean { return action.payload;
const subjPronoun = }
subject && subject.selection.type === "pronoun" if (action.type === "set subject") {
? subject.selection const { subject, skipPronounConflictCheck } = action.payload;
: undefined; const object = getObjectSelection(vps.blocks).selection;
const objPronoun = if (
object && typeof object === "object" && object.selection.type === "pronoun" !skipPronounConflictCheck
? object.selection &&
: undefined; hasPronounConflict(subject, object)
if (!subjPronoun || !objPronoun) return false; ) {
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person); if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
blocks: adjustSubjectSelection(vps.blocks, action.payload.subject),
};
}
if (action.type === "set object") {
if (!vps.verb) return vps;
const objectB = getObjectSelection(vps.blocks).selection;
const subjectB = getSubjectSelection(vps.blocks).selection;
if ((objectB === "none") || (typeof objectB === "number")) {
return vps;
}
const object = action.payload;
// check for pronoun conflict
if (hasPronounConflict(subjectB, object)) {
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
blocks: adjustObjectSelection(vps.blocks, object),
};
}
if (action.type === "swap subj/obj") {
if (vps.verb?.isCompound === "dynamic") return vps;
return switchSubjObj(vps);
}
if (action.type === "set form") {
return {
...vps,
form: action.payload,
};
}
if (action.type === "set voice") {
if (vps.verb && vps.verb.canChangeVoice) {
const subject = getSubjectSelection(vps.blocks).selection;
const object = getObjectSelection(vps.blocks).selection;
const voice = action.payload;
if (voice === "passive" && vps.verb.tenseCategory === "imperative") {
return vps;
}
if (voice === "passive") {
return {
...vps,
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, typeof object === "object" ? object : undefined),
"none",
),
verb: {
...vps.verb,
voice,
// tenseCategory: vps.verb.tenseCategory === "modal" ? "basic" : vps.verb.tenseCategory,
},
};
} else {
return {
...vps,
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, undefined),
typeof subject === "object" ? subject : undefined,
),
verb: {
...vps.verb,
voice,
},
};
}
} else {
return vps;
}
}
if (action.type === "set transitivity") {
if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps;
return changeTransitivity(vps, action.payload);
}
if (action.type === "set statDyn") {
if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps;
return changeStatDyn(vps, action.payload);
}
if (action.type === "set negativity") {
if (!vps.verb) return vps;
return {
...vps,
verb: {
...vps.verb,
negative: action.payload === "true",
},
};
}
if (action.type === "set tense") {
const tense = action.payload;
if (!(vps.verb && tense)) return vps;
if (isPerfectTense(tense)) {
return {
...vps,
verb: {
...vps.verb,
perfectTense: tense,
tenseCategory: "perfect",
},
};
} else if (isImperativeTense(tense)) {
return {
...vps,
verb: {
...vps.verb,
imperativeTense: tense,
tenseCategory: "imperative",
},
};
} else {
return {
...vps,
verb: {
...vps.verb,
verbTense: tense,
tenseCategory: vps.verb.tenseCategory === "perfect"
? "basic"
: vps.verb.tenseCategory,
},
};
}
}
if (action.type === "set tense category") {
if (!vps.verb) return vps;
const category = action.payload;
if (category === "imperative") {
return ensure2ndPersSubjPronounAndNoConflict({
...vps,
verb: {
...vps.verb,
voice: "active",
tenseCategory: category,
},
});
}
if (category === "modal") {
return {
...vps,
verb: {
...vps.verb,
tenseCategory: category,
},
}
}
return {
...vps,
verb: {
...vps.verb,
tenseCategory: category,
},
};
}
if (action.type === "toggle servant shrink") {
return {
...vps,
form: {
...vps.form,
shrinkServant: !vps.form.shrinkServant,
},
};
}
if (action.type === "toggle king remove") {
return {
...vps,
form: {
...vps.form,
removeKing: !vps.form.removeKing,
},
};
}
if (action.type === "set verb") {
return makeVPSelectionState(action.payload, vps);
}
if (action.type === "insert new AP") {
return {
...vps,
blocks: insertNewAP(vps.blocks),
};
}
if (action.type === "set AP") {
const { index, AP } = action.payload;
return {
...vps,
blocks: setAP(vps.blocks, index, AP),
};
}
if (action.type === "remove AP") {
return {
...vps,
blocks: removeAP(vps.blocks, action.payload),
};
}
if (action.type === "shift block") {
const { index, direction } = action.payload;
return {
...vps,
blocks: shiftBlock(vps.blocks, index, direction),
};
}
if (action.type === "set externalComplement") {
const selection = action.payload;
return {
...vps,
externalComplement: selection === undefined
// TODO: this is a bit messy
// when using the ComplementPicker with an EP - undefined means it hasn't been selected
// when using the ComplementPicker with a VP - undefined means there can be no complement
? { type: "complement", selection: { type: "unselected" }}
: selection,
}
}
throw new Error("unknown vpsReducer state");
}
const modified = doReduce();
const err = checkForMiniPronounsError(modified);
if (err) {
if (sendAlert) sendAlert(err);
return vps;
}
return modified;
} }
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
const subjPronoun = (subject && subject.selection.type === "pronoun") ? subject.selection : undefined;
const objPronoun = (object && typeof object === "object" && object.selection.type === "pronoun") ? object.selection : undefined;
if (!subjPronoun || !objPronoun) return false;
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
}

View File

@ -1026,7 +1026,6 @@ export type Rendered<
demonstrative: DemonstrativeSelection["demonstrative"]; demonstrative: DemonstrativeSelection["demonstrative"];
hideNoun: boolean; hideNoun: boolean;
ps: PsString; ps: PsString;
e: string;
} }
: T extends ComplementSelection : T extends ComplementSelection
? { ? {