ability to copy phrase selection code

This commit is contained in:
lingdocs 2022-05-30 15:59:44 -05:00
parent dc0cd48015
commit cab6528a6c
4 changed files with 126 additions and 19 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/pashto-inflector", "name": "@lingdocs/pashto-inflector",
"version": "2.6.8", "version": "2.6.9",
"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

@ -13,6 +13,9 @@ import { completeEPSelection } from "../../lib/phrase-building/render-ep";
import { makeEPSBlocks, getSubjectSelection } from "../../lib/phrase-building/blocks-utils"; import { makeEPSBlocks, getSubjectSelection } from "../../lib/phrase-building/blocks-utils";
import APPicker from "../ap-picker/APPicker"; import APPicker from "../ap-picker/APPicker";
import autoAnimate from "@formkit/auto-animate"; import autoAnimate from "@formkit/auto-animate";
// @ts-ignore
import LZString from "lz-string";
const phraseURLParam = "EPhrase";
const blankEps: T.EPSelectionState = { const blankEps: T.EPSelectionState = {
blocks: makeEPSBlocks(), blocks: makeEPSBlocks(),
@ -37,10 +40,22 @@ function EPExplorer(props: {
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode"); const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPState4", flashMessage); const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPState4", flashMessage);
const [alert, setAlert] = useState<string | undefined>(undefined); const [alert, setAlert] = useState<string | undefined>(undefined);
const [showClipped, setShowClipped] = useState<string>("");
const parent = useRef<HTMLDivElement>(null); const parent = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
parent.current && autoAnimate(parent.current); parent.current && autoAnimate(parent.current);
}, [parent]); }, [parent]);
useEffect(() => {
const EPSFromUrl = getEPSFromUrl();
if (EPSFromUrl) {
setMode("phrases");
adjustEps({
type: "load EPS",
payload: EPSFromUrl,
});
}
// eslint-disable-next-line
}, []);
const subject = getSubjectSelection(eps.blocks).selection; const subject = getSubjectSelection(eps.blocks).selection;
const king = subject?.type === "pronoun" const king = subject?.type === "pronoun"
? "subject" ? "subject"
@ -53,9 +68,26 @@ function EPExplorer(props: {
setAlert(undefined); setAlert(undefined);
}, 1500); }, 1500);
} }
function flashClippedMessage(m: string) {
setShowClipped(m);
setTimeout(() => {
setShowClipped("");
}, 1250);
}
function handleCopyCode() {
const code = getCode(eps);
navigator.clipboard.writeText(code);
flashClippedMessage("Copied phrase code to clipboard");
}
function handleCopyShareLink() {
const shareUrl = getShareUrl(eps);
navigator.clipboard.writeText(shareUrl);
flashClippedMessage("Copied phrase URL to clipboard");
}
const phraseIsComplete = !!completeEPSelection(eps); const phraseIsComplete = !!completeEPSelection(eps);
return <div> return <div>
<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 />
<ButtonSelect <ButtonSelect
value={mode} value={mode}
options={[ options={[
@ -64,6 +96,22 @@ function EPExplorer(props: {
]} ]}
handleChange={setMode} handleChange={setMode}
/> />
<div className="d-flex flex-row">
<div
className="clickable mr-4"
onClick={mode === "phrases" ? handleCopyCode : undefined}
style={{ width: "1rem" }}
>
{(mode === "phrases" && phraseIsComplete) ? <i className="fas fa-code" /> : ""}
</div>
<div
className="clickable"
onClick={mode === "phrases" ? handleCopyShareLink : undefined}
style={{ width: "1rem" }}
>
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
</div>
</div>
</div> </div>
{mode === "phrases" && <div className="clickable h5" onClick={() => adjustEps({ type: "insert new AP" })}>+ AP</div>} {mode === "phrases" && <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" }}> <div ref={parent} className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
@ -160,7 +208,39 @@ function EPExplorer(props: {
}}> }}>
{alert} {alert}
</div>} </div>}
{showClipped && <div className="alert alert-primary text-center" role="alert" style={{
position: "fixed",
top: "30%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: 9999999999999,
}}>
{showClipped}
</div>}
</div>; </div>;
} }
export default EPExplorer; export default EPExplorer;
function getShareUrl(eps: T.EPSelectionState): string {
const code = getCode(eps);
const encoded = LZString.compressToEncodedURIComponent(code);
const url = new URL(window.location.href);
// need to delete or else you could just get a second param written after
// which gets ignored
url.searchParams.delete(phraseURLParam);
url.searchParams.append(phraseURLParam, encoded);
return url.toString();
}
function getCode(eps: T.EPSelectionState): string {
return JSON.stringify(eps);
}
function getEPSFromUrl(): T.EPSelectionState | 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.EPSelectionState;
}

View File

@ -42,7 +42,10 @@ type EpsReducerAction = {
index: number, index: number,
direction: "back" | "forward", 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") {
@ -173,6 +176,9 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
blocks: shiftBlock(eps.blocks, index, direction), blocks: shiftBlock(eps.blocks, index, direction),
}; };
} }
if (action.type === "load EPS") {
return action.payload;
}
throw new Error("unknown epsReducer action"); throw new Error("unknown epsReducer action");
} }

View File

@ -20,7 +20,7 @@ import APPicker from "../ap-picker/APPicker";
import autoAnimate from "@formkit/auto-animate"; import autoAnimate from "@formkit/auto-animate";
import { getObjectSelection, getSubjectSelection, isNoObject } from "../../lib/phrase-building/blocks-utils"; import { getObjectSelection, getSubjectSelection, isNoObject } from "../../lib/phrase-building/blocks-utils";
const phraseURLParam = "VPPhrase"; const phraseURLParam = "VPhrase";
// 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"
@ -55,7 +55,7 @@ function VPExplorer(props: {
}, },
"verbExplorerMode2", "verbExplorerMode2",
); );
const [showShareClipped, setShowShareClipped] = useState<boolean>(false); const [showClipped, setShowClipped] = useState<string>("");
const [alert, setAlert] = useState<string | undefined>(undefined); const [alert, setAlert] = useState<string | undefined>(undefined);
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 parent = useRef<HTMLDivElement>(null); const parent = useRef<HTMLDivElement>(null);
@ -121,14 +121,22 @@ function VPExplorer(props: {
payload: form, payload: form,
}); });
} }
function flashClippedMessage(m: string) {
setShowClipped(m);
setTimeout(() => {
setShowClipped("");
}, 1250);
}
// for some crazy reason I can't get the URI share thing to encode and decode properly // for some crazy reason I can't get the URI share thing to encode and decode properly
function handleCopyShareLink() { function handleCopyShareLink() {
const shareUrl = getShareUrl(vps); const shareUrl = getShareUrl(vps);
navigator.clipboard.writeText(shareUrl); navigator.clipboard.writeText(shareUrl);
setShowShareClipped(true); flashClippedMessage("Copied phrase URL to clipboard");
setTimeout(() => { }
setShowShareClipped(false); function handleCopyCode() {
}, 1250); const code = getCode(vps);
navigator.clipboard.writeText(code);
flashClippedMessage("Copied phrase code to clipboard");
} }
const object = getObjectSelection(vps.blocks).selection; const object = getObjectSelection(vps.blocks).selection;
const subject = getSubjectSelection(vps.blocks).selection; const subject = getSubjectSelection(vps.blocks).selection;
@ -160,6 +168,14 @@ function VPExplorer(props: {
]} ]}
handleChange={setMode} handleChange={setMode}
/> />
<div className="d-flex flex-row">
<div
className="clickable mr-4"
onClick={mode === "phrases" ? handleCopyCode : undefined}
style={{ width: "1rem" }}
>
{(mode === "phrases" && phraseIsComplete) ? <i className="fas fa-code" /> : ""}
</div>
<div <div
className="clickable" className="clickable"
onClick={mode === "phrases" ? handleCopyShareLink : undefined} onClick={mode === "phrases" ? handleCopyShareLink : undefined}
@ -168,6 +184,7 @@ function VPExplorer(props: {
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""} {mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
</div> </div>
</div> </div>
</div>
{(vps.verb && (typeof object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) && {(vps.verb && (typeof object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) &&
<div className="text-center my-2"> <div className="text-center my-2">
<button onClick={handleSubjObjSwap} className="btn btn-sm btn-light"> <button onClick={handleSubjObjSwap} className="btn btn-sm btn-light">
@ -288,14 +305,14 @@ function VPExplorer(props: {
showing={showingExplanation} showing={showingExplanation}
setShowing={setShowingExplanation} setShowing={setShowingExplanation}
/> />
{showShareClipped && <div className="alert alert-primary text-center" role="alert" style={{ {showClipped && <div className="alert alert-primary text-center" role="alert" style={{
position: "fixed", position: "fixed",
top: "30%", top: "30%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
zIndex: 9999999999999, zIndex: 9999999999999,
}}> }}>
Phrase URL copied to clipboard {showClipped}
</div>} </div>}
{alert && <div className="alert alert-warning text-center" role="alert" style={{ {alert && <div className="alert alert-warning text-center" role="alert" style={{
position: "fixed", position: "fixed",
@ -312,8 +329,8 @@ function VPExplorer(props: {
export default VPExplorer; export default VPExplorer;
function getShareUrl(vps: T.VPSelectionState): string { function getShareUrl(vps: T.VPSelectionState): string {
const stringJSON = JSON.stringify(vps); const code = getCode(vps);
const encoded = LZString.compressToEncodedURIComponent(stringJSON); const encoded = LZString.compressToEncodedURIComponent(code);
const url = new URL(window.location.href); const url = new URL(window.location.href);
// need to delete or else you could just get a second param written after // need to delete or else you could just get a second param written after
// which gets ignored // which gets ignored
@ -322,6 +339,10 @@ function getShareUrl(vps: T.VPSelectionState): string {
return url.toString(); return url.toString();
} }
function getCode(vps: T.VPSelectionState): string {
return JSON.stringify(vps);
}
function getVPSFromUrl(): T.VPSelectionState | undefined { function getVPSFromUrl(): T.VPSelectionState | undefined {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const fromParams = params.get(phraseURLParam); const fromParams = params.get(phraseURLParam);