ability to share phrase url

This commit is contained in:
lingdocs 2022-05-03 10:23:33 -05:00
parent 24e85f9df6
commit b75ddb9689
14 changed files with 118 additions and 28 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/pashto-inflector", "name": "@lingdocs/pashto-inflector",
"version": "2.3.7", "version": "2.3.8",
"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",
@ -24,6 +24,8 @@
}, },
"dependencies": { "dependencies": {
"classnames": "^2.2.6", "classnames": "^2.2.6",
"jsurl2": "^2.1.0",
"lz-string": "^1.4.4",
"pbf": "^3.2.1", "pbf": "^3.2.1",
"rambda": "^6.7.0", "rambda": "^6.7.0",
"react-select": "^5.2.2" "react-select": "^5.2.2"

View File

@ -2,11 +2,13 @@ import * as T from "../../types";
import { renderEP } from "../../lib/phrase-building/render-ep"; import { renderEP } from "../../lib/phrase-building/render-ep";
import { compileEP } from "../../lib/phrase-building/compile-ep"; import { compileEP } from "../../lib/phrase-building/compile-ep";
import AbbreviationFormSelector from "../vp-explorer/AbbreviationFormSelector"; import AbbreviationFormSelector from "../vp-explorer/AbbreviationFormSelector";
import useStickyState from "../../lib/useStickyState";
import Examples from "../Examples"; import Examples from "../Examples";
function EPDisplay({ eps, opts }: { eps: T.EPSelectionState, opts: T.TextOptions }) { function EPDisplay({ eps, opts, setForm }: {
const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationFormEq"); eps: T.EPSelectionState,
opts: T.TextOptions,
setForm: (form: T.FormVersion) => void,
}) {
const EP = completeEPSelection(eps); const EP = completeEPSelection(eps);
if (!EP) { if (!EP) {
return <div className="lead text-center my-4"> return <div className="lead text-center my-4">
@ -20,11 +22,11 @@ function EPDisplay({ eps, opts }: { eps: T.EPSelectionState, opts: T.TextOptions
</div> </div>
} }
const rendered = renderEP(EP); const rendered = renderEP(EP);
const result = compileEP(rendered, form); const result = compileEP(rendered, EP.form);
return <div className="text-center pt-3"> return <div className="text-center pt-3">
<AbbreviationFormSelector <AbbreviationFormSelector
adjustable="king" adjustable="king"
form={{ ...form, shrinkServant: false }} form={EP.form}
onChange={setForm} onChange={setForm}
/> />
{"long" in result.ps ? {"long" in result.ps ?

View File

@ -32,7 +32,8 @@ function EPExplorer(props: {
negative: false, negative: false,
}, },
shrunkenPossesive: undefined, shrunkenPossesive: undefined,
}, "EPSelectionState3"); form: { removeKing: false, shrinkServant: false },
}, "EPSelectionState10");
function handlePredicateTypeChange(type: "NP" | "Complement") { function handlePredicateTypeChange(type: "NP" | "Complement") {
setEps(o => ({ setEps(o => ({
...o, ...o,
@ -63,6 +64,12 @@ function EPExplorer(props: {
shrunkenPossesive, shrunkenPossesive,
})); }));
} }
function handleSetForm(form: T.FormVersion) {
setEps(o => ({
...o,
form,
}));
}
const king = eps.subject?.type === "pronoun" const king = eps.subject?.type === "pronoun"
? "subject" ? "subject"
: eps.predicate.type === "Complement" : eps.predicate.type === "Complement"
@ -134,7 +141,11 @@ function EPExplorer(props: {
</div> </div>
</div> </div>
{mode === "charts" && <EqChartsDisplay tense={eps.equative.tense} opts={props.opts} />} {mode === "charts" && <EqChartsDisplay tense={eps.equative.tense} opts={props.opts} />}
{mode === "phrases" && <EPDisplay opts={props.opts} eps={eps} />} {mode === "phrases" && <EPDisplay
opts={props.opts}
eps={eps}
setForm={handleSetForm}
/>}
</div>; </div>;
} }

View File

@ -30,7 +30,6 @@ function NPPicker(props: {
const [npType, setNpType] = useState<T.NPType | undefined>(props.np ? props.np.type : undefined); const [npType, setNpType] = useState<T.NPType | undefined>(props.np ? props.np.type : undefined);
useEffect(() => { useEffect(() => {
setNpType(props.np ? props.np.type : undefined); setNpType(props.np ? props.np.type : undefined);
// setAddingPoss(false);
}, [props.np]); }, [props.np]);
function handleClear() { function handleClear() {
if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return; if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return;

View File

@ -8,8 +8,11 @@ import {
import useStickyState from "../../lib/useStickyState"; import useStickyState from "../../lib/useStickyState";
import Examples from "../Examples"; import Examples from "../Examples";
function VPDisplay({ VP, opts }: { VP: T.VPSelectionState, opts: T.TextOptions }) { function VPDisplay({ VP, opts, setForm }: {
const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationForm"); VP: T.VPSelectionState,
opts: T.TextOptions,
setForm: (form: T.FormVersion) => void,
}) {
const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV"); const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV");
const VPComplete = completeVPSelection(VP); const VPComplete = completeVPSelection(VP);
if (!VPComplete) { if (!VPComplete) {
@ -20,7 +23,7 @@ function VPDisplay({ VP, opts }: { VP: T.VPSelectionState, opts: T.TextOptions }
})()} })()}
</div>; </div>;
} }
const result = compileVP(renderVP(VPComplete), { ...form, OSV }); const result = compileVP(renderVP(VPComplete), { ...VP.form, OSV });
return <div className="text-center mt-1"> return <div className="text-center mt-1">
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2"> {VP.verb.transitivity === "transitive" && <div className="form-check mb-2">
<input <input
@ -36,7 +39,7 @@ function VPDisplay({ VP, opts }: { VP: T.VPSelectionState, opts: T.TextOptions }
</div>} </div>}
<AbbreviationFormSelector <AbbreviationFormSelector
adjustable={whatsAdjustable(VPComplete)} adjustable={whatsAdjustable(VPComplete)}
form={form} form={VP.form}
onChange={setForm} onChange={setForm}
/> />
{"long" in result.ps ? {"long" in result.ps ?

View File

@ -16,17 +16,11 @@ import { isPastTense } from "../../lib/phrase-building/vp-tools";
import VPExplorerQuiz from "./VPExplorerQuiz"; import VPExplorerQuiz from "./VPExplorerQuiz";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools"; import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal"; import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal";
// @ts-ignore
import LZString from "lz-string";
// TO FINISH IMPERATIVE STUFF!! const phraseURLParam = "VPPhrase";
// TODO: English Builders for imperatives
// TODO: Quiz with imperatives
// TODO: make answerFeedback emojis appear at random translate angles a little bit
// add energy drinks?
// TODO: Drill Down text display options
// TODO: SHOW KING AND SERVANT ONCE TENSE PICKED, EVEN IF NPs not selected
// TODO: Issue with dynamic compounds english making with plurals // TODO: Issue with dynamic compounds english making with plurals
// TODO: Issue with "the money were taken" // TODO: Issue with "the money were taken"
// TODO: Use the same component for PronounPicker and NPPronounPicker (sizing issue) // TODO: Use the same component for PronounPicker and NPPronounPicker (sizing issue)
@ -38,14 +32,17 @@ import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationMod
// TODO: error handling on error with rendering etc // TODO: error handling on error with rendering etc
export function VPExplorer(props: { export function VPExplorer(props: {
loaded?: T.VPSelectionState,
verb: T.VerbEntry, verb: T.VerbEntry,
opts: T.TextOptions, opts: T.TextOptions,
handleLinkClick: ((ts: number) => void) | "none", handleLinkClick: ((ts: number) => void) | "none",
entryFeeder: T.EntryFeeder, entryFeeder: T.EntryFeeder,
}) { }) {
const [vps, setVps] = useStickyState<T.VPSelectionState>( const [vps, setVps] = useStickyState<T.VPSelectionState>(
savedVps => makeVPSelectionState(props.verb, savedVps), props.loaded
"vpsState7", ? props.loaded
: savedVps => makeVPSelectionState(props.verb, savedVps),
"vpsState8",
); );
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">( const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
savedMode => { savedMode => {
@ -55,12 +52,20 @@ export function VPExplorer(props: {
}, },
"verbExplorerMode2", "verbExplorerMode2",
); );
const [showShareClipped, setShowShareClipped] = useState<boolean>(false);
const [showingExplanation, setShowingExplanation] = useState<{ role: "servant" | "king", item: "subject" | "object" } | false>(false); const [showingExplanation, setShowingExplanation] = useState<{ role: "servant" | "king", item: "subject" | "object" } | false>(false);
const isPast = isPastTense(vps.verb.tenseCategory === "perfect" ? vps.verb.perfectTense : vps.verb.verbTense); const isPast = isPastTense(vps.verb.tenseCategory === "perfect" ? vps.verb.perfectTense : vps.verb.verbTense);
const roles = getKingAndServant( const roles = getKingAndServant(
isPast, isPast,
vps.verb.transitivity !== "intransitive", vps.verb.transitivity !== "intransitive",
); );
useEffect(() => {
const VPSFromUrl = getVPSFromUrl();
if (VPSFromUrl) {
setVps(VPSFromUrl);
}
// eslint-disable-next-line
}, []);
useEffect(() => { useEffect(() => {
setVps(oldVps => { setVps(oldVps => {
if (mode === "quiz") { if (mode === "quiz") {
@ -112,6 +117,21 @@ export function VPExplorer(props: {
} }
return f; return f;
} }
function handleSetForm(form: T.FormVersion) {
setVps(o => ({
...o,
form,
}));
}
// for some crazy reason I can't get the URI share thing to encode and decode properly
function handleCopyShareLink() {
const shareUrl = getShareUrl(vps);
navigator.clipboard.writeText(shareUrl);
setShowShareClipped(true);
setTimeout(() => {
setShowShareClipped(false);
}, 1000);
}
return <div className="mt-3" style={{ maxWidth: "950px"}}> return <div className="mt-3" style={{ maxWidth: "950px"}}>
<VerbPicker <VerbPicker
vps={vps} vps={vps}
@ -119,7 +139,9 @@ export function VPExplorer(props: {
opts={props.opts} opts={props.opts}
handleLinkClick={props.handleLinkClick} handleLinkClick={props.handleLinkClick}
/> />
<div className="mt-2 mb-3 text-center"> <div className="mt-2 mb-3 d-flex flex-row justify-content-between align-items-center">
<div style={{ width: "1rem" }}>
</div>
<ButtonSelect <ButtonSelect
value={mode} value={mode}
options={[ options={[
@ -129,6 +151,13 @@ export function VPExplorer(props: {
]} ]}
handleChange={setMode} handleChange={setMode}
/> />
<div
className="clickable"
onClick={mode === "phrases" ? handleCopyShareLink : undefined}
style={{ width: "1rem" }}
>
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
</div>
</div> </div>
{(vps.verb && (typeof vps.verb.object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) && {(vps.verb && (typeof vps.verb.object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) &&
<div className="text-center my-2"> <div className="text-center my-2">
@ -183,13 +212,26 @@ export function VPExplorer(props: {
/> />
</div> </div>
</div>} </div>}
{mode === "phrases" && <VPDisplay VP={vps} opts={props.opts} />} {mode === "phrases" && <VPDisplay
VP={vps}
opts={props.opts}
setForm={handleSetForm}
/>}
{mode === "charts" && <ChartDisplay VS={vps.verb} opts={props.opts} />} {mode === "charts" && <ChartDisplay VS={vps.verb} opts={props.opts} />}
{mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />} {mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />}
<VPExplorerExplanationModal <VPExplorerExplanationModal
showing={showingExplanation} showing={showingExplanation}
setShowing={setShowingExplanation} setShowing={setShowingExplanation}
/> />
{showShareClipped && <div className="alert alert-primary text-center" role="alert" style={{
position: "fixed",
top: "30%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: 9999999999999,
}}>
Phrase URL copied to clipboard
</div>}
</div> </div>
} }
@ -202,4 +244,19 @@ function hasPronounConflict(subject: T.NPSelection | undefined, object: undefine
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person); return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
} }
function getShareUrl(vps: T.VPSelectionState): string {
const stringJSON = JSON.stringify(vps);
const encoded = LZString.compressToEncodedURIComponent(stringJSON);
const url = new URL(window.location.href);
url.searchParams.append(phraseURLParam, encoded);
return url.toString();
}
function getVPSFromUrl(): T.VPSelectionState | undefined {
const params = new URLSearchParams(window.location.search);
const fromParams = params.get(phraseURLParam);
if (!fromParams) return;
const decoded = LZString.decompressFromEncodedURIComponent(fromParams);
return JSON.parse(decoded) as T.VPSelectionState;
}

View File

@ -446,6 +446,7 @@ function getRandomVPSelection(mix: MixType = "both") {
subject: subject !== undefined ? subject : randSubj, subject: subject !== undefined ? subject : randSubj,
verb: randomizeTense(verb, true), verb: randomizeTense(verb, true),
shrunkenPossesive: undefined, shrunkenPossesive: undefined,
form: { removeKing: false, shrinkServant: false },
} }
} }
const v: T.VerbSelectionComplete = { const v: T.VerbSelectionComplete = {
@ -462,6 +463,7 @@ function getRandomVPSelection(mix: MixType = "both") {
subject: randSubj, subject: randSubj,
verb: randomizeTense(v, true), verb: randomizeTense(v, true),
shrunkenPossesive: undefined, shrunkenPossesive: undefined,
form: { removeKing: false, shrinkServant: false },
}; };
}; };
}; };

View File

@ -66,6 +66,7 @@ export function makeVPSelectionState(
canChangeVoice: transitivity === "transitive", canChangeVoice: transitivity === "transitive",
canChangeStatDyn: "stative" in info, canChangeStatDyn: "stative" in info,
}, },
form: os ? os.form : { removeKing: false, shrinkServant: false },
shrunkenPossesive: os ? os.shrunkenPossesive : undefined, shrunkenPossesive: os ? os.shrunkenPossesive : undefined,
}; };
} }

View File

@ -35,7 +35,7 @@ export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?
}; };
} }
function getSegmentsAndKids(EP: T.EPRendered, form: T.FormVersion): { kids: Segment[], NPs: Segment[] } { function getSegmentsAndKids(EP: T.EPRendered, form: Omit<T.FormVersion, "shrinkServant">): { kids: Segment[], NPs: Segment[] } {
function ifNotRemoved(s: Segment, role: "subject" | "predicate"): Segment[] { function ifNotRemoved(s: Segment, role: "subject" | "predicate"): Segment[] {
if (form.removeKing && EP.king === role) { if (form.removeKing && EP.king === role) {
return []; return [];

View File

@ -33,6 +33,7 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
equative: renderEquative(EP.equative, kingPerson), equative: renderEquative(EP.equative, kingPerson),
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative), englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
shrunkenPossesive: EP.shrunkenPossesive, shrunkenPossesive: EP.shrunkenPossesive,
form: EP.form,
}; };
} }

View File

@ -59,6 +59,7 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
object: VP.verb.isCompound === "dynamic" ? "none" : VP.verb.object, object: VP.verb.isCompound === "dynamic" ? "none" : VP.verb.object,
vs: VP.verb, vs: VP.verb,
}), }),
form: VP.form,
}; };
return b; return b;
} }

File diff suppressed because one or more lines are too long

View File

@ -509,6 +509,7 @@ export type VPRendered = {
object: Rendered<NPSelection> | ObjectNP, object: Rendered<NPSelection> | ObjectNP,
verb: VerbRendered, verb: VerbRendered,
englishBase?: string[], englishBase?: string[],
form: FormVersion,
} }
export type VerbTense = "presentVerb" export type VerbTense = "presentVerb"
@ -531,12 +532,14 @@ export type VPSelectionState = {
subject: NPSelection | undefined, subject: NPSelection | undefined,
verb: VerbSelection, verb: VerbSelection,
shrunkenPossesive: undefined | number, shrunkenPossesive: undefined | number,
form: FormVersion,
}; };
export type VPSelectionComplete = { export type VPSelectionComplete = {
subject: NPSelection, subject: NPSelection,
verb: VerbSelectionComplete, verb: VerbSelectionComplete,
shrunkenPossesive: undefined | number, shrunkenPossesive: undefined | number,
form: FormVersion,
}; };
export type VerbSelectionComplete = Omit<VerbSelection, "object" | "verbTense" | "perfectTense" | "imperfectiveTense" | "tenseCategory"> & { export type VerbSelectionComplete = Omit<VerbSelection, "object" | "verbTense" | "perfectTense" | "imperfectiveTense" | "tenseCategory"> & {
@ -664,6 +667,7 @@ export type EPSelectionState = {
}, },
equative: EquativeSelection, equative: EquativeSelection,
shrunkenPossesive: undefined | number, shrunkenPossesive: undefined | number,
form: FormVersion,
}; };
export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & { export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & {
@ -675,6 +679,7 @@ export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"
type: "Complement", type: "Complement",
selection: EqCompSelection, selection: EqCompSelection,
}, },
form: FormVersion,
}; };
export type EqCompType = "adjective" | "loc. adv."; // TODO: - more export type EqCompType = "adjective" | "loc. adv."; // TODO: - more
@ -699,6 +704,7 @@ export type EPRendered = {
equative: EquativeRendered, equative: EquativeRendered,
englishBase?: string[], englishBase?: string[],
shrunkenPossesive: undefined | number, shrunkenPossesive: undefined | number,
form: FormVersion,
} }
export type EntryFeeder = { export type EntryFeeder = {

View File

@ -7131,6 +7131,11 @@ jsonfile@^6.0.1:
optionalDependencies: optionalDependencies:
graceful-fs "^4.1.6" graceful-fs "^4.1.6"
jsurl2@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/jsurl2/-/jsurl2-2.1.0.tgz#4c7f0cd33344f318cea5e321089c983194e947c7"
integrity sha512-dN5xpdjYWxhkn/hN9jGovVP/sy8HqP3BiNU7nKm7pwtRSfQlKG8FZO7wlO4kmydAYzv3UwVVzGjAnEBr4KCmmw==
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
version "3.2.1" version "3.2.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b"