From fd86dc15734f3b7126d88f0130897c597100e30a Mon Sep 17 00:00:00 2001 From: polwex Date: Thu, 15 May 2025 20:32:25 +0700 Subject: m --- src/zoom/Word.tsx | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/zoom/Word.tsx (limited to 'src/zoom/Word.tsx') diff --git a/src/zoom/Word.tsx b/src/zoom/Word.tsx new file mode 100644 index 0000000..c665004 --- /dev/null +++ b/src/zoom/Word.tsx @@ -0,0 +1,194 @@ +import React, { memo, useCallback, useEffect, useState } from "react"; +import { motion } from "motion/react"; +import { ViewProps, LoadingStatus, WordData } from "./logic/types"; +// import { fetchWord } from "../logic/calls"; +import { wordVariants, createHoverEffect } from "./animations"; +import { useZoom } from "./hooks/useZoom"; +import { NLP } from "sortug-ai"; + +interface Props extends ViewProps { + word: NLP.Stanza.Word; +} + +function Word({ rawText, context, idx, word }: Props) { + const { viewState, handleElementClick } = useZoom(); + const { level, wIndex } = viewState; + const selected = wIndex === idx; + const isFocused = level === "word" && selected; + + // State for word data + const [loading, setLoading] = useState("pending"); + const [wordData, setData] = useState(null); + const [error, setError] = useState(null); + + // Fetch word details when selected + const getMeaning = useCallback(() => { + setLoading("loading"); + + // Try to fetch the word data + // fetchWord(rawText, "en") + // .then((res) => { + // if ("error" in res) { + // setError(`Error loading word data: ${res.error}`); + // setLoading("error"); + // } else { + // setData(res.ok); + // setLoading("success"); + // } + // }) + // .catch((err) => { + // setError(`Failed to fetch word data: ${err.message}`); + // setLoading("error"); + // }); + }, [rawText]); + + // Load word data when the word is selected + useEffect(() => { + if (isFocused && !wordData && loading === "pending") { + getMeaning(); + } + }, [isFocused, getMeaning, wordData, loading]); + + return ( + <> + {/* Overlay backdrop when word is selected */} + {isFocused && } + + handleElementClick(e, idx)} + whileHover={ + level === "clause" + ? createHoverEffect(level, "clause", "255, 200, 200") + : {} + } + style={{ + backgroundColor: isFocused ? "white" : undefined, + boxShadow: isFocused ? "0 8px 32px rgba(0, 0, 0, 0.15)" : undefined, + }} + > + {level === "clause" || !selected ? ( + {rawText} + ) : ( +
+ {loading === "loading" && ( +
+
+

Loading word information...

+
+ )} + + {loading === "error" && ( +
+

{error || "Failed to load word information"}

+
+ )} + + {loading === "success" && wordData && ( +
+
+

{wordData.spelling}

+ + {/* Syllables section moved to header - for next level of zoom */} +
+ {Array.from({ length: wordData.syllables || 1 }).map( + (_, i) => { + // Create a simple syllable division (not linguistically accurate) + const syllableLength = Math.ceil( + rawText.length / (wordData.syllables || 1), + ); + const start = i * syllableLength; + const end = Math.min( + start + syllableLength, + rawText.length, + ); + const syllable = rawText.substring(start, end); + + return ( + { + e.stopPropagation(); + handleElementClick(e, i); + }} + > + {syllable} + + ); + }, + )} +
+
+ + {/* Pronunciation */} + {wordData.ipa && wordData.ipa.length > 0 && ( +
+

Pronunciation

+ {wordData.ipa.map((pronunciation, i) => ( +
+ {pronunciation.ipa} + {pronunciation.tags && + pronunciation.tags.length > 0 && ( + + {pronunciation.tags.join(", ")} + + )} +
+ ))} +
+ )} + + {/* Word Senses/Meanings */} + {wordData.senses && wordData.senses.length > 0 && ( +
+

Meanings

+ + {wordData.senses.map((sense, i) => ( +
+
+ {sense.pos && ( + {sense.pos} + )} + {sense.etymology && ( + {sense.etymology} + )} +
+ + {sense.senses && sense.senses.length > 0 && ( +
    + {sense.senses.map((subSense, j) => ( +
  • + {subSense.glosses && + subSense.glosses.length > 0 && ( +
    + {subSense.glosses.join("; ")} +
    + )} +
  • + ))} +
+ )} +
+ ))} +
+ )} +
+ )} +
+ )} + + + ); +} + +export default memo(Word); -- cgit v1.2.3