diff --git a/website/src/components/Entry.tsx b/website/src/components/Entry.tsx index 99732f7..58fffff 100644 --- a/website/src/components/Entry.tsx +++ b/website/src/components/Entry.tsx @@ -7,37 +7,39 @@ */ import ExtraEntryInfo from "../components/ExtraEntryInfo"; import classNames from "classnames"; -import { - Types as T, - InlinePs, -} from "@lingdocs/ps-react"; +import { Types as T, InlinePs } from "@lingdocs/ps-react"; -function Entry({ entry, textOptions, nonClickable, isolateEntry }: { - entry: T.DictionaryEntry, - textOptions: T.TextOptions, - nonClickable?: boolean, - isolateEntry?: (ts: number) => void, +function Entry({ + entry, + textOptions, + nonClickable, + isolateEntry, +}: { + entry: T.DictionaryEntry; + textOptions: T.TextOptions; + nonClickable?: boolean; + isolateEntry?: (ts: number) => void; }) { - return ( -
isolateEntry(entry.ts) : undefined} - data-testid="entry" - > -
- - {{ p: entry.p, f: entry.f }} - - {` `} - {entry.c} -
- -
{entry.e}
-
- ); -}; + return ( +
isolateEntry(entry.ts) : undefined + } + data-testid="entry" + > +
+ + {{ p: entry.p, f: entry.f }} + + {` `} + {entry.c} + {entry.a && !nonClickable && } +
+ +
{entry.e}
+
+ ); +} export default Entry; diff --git a/website/src/components/PlayStorageAudio.tsx b/website/src/components/PlayStorageAudio.tsx new file mode 100644 index 0000000..af1fd61 --- /dev/null +++ b/website/src/components/PlayStorageAudio.tsx @@ -0,0 +1,13 @@ +export default function playStorageAudio(ts: number, callback: () => void) { + if (!ts) return; + let audio = new Audio(`https://storage.lingdocs.com/${ts}.mp3`); + audio.addEventListener("ended", () => { + callback(); + audio.remove(); + audio.srcObject = null; + }); + audio.play().catch((e) => { + console.error(e); + alert("Error playing audio - Connect to the internet and try again"); + }); +} diff --git a/website/src/screens/IsolatedEntry.tsx b/website/src/screens/IsolatedEntry.tsx index 7d6246c..9e09392 100644 --- a/website/src/screens/IsolatedEntry.tsx +++ b/website/src/screens/IsolatedEntry.tsx @@ -8,334 +8,429 @@ import { useEffect, useState } from "react"; import { - VPExplorer, - InflectionsTable, - inflectWord, - InlinePs, - Types as T, - typePredicates as tp, - getInflectionPattern, - HumanReadableInflectionPattern, + VPExplorer, + InflectionsTable, + inflectWord, + InlinePs, + Types as T, + typePredicates as tp, + getInflectionPattern, + HumanReadableInflectionPattern, } from "@lingdocs/ps-react"; -import { - submissionBase, - addSubmission, -} from "../lib/submissions"; +import { submissionBase, addSubmission } from "../lib/submissions"; import { Link } from "react-router-dom"; import Entry from "../components/Entry"; import Results from "../screens/Results"; import WordlistWordEditor from "../components/WordlistWordEditor"; import { - addToWordlist, - deleteWordFromWordlist, - hasAttachment, + addToWordlist, + deleteWordFromWordlist, + hasAttachment, } from "../lib/wordlist-database"; import { wordlistEnabled } from "../lib/level-management"; -import AudioPlayButton from "../components/AudioPlayButton"; +import AudioPlayButton from "../components/AudioPlayButton"; import { Helmet } from "react-helmet"; import { Modal } from "react-bootstrap"; import { getTextOptions } from "../lib/get-text-options"; -import { - entryFeeder, -} from "../lib/dictionary"; -import { - State, - DictionaryAPI, -} from "../types/dictionary-types"; +import { entryFeeder } from "../lib/dictionary"; +import { State, DictionaryAPI } from "../types/dictionary-types"; +import playStorageAudio from "../components/PlayStorageAudio"; -function IsolatedEntry({ state, dictionary, isolateEntry }: { - state: State, - dictionary: DictionaryAPI, - isolateEntry: (ts: number) => void, +function IsolatedEntry({ + state, + dictionary, + isolateEntry, +}: { + state: State; + dictionary: DictionaryAPI; + isolateEntry: (ts: number) => void; }) { - const [exploded, setExploded] = useState(false); - const [editing, setEditing] = useState(false); - const [comment, setComment] = useState(""); - const [editSubmitted, setEditSubmitted] = useState(false); - const [showingDeleteWarning, setShowingDeleteWarning] = useState(false); - const [showClipped, setShowClipped] = useState(""); - useEffect(() => { - setEditing(false); - setComment(""); - setEditSubmitted(false); - }, [state]); - function flashClippedMessage(m: string) { - setShowClipped(m); - setTimeout(() => { - setShowClipped(""); - }, 1250); + const [exploded, setExploded] = useState(false); + const [playing, setPlaying] = useState(false); + const [editing, setEditing] = useState(false); + const [comment, setComment] = useState(""); + const [editSubmitted, setEditSubmitted] = useState(false); + const [showingDeleteWarning, setShowingDeleteWarning] = + useState(false); + const [showClipped, setShowClipped] = useState(""); + useEffect(() => { + setEditing(false); + setComment(""); + setEditSubmitted(false); + setPlaying(false); + }, [state]); + function flashClippedMessage(m: string) { + setShowClipped(m); + setTimeout(() => { + setShowClipped(""); + }, 1250); + } + const wordlistWord = state.wordlist.find( + (w) => w.entry.ts === state.isolatedEntry?.ts + ); + const textOptions = getTextOptions(state); + function submitEdit() { + if (!state.isolatedEntry) return; + if (!state.user) return; + addSubmission( + { + ...submissionBase(state.user), + type: "edit suggestion", + entry: state.isolatedEntry, + comment, + }, + state.user + ); + setEditing(false); + setComment(""); + setEditSubmitted(true); + } + function handleAddToWordlist() { + if (!state.isolatedEntry) return; + const toAdd = { + entry: state.isolatedEntry, + notes: "", + }; + addToWordlist(toAdd); + } + function handleDeleteFromWordlist() { + if (!state.isolatedEntry) return; + if (!wordlistWord) return; + setShowingDeleteWarning(false); + deleteWordFromWordlist(wordlistWord._id); + } + const entry = state.isolatedEntry; + if (!entry) { + return ( +
+

Word not found

+
+ Home +
+
+ ); + } + const complement = entry.l ? dictionary.findOneByTs(entry.l) : undefined; + const relatedEntries = dictionary.findRelatedEntries(entry); + const inf = ((): T.InflectorOutput | false => { + try { + return inflectWord(entry); + } catch (e) { + console.error("error inflecting entry", entry); + return false; } - const wordlistWord = state.wordlist.find((w) => w.entry.ts === state.isolatedEntry?.ts); - const textOptions = getTextOptions(state); - function submitEdit() { - if (!state.isolatedEntry) return; - if (!state.user) return; - addSubmission({ - ...submissionBase(state.user), - type: "edit suggestion", - entry: state.isolatedEntry, - comment, - }, state.user); - setEditing(false); - setComment(""); - setEditSubmitted(true); + })(); + const isVerbEntry = tp.isVerbEntry({ entry, complement }); + function DisplayVPExplorer(props: { + entry: T.DictionaryEntry; + complement: T.DictionaryEntry | undefined; + }) { + try { + return ( + + ); + } catch (e) { + console.error("error rendering VPExplorer", e); + return null; } - function handleAddToWordlist() { - if (!state.isolatedEntry) return; - const toAdd = { - entry: state.isolatedEntry, - notes: "", - }; - addToWordlist(toAdd); - } - function handleDeleteFromWordlist() { - if (!state.isolatedEntry) return; - if (!wordlistWord) return; - setShowingDeleteWarning(false); - deleteWordFromWordlist(wordlistWord._id); - } - const entry = state.isolatedEntry; - if (!entry) { - return
-

Word not found

-
Home
-
; - } - const complement = entry.l - ? dictionary.findOneByTs(entry.l) - : undefined; - const relatedEntries = dictionary.findRelatedEntries(entry); - const inf = ((): T.InflectorOutput | false => { - try { - return inflectWord(entry); - } catch (e) { - console.error("error inflecting entry", entry); - return false; - } - })(); - const isVerbEntry = tp.isVerbEntry({ entry, complement }); - function DisplayVPExplorer(props: { - entry: T.DictionaryEntry, - complement: T.DictionaryEntry | undefined, - }) { - try { - return - } catch (e) { - console.error("error rendering VPExplorer", e); - return null; - } - } - function handleClipId() { - if (!entry) return - navigator.clipboard.writeText(entry.ts.toString()); - flashClippedMessage("word id copied to clipboard"); - } - function handleClipEntry() { - if (!entry) return - navigator.clipboard.writeText(JSON.stringify(entry)); - flashClippedMessage("entry data copied to clipboard"); - } - return
- - {entry.p} - LingDocs Pashto Dictionary - -
-
- -
-
-
-
setExploded(os => !os)} - > - -
-
- -
- {state.user && state.user.level === "editor" && <> -
- -
- -
- -
- - } - {state.user && <> -
setEditing(os => !os)} - > - -
- {wordlistEnabled(state.user) &&
setShowingDeleteWarning(true) - : () => handleAddToWordlist() - } - > - -
} - } -
-
+ } + function handleClipId() { + if (!entry) return; + navigator.clipboard.writeText(entry.ts.toString()); + flashClippedMessage("word id copied to clipboard"); + } + function handleClipEntry() { + if (!entry) return; + navigator.clipboard.writeText(JSON.stringify(entry)); + flashClippedMessage("entry data copied to clipboard"); + } + function handlePlayStorageAudio() { + if (!entry) return; + setPlaying(true); + playStorageAudio(entry.ts, () => { + setPlaying(false); + }); + } + return ( +
+ + {entry.p} - LingDocs Pashto Dictionary + +
+
+
- {wordlistWord && <> - {hasAttachment(wordlistWord, "audio") && } - - } - {editing && -
-
- - setComment(e.target.value)} - /> -
-
- - -
+
+
+ {entry.a && ( +
+ +
+ )} +
setExploded((os) => !os)} + > +
- } - {editSubmitted &&

Thank you for your help!

} - {inf && <> - {inf.inflections && (() => { - const pattern = getInflectionPattern( - // @ts-ignore - entry - ); - return ; +
+ +
+ {state.user && state.user.level === "editor" && ( + <> +
+ +
+ +
+ +
+ + + )} + {state.user && ( + <> +
setEditing((os) => !os)} + > + +
+ {wordlistEnabled(state.user) && ( +
setShowingDeleteWarning(true) + : () => handleAddToWordlist() + } + > + +
+ )} + + )} +
+
+
+ {wordlistWord && ( + <> + {hasAttachment(wordlistWord, "audio") && ( + + )} + + + )} + {editing && ( +
+
+ + setComment(e.target.value)} + /> +
+
+ + +
+
+ )} + {editSubmitted &&

Thank you for your help!

} + {inf && ( + <> + {inf.inflections && + (() => { + const pattern = getInflectionPattern( + // @ts-ignore + entry + ); + return ( + + ); })()} - {"plural" in inf && inf.plural !== undefined &&
-
Plural
- -
} - {"bundledPlural" in inf && inf.bundledPlural !== undefined &&
-
Bundled Plural
- -
} - {"arabicPlural" in inf && inf.arabicPlural !== undefined &&
-
Arabic Plural
- -
} - } - {isVerbEntry &&
- -
} - {showClipped &&
+
Plural
+ +
+ )} + {"bundledPlural" in inf && inf.bundledPlural !== undefined && ( +
+
Bundled Plural
+ +
+ )} + {"arabicPlural" in inf && inf.arabicPlural !== undefined && ( +
+
Arabic Plural
+ +
+ )} + + )} + {isVerbEntry && ( +
+ +
+ )} + {showClipped && ( +
- {showClipped} -
} - - {!!(relatedEntries && relatedEntries.length) ? <> -

Related Words

- null} - /> - :
} - setShowingDeleteWarning(false)} - animation={false} + }} > - - Delete from wordlist? - - Delete {{ p: entry.p, f: entry.f }} from your wordlist? - - - - - - -
; + {showClipped} +
+ )} + + {!!(relatedEntries && relatedEntries.length) ? ( + <> +

+ Related Words +

+ null} + /> + + ) : ( +
+ )} + setShowingDeleteWarning(false)} + animation={false} + > + + Delete from wordlist? + + + Delete{" "} + {{ p: entry.p, f: entry.f }}{" "} + from your wordlist? + + + + + + +
+ ); } function explodeEntry(entry: T.DictionaryEntry): T.DictionaryEntry { - return { - ...entry, - p: entry.p.split("").join(" "), - }; + return { + ...entry, + p: entry.p.split("").join(" "), + }; } function inflectionSubUrl(pattern: T.InflectionPattern): string { - return pattern === 0 - ? "" - : pattern === 1 - ? "#1-basic" - : pattern === 2 - ? "#2-words-ending-in-an-unstressed-ی---ey" - : pattern === 3 - ? "#3-words-ending-in-a-stressed-ی---éy" - : pattern === 4 - ? "#4-words-with-the-pashtoon-pattern" - : pattern === 5 - ? "#5-shorter-words-that-squish" - // : pattern === 6 - : "#6-inanimate-feminine-nouns-ending-in-ي---ee" + return pattern === 0 + ? "" + : pattern === 1 + ? "#1-basic" + : pattern === 2 + ? "#2-words-ending-in-an-unstressed-ی---ey" + : pattern === 3 + ? "#3-words-ending-in-a-stressed-ی---éy" + : pattern === 4 + ? "#4-words-with-the-pashtoon-pattern" + : pattern === 5 + ? "#5-shorter-words-that-squish" + : // : pattern === 6 + "#6-inanimate-feminine-nouns-ending-in-ي---ee"; } -export default IsolatedEntry; \ No newline at end of file +export default IsolatedEntry;