summaryrefslogtreecommitdiff
path: root/packages/prosody-ui/src/components
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-11-23 01:12:53 +0700
committerpolwex <polwex@sortug.com>2025-11-23 01:12:53 +0700
commitcb1b56f5a0eddbf77446f415f2beda57c8305f85 (patch)
treed333ca5c143063af8ee1b2f9e2d1d25f8ef2007c /packages/prosody-ui/src/components
wut
Diffstat (limited to 'packages/prosody-ui/src/components')
-rw-r--r--packages/prosody-ui/src/components/Sentence.tsx57
-rw-r--r--packages/prosody-ui/src/components/Word.tsx119
-rw-r--r--packages/prosody-ui/src/components/sentence.css272
-rw-r--r--packages/prosody-ui/src/components/word.css0
4 files changed, 448 insertions, 0 deletions
diff --git a/packages/prosody-ui/src/components/Sentence.tsx b/packages/prosody-ui/src/components/Sentence.tsx
new file mode 100644
index 0000000..33144ac
--- /dev/null
+++ b/packages/prosody-ui/src/components/Sentence.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+import { notRandomFromArray } from "sortug";
+import "./sentence.css";
+
+export function ColoredText({
+ frags,
+ fn,
+ lang,
+}: {
+ frags: string[];
+ fn?: (s: string) => void;
+ lang?: string;
+}) {
+ return (
+ <>
+ {frags.map((s, i) => {
+ const prev = frags[i - 1];
+ const prevC = prev ? notRandomFromArray(prev, colors) : "lol";
+ const color = notRandomFromArray(s, colors);
+ const opacity = prev && prevC === color ? 0.8 : 1;
+ const style = { color, opacity };
+ console.log({ style });
+ return <CTInner lang={lang} key={s + i} s={s} style={style} fn={fn} />;
+ })}
+ </>
+ );
+}
+
+export function CTInner({
+ s,
+ style,
+ fn,
+ lang,
+}: {
+ s: string;
+ style: any;
+ fn?: (s: string) => void;
+ lang?: string;
+}) {
+ function handleClick(e: React.MouseEvent<HTMLSpanElement>) {
+ console.log(!!fn, "fn");
+ if (fn) fn(e.currentTarget.innerText.trim());
+ }
+ return (
+ <span lang={lang} onClick={handleClick} className="word cp" style={style}>
+ {s}
+ </span>
+ );
+}
+export const colors = [
+ "#8c2c2c",
+ "#000000",
+ "#ffd400",
+ "#1513a0",
+ "#7e7e7e",
+ "1eb52d",
+];
diff --git a/packages/prosody-ui/src/components/Word.tsx b/packages/prosody-ui/src/components/Word.tsx
new file mode 100644
index 0000000..82939ce
--- /dev/null
+++ b/packages/prosody-ui/src/components/Word.tsx
@@ -0,0 +1,119 @@
+import React, { useCallback, useEffect, useState } from "react";
+import spinner from "../assets/icons/spinner.svg";
+import likeIcon from "../assets/icons/heart.svg";
+import commentsIcon from "../assets/icons/quote.svg";
+import shareIcon from "../assets/icons/share.svg";
+import fontIcon from "../assets/icons/font.svg";
+import bookmarkIcon from "../assets/icons/bookmark.svg";
+import speakerIcon from "../assets/icons/speaker.svg";
+import type { AnalyzeRes, Meaning } from "../logic/types";
+import { ColoredText } from "./Sentence.tsx";
+import { P, Span, useSpeechSynthesis } from "../hooks/useLang.tsx";
+
+function Word({ data, lang }: { data: AnalyzeRes; lang: string }) {
+ async function load() {
+ // const wiki = await fetchWiki(data.word);
+ // console.log(wiki, "wiki res");
+ // if ("ok" in wiki) setM(wiki.ok.meanings);
+ // else setError(wiki.error);
+ // setLoading(false);
+ }
+ useEffect(() => {
+ load();
+ }, []);
+ const [error, setError] = useState("");
+ const [loading, setLoading] = useState(true);
+ const [meanings, setM] = useState<Meaning[]>([]);
+ const [font, setFont] = useState(0);
+
+ function changeFont() {
+ if (font === 6) setFont(0);
+ else setFont(font + 1);
+ }
+ const { voices, speaking, speak, stop } = useSpeechSynthesis();
+ function playAudio() {
+ console.log({ voices, speaking });
+ console.log("word", data.word);
+ speak(data.word);
+ }
+
+ async function saveW() {}
+
+ return (
+ <div className={`font-${font}`} id="word-modal" title={data.word}>
+ <img className="font-icon cp" onClick={changeFont} src={fontIcon} />
+ <img className="save-icon cp" onClick={saveW} src={bookmarkIcon} />
+ <div className="original">
+ <ColoredText frags={data.syllables} />
+ </div>
+ <div className="pronunciation IPA flex1 flex-center">
+ <P>{`/${data.ipa.replace(/\s/g, "")}/`}</P>
+ <img onClick={playAudio} className="icon cp" src={speakerIcon} />
+ </div>
+ <div className="meanings">
+ {loading ? (
+ <img src={spinner} className="spinner bc" />
+ ) : (
+ meanings.map((m) => (
+ <div key={JSON.stringify(m)} className="meaning">
+ <div className="pos">
+ <Span>{m.pos}</Span>
+ </div>
+ <ol>
+ {m.meaning.map((t, i) => (
+ <li key={t + i} className="translation">
+ <P>{t}</P>
+ </li>
+ ))}
+ </ol>
+ </div>
+ ))
+ )}
+ {error && <div className="error">{error}</div>}
+ </div>
+ </div>
+ );
+}
+
+export default Word;
+
+// function FloatingButtons({
+// tweet,
+// avatar,
+// changeFont,
+// }: {
+// tweet: Tweet;
+// avatar: string;
+// changeFont: any;
+// }) {
+// const { apiKeys, prompts, setModal } = useGlobalState();
+// function openUser() {}
+// function openComments() {}
+// function openShare() {}
+// async function doLike() {
+// const key = apiKeys.openai;
+// // TODO hide button if key not set
+// console.log(tweet.text, "whole text");
+// console.log(tweet.media, "media");
+// if (tweet.media[0] && !tweet.media[0].isVideo) {
+// const res = await vision(tweet.media[0].url, "", key);
+// console.log(res, "vision res");
+// }
+// // const res = await translate(tweet.text, prompts.translate,key);
+// // if ("ok" in res)
+// // setModal(<TranslationModal text={res.ok.choices[0].message.content}/>)
+// }
+// async function doBookmark() {}
+// return (
+// <div id="entry-icons">
+// <div className="avatar">
+// <img className="cp" onClick={openUser} src={avatar} />
+// </div>
+// <img className="cp" onClick={doLike} src={likeIcon} />
+// <img className="cp" onClick={openComments} src={commentsIcon} />
+// <img className="cp" onClick={doBookmark} src={bookmarkIcon} />
+// <img className="cp" onClick={openShare} src={shareIcon} />
+// <img className="cp" onClick={changeFont} src={fontIcon} />
+// </div>
+// );
+// }
diff --git a/packages/prosody-ui/src/components/sentence.css b/packages/prosody-ui/src/components/sentence.css
new file mode 100644
index 0000000..0bd0a49
--- /dev/null
+++ b/packages/prosody-ui/src/components/sentence.css
@@ -0,0 +1,272 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+#root>.spinner {
+ width: 100px;
+ height: 100px;
+}
+
+#entry>.spinner {
+ width: 80px;
+ height: 80px;
+}
+
+
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+#cookies {
+ & .active {
+ background-color: var(--huang);
+ }
+
+ & input {
+ margin-left: 1rem;
+ width: 100%;
+ }
+
+ & textarea {
+ width: 100%;
+ height: 500px;
+ resize: none;
+ outline: none;
+ }
+}
+
+#entry {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ padding: 1rem;
+ /* prov */
+ border: 2px solid black;
+
+ & div[lang="th"] {
+
+ & .tw-text,
+ & .tw-hashtag {
+ font-size: 3rem;
+ }
+ }
+
+ & .text-wrapper {
+ display: block;
+ margin: 0.5rem 0;
+ /* overflow: hidden; */
+ }
+
+ & .word {
+ display: inline-block;
+ transition: transform 0.3s ease;
+ }
+
+ & .word:hover {
+ transform: scale(1.4);
+ background-color: white;
+ }
+
+ & #tw-media {
+ max-width: 100%;
+
+ & img,
+ & video {
+ max-width: 100%;
+ }
+ }
+}
+
+#inner {
+ height: 100%;
+ max-height: 100%;
+ overflow-y: auto;
+}
+
+#entry-icons {
+ position: absolute;
+ bottom: 5%;
+ right: 5%;
+ width: 50px;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ & .avatar {
+ border: 2px solid black;
+ border-radius: 50%;
+ width: 50px;
+ height: 50px;
+ overflow: hidden;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+
+ & img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+}
+
+#word-modal {
+ position: relative;
+
+ & .font-icon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 32px;
+ height: 32px;
+ }
+
+ & .save-icon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 32px;
+ height: 32px;
+ }
+
+ & .original {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+ }
+
+ & .syllable {}
+
+ & .IPA {
+ font-size: 1.6rem;
+ line-height: 1.6rem;
+ & img{
+ width: 50px;
+ margin-left: 1rem;
+ }
+ }
+
+ & .meanings {
+
+ & .spinner {
+ width: 80px;
+ height: 80px;
+ }
+
+ & .meaning {
+ margin: 1rem auto;
+ }
+
+ & .pos {
+ font-size: 1.2rem;
+ margin-bottom: 0.3rem;
+ text-align: left;
+ }
+
+ & ol {
+ word-wrap: normal;
+ margin: auto;
+ text-align: left;
+ }
+ }
+}
+
+img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+.flex1{
+ width: 100%;
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+.flex-center{
+ justify-content: center;
+}
+
+/* p { */
+/* position: absolute; */
+/* top: 50%; */
+/* left: 50%; */
+/* transform: translate(-50%, -50%); */
+/* color: white; */
+/* background-color: rgba(0, 0, 0, 0.5); */
+/* padding: 10px; */
+/* border-radius: 5px; */
+/* } */
+#modal-bg{
+ height: 100vh;
+ width: 100vw;
+ background-color: rgb(0, 0, 0, 0.9);
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+}
+
+#modal-fg {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 80%;
+ z-index: 101;
+ transform: translate(-50%, -50%);
+ /* background-color: var(--background-color); */
+ background-color: lightgrey;
+ font-size: 1.2rem;
+ padding: 1rem;
+ max-height: 80%;
+ overflow-y: scroll;
+}
+
+
+.text-ipa{
+ font-size: 1.5rem;
+}
+.text-thai{
+ font-size: 1.5rem;
+}
+
+
+/* // new */
+.word.cp{
+ margin: 0 0.5ch;
+}
diff --git a/packages/prosody-ui/src/components/word.css b/packages/prosody-ui/src/components/word.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/prosody-ui/src/components/word.css