优化代码,拆分组件
This commit is contained in:
5174
package-lock.json
generated
5174
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,12 +10,9 @@
|
|||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-tailwind/react": "^2.1.10",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"edge-tts-universal": "^1.3.2",
|
"edge-tts-universal": "^1.3.2",
|
||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"rc-modal-sheet": "^1.0.2",
|
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Button from "@/components/Button";
|
import MyButton from "@/components/MyButton";
|
||||||
import IconClick from "@/components/IconClick";
|
import IconClick from "@/components/IconClick";
|
||||||
import IMAGES from "@/config/images";
|
import IMAGES from "@/config/images";
|
||||||
import { Letter, SupportedAlphabets } from "@/interfaces";
|
import { Letter, SupportedAlphabets } from "@/interfaces";
|
||||||
@@ -73,22 +73,22 @@ export default function MemoryCard({
|
|||||||
></IconClick>
|
></IconClick>
|
||||||
{more ? (
|
{more ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<MyButton
|
||||||
className="w-20"
|
className="w-20"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLetterDisplay(!letterDisplay);
|
setLetterDisplay(!letterDisplay);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{letterDisplay ? "隐藏字母" : "显示字母"}
|
{letterDisplay ? "隐藏字母" : "显示字母"}
|
||||||
</Button>
|
</MyButton>
|
||||||
<Button
|
<MyButton
|
||||||
className="w-20"
|
className="w-20"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIPADisplay(!ipaDisplay);
|
setIPADisplay(!ipaDisplay);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ipaDisplay ? "隐藏IPA" : "显示IPA"}
|
{ipaDisplay ? "隐藏IPA" : "显示IPA"}
|
||||||
</Button>
|
</MyButton>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Button from "@/components/Button";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
import { Letter, SupportedAlphabets } from "@/interfaces";
|
import { Letter, SupportedAlphabets } from "@/interfaces";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import MemoryCard from "./MemoryCard";
|
import MemoryCard from "./MemoryCard";
|
||||||
@@ -60,18 +60,18 @@ export default function Alphabet() {
|
|||||||
<div className="border border-gray-200 m-4 mt-4 flex flex-col justify-center items-center p-4 rounded-2xl gap-2">
|
<div className="border border-gray-200 m-4 mt-4 flex flex-col justify-center items-center p-4 rounded-2xl gap-2">
|
||||||
<span className="text-2xl md:text-3xl">请选择您想学习的字符</span>
|
<span className="text-2xl md:text-3xl">请选择您想学习的字符</span>
|
||||||
<div className="flex gap-1 flex-wrap">
|
<div className="flex gap-1 flex-wrap">
|
||||||
<Button onClick={() => setChosenAlphabet("japanese")}>
|
<LightButton onClick={() => setChosenAlphabet("japanese")}>
|
||||||
日语假名
|
日语假名
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button onClick={() => setChosenAlphabet("english")}>
|
<LightButton onClick={() => setChosenAlphabet("english")}>
|
||||||
英文字母
|
英文字母
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button onClick={() => setChosenAlphabet("uyghur")}>
|
<LightButton onClick={() => setChosenAlphabet("uyghur")}>
|
||||||
维吾尔字母
|
维吾尔字母
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button onClick={() => setChosenAlphabet("esperanto")}>
|
<LightButton onClick={() => setChosenAlphabet("esperanto")}>
|
||||||
世界语字母
|
世界语字母
|
||||||
</Button>
|
</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -28,5 +28,3 @@ body {
|
|||||||
.code-block {
|
.code-block {
|
||||||
font-family: var(--font-geist-mono), monospace;
|
font-family: var(--font-geist-mono), monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@source '../../node_modules/rc-modal-sheet/**/*.js';
|
|
||||||
|
|||||||
65
src/app/memorize/Choose.tsx
Normal file
65
src/app/memorize/Choose.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
|
import ACard from "@/components/cards/ACard";
|
||||||
|
import BCard from "@/components/cards/BCard";
|
||||||
|
import Window from "@/components/Window";
|
||||||
|
import { LOCALES } from "@/config/locales";
|
||||||
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
import { WordData } from "./page";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
setEditPage: Dispatch<SetStateAction<"choose" | "edit">>;
|
||||||
|
wordData: WordData;
|
||||||
|
setWordData: Dispatch<SetStateAction<WordData>>;
|
||||||
|
localeKey: 0 | 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Choose({
|
||||||
|
setEditPage,
|
||||||
|
wordData,
|
||||||
|
setWordData,
|
||||||
|
localeKey,
|
||||||
|
}: Props) {
|
||||||
|
const [chosenLocale, setChosenLocale] = useState<
|
||||||
|
(typeof LOCALES)[number] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const handleChooseClick = () => {
|
||||||
|
if (chosenLocale) {
|
||||||
|
setWordData({
|
||||||
|
locales: [
|
||||||
|
localeKey === 0 ? chosenLocale : wordData.locales[0],
|
||||||
|
localeKey === 1 ? chosenLocale : wordData.locales[1],
|
||||||
|
],
|
||||||
|
wordPairs: wordData.wordPairs,
|
||||||
|
});
|
||||||
|
setEditPage("edit");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Window>
|
||||||
|
<ACard className="flex flex-col">
|
||||||
|
<div className="overflow-y-auto flex-1 border border-gray-200 rounded-2xl p-2 grid grid-cols-6 gap-2">
|
||||||
|
{LOCALES.map((locale, index) => (
|
||||||
|
<LightButton
|
||||||
|
key={index}
|
||||||
|
className="w-26"
|
||||||
|
selected={locale === chosenLocale}
|
||||||
|
onClick={() => setChosenLocale(locale)}
|
||||||
|
>
|
||||||
|
{locale}
|
||||||
|
</LightButton>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center justify-center">
|
||||||
|
<BCard className="flex gap-2 justify-center items-center w-fit">
|
||||||
|
<LightButton onClick={handleChooseClick}>choose</LightButton>
|
||||||
|
<LightButton onClick={() => setEditPage("edit")}>
|
||||||
|
Back
|
||||||
|
</LightButton>
|
||||||
|
</BCard>
|
||||||
|
</div>
|
||||||
|
</ACard>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
}
|
||||||
97
src/app/memorize/Edit.tsx
Normal file
97
src/app/memorize/Edit.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
|
import ACard from "@/components/cards/ACard";
|
||||||
|
import BCard from "@/components/cards/BCard";
|
||||||
|
import Window from "@/components/Window";
|
||||||
|
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
|
||||||
|
import DarkButton from "@/components/buttons/DarkButton";
|
||||||
|
import { WordData } from "./page";
|
||||||
|
import Choose from "./Choose";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
setPage: Dispatch<SetStateAction<"start" | "main" | "edit">>;
|
||||||
|
wordData: WordData;
|
||||||
|
setWordData: Dispatch<SetStateAction<WordData>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Edit({ setPage, wordData, setWordData }: Props) {
|
||||||
|
const [localeKey, setLocaleKey] = useState<0 | 1>(0);
|
||||||
|
const [editPage, setEditPage] = useState<"choose" | "edit">("edit");
|
||||||
|
const convertIntoWordData = (text: string) => {
|
||||||
|
const t1 = text
|
||||||
|
.replace(",", ",")
|
||||||
|
.split("\n")
|
||||||
|
.map((v) => v.trim())
|
||||||
|
.filter((v) => v.includes(","));
|
||||||
|
const t2 = t1
|
||||||
|
.map((v) => {
|
||||||
|
const [left, right] = v.split(",", 2).map((v) => v.trim());
|
||||||
|
if (left && right) return [left, right] as [string, string];
|
||||||
|
else return null;
|
||||||
|
})
|
||||||
|
.filter((v) => v !== null);
|
||||||
|
const new_data: WordData = {
|
||||||
|
locales: [...wordData.locales],
|
||||||
|
wordPairs: t2,
|
||||||
|
};
|
||||||
|
setWordData(new_data);
|
||||||
|
};
|
||||||
|
const convertFromWordData = () => {
|
||||||
|
let result = "";
|
||||||
|
for (const pair of wordData.wordPairs) {
|
||||||
|
result += `${pair[0]}, ${pair[1]}\n`;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
let input = convertFromWordData();
|
||||||
|
const handleSave = () => {
|
||||||
|
convertIntoWordData(input);
|
||||||
|
};
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
input = e.target.value;
|
||||||
|
};
|
||||||
|
if (editPage === "edit")
|
||||||
|
return (
|
||||||
|
<Window>
|
||||||
|
<ACard className="flex flex-col">
|
||||||
|
<textarea
|
||||||
|
className="flex-1 text-gray-800 font-serif text-2xl border-gray-200 border rounded-2xl w-full resize-none outline-0 p-2"
|
||||||
|
defaultValue={input}
|
||||||
|
onChange={handleChange}
|
||||||
|
></textarea>
|
||||||
|
<div className="w-full flex items-center justify-center">
|
||||||
|
<BCard className="flex gap-2 justify-center items-center w-fit">
|
||||||
|
<LightButton onClick={() => setPage("main")}>
|
||||||
|
Back
|
||||||
|
</LightButton>
|
||||||
|
<LightButton onClick={handleSave}>Save Text</LightButton>
|
||||||
|
<DarkButton
|
||||||
|
onClick={() => {
|
||||||
|
setLocaleKey(0);
|
||||||
|
setEditPage("choose");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Choose Locale 1
|
||||||
|
</DarkButton>
|
||||||
|
<DarkButton
|
||||||
|
onClick={() => {
|
||||||
|
setLocaleKey(1);
|
||||||
|
setEditPage("choose");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Choose Locale 2
|
||||||
|
</DarkButton>
|
||||||
|
</BCard>
|
||||||
|
</div>
|
||||||
|
</ACard>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
if (editPage === "choose")
|
||||||
|
return (
|
||||||
|
<Choose
|
||||||
|
wordData={wordData}
|
||||||
|
setEditPage={setEditPage}
|
||||||
|
setWordData={setWordData}
|
||||||
|
localeKey={localeKey}
|
||||||
|
></Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
src/app/memorize/Main.tsx
Normal file
40
src/app/memorize/Main.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
|
import ACard from "@/components/cards/ACard";
|
||||||
|
import BCard from "@/components/cards/BCard";
|
||||||
|
import Window from "@/components/Window";
|
||||||
|
import { WordData } from "./page";
|
||||||
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
wordData: WordData;
|
||||||
|
setPage: Dispatch<SetStateAction<"start" | "main" | "edit">>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Main({ wordData, setPage: setPage }: Props) {
|
||||||
|
return (
|
||||||
|
<Window>
|
||||||
|
<ACard className="flex-col flex">
|
||||||
|
<h1 className="text-center font-extrabold text-4xl text-gray-800 m-2 mb-4">
|
||||||
|
Memorize
|
||||||
|
</h1>
|
||||||
|
<div className="flex-1 font-serif text-2xl w-full h-full text-gray-800">
|
||||||
|
<BCard>
|
||||||
|
<p>locale 1 {wordData.locales[0]}</p>
|
||||||
|
<p>locale 2 {wordData.locales[1]}</p>
|
||||||
|
<p>Total Words: {wordData.wordPairs.length}</p>
|
||||||
|
</BCard>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center justify-center">
|
||||||
|
<BCard className="flex gap-2 justify-center items-center w-fit">
|
||||||
|
<LightButton onClick={() => setPage("start")}>
|
||||||
|
Start
|
||||||
|
</LightButton>
|
||||||
|
<LightButton>Load</LightButton>
|
||||||
|
<LightButton>Save</LightButton>
|
||||||
|
<LightButton onClick={() => setPage("edit")}>Edit</LightButton>
|
||||||
|
</BCard>
|
||||||
|
</div>
|
||||||
|
</ACard>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
src/app/memorize/Start.tsx
Normal file
53
src/app/memorize/Start.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
|
import ACard from "@/components/cards/ACard";
|
||||||
|
import BCard from "@/components/cards/BCard";
|
||||||
|
import Window from "@/components/Window";
|
||||||
|
import { WordData } from "./page";
|
||||||
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
wordData: WordData;
|
||||||
|
setPage: Dispatch<SetStateAction<"start" | "main" | "edit">>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Start({ wordData, setPage }: Props) {
|
||||||
|
const [display, setDisplay] = useState<"ask" | "show">("ask");
|
||||||
|
const [wordPair, setWordPair] = useState(
|
||||||
|
wordData.wordPairs[Math.floor(Math.random() * wordData.wordPairs.length)],
|
||||||
|
);
|
||||||
|
const show = () => {
|
||||||
|
setDisplay("show");
|
||||||
|
};
|
||||||
|
const next = () => {
|
||||||
|
setDisplay("ask");
|
||||||
|
setWordPair(
|
||||||
|
wordData.wordPairs[Math.floor(Math.random() * wordData.wordPairs.length)],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Window>
|
||||||
|
<div className="flex-col flex items-center h-96 w-[66dvw]">
|
||||||
|
<div className="flex-1 w-full p-4 gap-4 flex flex-col text-5xl font-serif">
|
||||||
|
<div className="p-4 w-full border border-white rounded shadow">
|
||||||
|
{wordPair[0]}
|
||||||
|
</div>
|
||||||
|
{display === "show" && (
|
||||||
|
<div className="p-4 w-full flex-1 border border-white rounded shadow">
|
||||||
|
{wordPair[1]}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center justify-center">
|
||||||
|
<div className="flex gap-2 justify-center items-center w-fit">
|
||||||
|
{display === "ask" ? (
|
||||||
|
<LightButton onClick={show}>Show</LightButton>
|
||||||
|
) : (
|
||||||
|
<LightButton onClick={next}>Next</LightButton>
|
||||||
|
)}
|
||||||
|
<LightButton onClick={() => setPage("main")}>Exit</LightButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,160 +1,41 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Button from "@/components/Button";
|
import { useState } from "react";
|
||||||
import { Select, Option } from "@material-tailwind/react";
|
import Main from "./Main";
|
||||||
import { ChangeEvent, useState } from "react";
|
import Edit from "./Edit";
|
||||||
|
import Start from "./Start";
|
||||||
|
|
||||||
interface ACardProps {
|
export interface WordData {
|
||||||
children?: React.ReactNode;
|
locales: [string, string];
|
||||||
className?: string;
|
wordPairs: [string, string][];
|
||||||
}
|
|
||||||
|
|
||||||
function ACard({ children, className }: ACardProps) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`w-[61vw] h-96 p-2 shadow-2xl bg-[#00BCD4] rounded-xl ${className}`}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BCard {
|
|
||||||
children?: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
function BCard({ children, className }: BCard) {
|
|
||||||
return (
|
|
||||||
<div className={`border border-[#0097A7] rounded-xl p-2 ${className}`}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WordData {
|
|
||||||
locale1: string;
|
|
||||||
locale2: string;
|
|
||||||
data: Record<string, string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Memorize() {
|
export default function Memorize() {
|
||||||
const [pageState, setPageState] = useState<
|
const [page, setPage] = useState<"start" | "main" | "edit">(
|
||||||
"choose" | "start" | "main" | "edit"
|
"start",
|
||||||
>("edit");
|
);
|
||||||
const [wordData, setWordData] = useState<WordData>({
|
const [wordData, setWordData] = useState<WordData>({
|
||||||
locale1: "en-US",
|
locales: ["en-US", "zh-CN"],
|
||||||
locale2: "zh-CN",
|
wordPairs: [
|
||||||
data: { hello: "你好" },
|
['hello', '你好'],
|
||||||
|
['world', '世界'],
|
||||||
|
['brutal', '残酷的'],
|
||||||
|
['apple', '苹果'],
|
||||||
|
['banana', '香蕉'],
|
||||||
|
['orange', '橙子'],
|
||||||
|
['grape', '葡萄'],
|
||||||
|
]
|
||||||
});
|
});
|
||||||
if (pageState === "main") {
|
if (page === "main")
|
||||||
|
return <Main wordData={wordData} setPage={setPage}></Main>;
|
||||||
|
if (page === "edit")
|
||||||
return (
|
return (
|
||||||
<>
|
<Edit
|
||||||
<div className="w-full h-screen flex justify-center items-center">
|
setPage={setPage}
|
||||||
<ACard>
|
wordData={wordData}
|
||||||
<h1 className="text-center font-extrabold text-4xl text-white m-2 mb-4">
|
setWordData={setWordData}
|
||||||
Memorize
|
></Edit>
|
||||||
</h1>
|
|
||||||
<div className="w-full text-white">
|
|
||||||
<BCard>
|
|
||||||
<p>Lang1: {wordData.locale1}</p>
|
|
||||||
<p>Lang2: {wordData.locale2}</p>
|
|
||||||
<p>Total Words: {Object.keys(wordData.data).length}</p>
|
|
||||||
</BCard>
|
|
||||||
</div>
|
|
||||||
<div className="w-full flex items-center justify-center">
|
|
||||||
<BCard className="flex gap-2 justify-center items-center w-fit">
|
|
||||||
<Button>Start</Button>
|
|
||||||
<Button>Load</Button>
|
|
||||||
<Button>Save</Button>
|
|
||||||
<Button onClick={() => setPageState("edit")}>Edit</Button>
|
|
||||||
</BCard>
|
|
||||||
</div>
|
|
||||||
</ACard>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
if (page === "start")
|
||||||
if (pageState === "choose") {
|
return <Start setPage={setPage} wordData={wordData}></Start>;
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
if (pageState === "start") {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
if (pageState === "edit") {
|
|
||||||
const convertIntoWordData = (text: string) => {
|
|
||||||
const t1 = text
|
|
||||||
.split("\n")
|
|
||||||
.map((v) => v.trim())
|
|
||||||
.filter((v) => v.includes(","));
|
|
||||||
const t2 = t1.map((v) => {
|
|
||||||
const [left, right] = v.split(",", 2).map((v) => v.trim());
|
|
||||||
if (left && right)
|
|
||||||
return {
|
|
||||||
[left]: right,
|
|
||||||
};
|
|
||||||
else return {};
|
|
||||||
});
|
|
||||||
const new_data = {
|
|
||||||
locale1: wordData.locale1,
|
|
||||||
locale2: wordData.locale2,
|
|
||||||
data: Object.assign({}, ...t2),
|
|
||||||
};
|
|
||||||
setWordData(new_data);
|
|
||||||
};
|
|
||||||
const convertFromWordData = () => {
|
|
||||||
let result = "";
|
|
||||||
for (const k in wordData.data) {
|
|
||||||
result += `${k}, ${wordData.data[k]}\n`;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
let input = convertFromWordData();
|
|
||||||
const handleSave = () => {
|
|
||||||
convertIntoWordData(input);
|
|
||||||
setPageState("main");
|
|
||||||
};
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
input = e.target.value;
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-screen flex flex-col justify-center items-center">
|
|
||||||
<ACard className="">
|
|
||||||
<textarea
|
|
||||||
className="text-white border-gray-200 border rounded-2xl w-full h-50 resize-none outline-0 p-2"
|
|
||||||
defaultValue={input}
|
|
||||||
onChange={handleChange}
|
|
||||||
></textarea>
|
|
||||||
<div className="w-full flex items-center justify-center">
|
|
||||||
<BCard className="flex gap-2 justify-center items-center w-fit">
|
|
||||||
<Button>choose locale1</Button>
|
|
||||||
<Button>choose locale2</Button>
|
|
||||||
<Button onClick={() => setPageState("main")}>Cancel</Button>
|
|
||||||
<Button onClick={handleSave}>Save</Button>
|
|
||||||
<button className="inline-flex items-center justify-center border align-middle select-none font-sans font-medium text-center transition-all duration-300 ease-in disabled:opacity-50 disabled:shadow-none disabled:cursor-not-allowed data-[shape=pill]:rounded-full data-[width=full]:w-full focus:shadow-none text-sm rounded-md py-2 px-4 shadow-sm hover:shadow-md bg-slate-800 border-slate-800 text-slate-50 hover:bg-slate-700 hover:border-slate-700">
|
|
||||||
Button
|
|
||||||
</button>
|
|
||||||
</BCard>
|
|
||||||
</div>
|
|
||||||
<div className="w-48"></div>
|
|
||||||
</ACard>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* <Select
|
|
||||||
label="选择语言"
|
|
||||||
placeholder="请选择语言"
|
|
||||||
onResize={undefined}
|
|
||||||
onResizeCapture={undefined}
|
|
||||||
onPointerEnterCapture={undefined}
|
|
||||||
onPointerLeaveCapture={undefined}
|
|
||||||
>
|
|
||||||
<Option>Material Tailwind HTML</Option>
|
|
||||||
<Option>Material Tailwind React</Option>
|
|
||||||
<Option>Material Tailwind Vue</Option>
|
|
||||||
<Option>Material Tailwind Angular</Option>
|
|
||||||
<Option>Material Tailwind Svelte</Option>
|
|
||||||
</Select> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Button from "@/components/Button";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
|
||||||
export default function UploadArea({
|
export default function UploadArea({
|
||||||
@@ -38,8 +38,8 @@ export default function UploadArea({
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col gap-2 m-2">
|
<div className="w-full flex flex-col gap-2 m-2">
|
||||||
<Button onClick={uploadVideo}>上传视频</Button>
|
<LightButton onClick={uploadVideo}>上传视频</LightButton>
|
||||||
<Button onClick={uploadSRT}>上传字幕</Button>
|
<LightButton onClick={uploadSRT}>上传字幕</LightButton>
|
||||||
<input type="file" className="hidden" ref={inputRef} />
|
<input type="file" className="hidden" ref={inputRef} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
|
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
|
||||||
import SubtitleDisplay from "./SubtitleDisplay";
|
import SubtitleDisplay from "./SubtitleDisplay";
|
||||||
import Button from "@/components/Button";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
||||||
|
|
||||||
type VideoPanelProps = {
|
type VideoPanelProps = {
|
||||||
@@ -184,15 +184,15 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>(
|
|||||||
></video>
|
></video>
|
||||||
<SubtitleDisplay subtitle={subtitle}></SubtitleDisplay>
|
<SubtitleDisplay subtitle={subtitle}></SubtitleDisplay>
|
||||||
<div className="buttons flex mt-2 gap-2 flex-wrap">
|
<div className="buttons flex mt-2 gap-2 flex-wrap">
|
||||||
<Button onClick={togglePlayPause}>
|
<LightButton onClick={togglePlayPause}>
|
||||||
{isPlaying ? "暂停" : "播放"}
|
{isPlaying ? "暂停" : "播放"}
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button onClick={previous}>上句</Button>
|
<LightButton onClick={previous}>上句</LightButton>
|
||||||
<Button onClick={next}>下句</Button>
|
<LightButton onClick={next}>下句</LightButton>
|
||||||
<Button onClick={restart}>句首</Button>
|
<LightButton onClick={restart}>句首</LightButton>
|
||||||
<Button
|
<LightButton
|
||||||
onClick={handleAutoPauseToggle}
|
onClick={handleAutoPauseToggle}
|
||||||
>{`自动暂停(${autoPause ? "是" : "否"})`}</Button>
|
>{`自动暂停(${autoPause ? "是" : "否"})`}</LightButton>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
className="seekbar"
|
className="seekbar"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Button from "@/components/Button";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
import IconClick from "@/components/IconClick";
|
import IconClick from "@/components/IconClick";
|
||||||
import IMAGES from "@/config/images";
|
import IMAGES from "@/config/images";
|
||||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||||
@@ -313,20 +313,20 @@ export default function TextSpeaker() {
|
|||||||
className={`${saving ? "bg-gray-200" : ""}`}
|
className={`${saving ? "bg-gray-200" : ""}`}
|
||||||
></IconClick>
|
></IconClick>
|
||||||
<div className="w-full flex flex-row flex-wrap gap-2 justify-center items-center">
|
<div className="w-full flex flex-row flex-wrap gap-2 justify-center items-center">
|
||||||
<Button
|
<LightButton
|
||||||
selected={ipaEnabled}
|
selected={ipaEnabled}
|
||||||
onClick={() => setIPAEnabled(!ipaEnabled)}
|
onClick={() => setIPAEnabled(!ipaEnabled)}
|
||||||
>
|
>
|
||||||
生成IPA
|
生成IPA
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button
|
<LightButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowSaveList(!showSaveList);
|
setShowSaveList(!showSaveList);
|
||||||
}}
|
}}
|
||||||
selected={showSaveList}
|
selected={showSaveList}
|
||||||
>
|
>
|
||||||
查看保存项
|
查看保存项
|
||||||
</Button>
|
</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ChangeEvent, useState } from "react";
|
import { ChangeEvent, useState } from "react";
|
||||||
import Button from "@/components/Button";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
import IconClick from "@/components/IconClick";
|
import IconClick from "@/components/IconClick";
|
||||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||||
import IMAGES from "@/config/images";
|
import IMAGES from "@/config/images";
|
||||||
@@ -208,12 +208,12 @@ export default function Translator() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="option1 w-full flex flex-row justify-between items-center">
|
<div className="option1 w-full flex flex-row justify-between items-center">
|
||||||
<span>detect language</span>
|
<span>detect language</span>
|
||||||
<Button
|
<LightButton
|
||||||
selected={ipaEnabled}
|
selected={ipaEnabled}
|
||||||
onClick={() => setIPAEnabled(!ipaEnabled)}
|
onClick={() => setIPAEnabled(!ipaEnabled)}
|
||||||
>
|
>
|
||||||
generate ipa
|
generate ipa
|
||||||
</Button>
|
</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card2 w-full md:w-1/2 flex flex-col-reverse gap-2">
|
<div className="card2 w-full md:w-1/2 flex flex-col-reverse gap-2">
|
||||||
@@ -240,33 +240,33 @@ export default function Translator() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="option2 w-full flex gap-1 items-center flex-wrap">
|
<div className="option2 w-full flex gap-1 items-center flex-wrap">
|
||||||
<span>translate into</span>
|
<span>translate into</span>
|
||||||
<Button
|
<LightButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTargetLang("Chinese");
|
setTargetLang("Chinese");
|
||||||
}}
|
}}
|
||||||
selected={targetLang === "Chinese"}
|
selected={targetLang === "Chinese"}
|
||||||
>
|
>
|
||||||
Chinese
|
Chinese
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button
|
<LightButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTargetLang("English");
|
setTargetLang("English");
|
||||||
}}
|
}}
|
||||||
selected={targetLang === "English"}
|
selected={targetLang === "English"}
|
||||||
>
|
>
|
||||||
English
|
English
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button
|
<LightButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTargetLang("Italian");
|
setTargetLang("Italian");
|
||||||
}}
|
}}
|
||||||
selected={targetLang === "Italian"}
|
selected={targetLang === "Italian"}
|
||||||
>
|
>
|
||||||
Italian
|
Italian
|
||||||
</Button>
|
</LightButton>
|
||||||
<Button onClick={inputLanguage} selected={!tl.includes(targetLang)}>
|
<LightButton onClick={inputLanguage} selected={!tl.includes(targetLang)}>
|
||||||
{"Other" + (tl.includes(targetLang) ? "" : ": " + targetLang)}
|
{"Other" + (tl.includes(targetLang) ? "" : ": " + targetLang)}
|
||||||
</Button>
|
</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import TheBoard from "@/app/word-board/TheBoard";
|
import TheBoard from "@/app/word-board/TheBoard";
|
||||||
import Button from "../../components/Button";
|
import LightButton from "../../components/buttons/LightButton";
|
||||||
import { KeyboardEvent, useRef, useState } from "react";
|
import { KeyboardEvent, useRef, useState } from "react";
|
||||||
import { Word } from "@/interfaces";
|
import { Word } from "@/interfaces";
|
||||||
import {
|
import {
|
||||||
@@ -165,12 +165,12 @@ export default function WordBoard() {
|
|||||||
type="text"
|
type="text"
|
||||||
className="focus:outline-none border-b-2 border-black"
|
className="focus:outline-none border-b-2 border-black"
|
||||||
/>
|
/>
|
||||||
<Button onClick={insertWord}>插入</Button>
|
<LightButton onClick={insertWord}>插入</LightButton>
|
||||||
<Button onClick={deleteWord}>删除</Button>
|
<LightButton onClick={deleteWord}>删除</LightButton>
|
||||||
<Button onClick={searchWord}>搜索</Button>
|
<LightButton onClick={searchWord}>搜索</LightButton>
|
||||||
<Button onClick={importWords}>导入</Button>
|
<LightButton onClick={importWords}>导入</LightButton>
|
||||||
<Button onClick={exportWords}>导出</Button>
|
<LightButton onClick={exportWords}>导出</LightButton>
|
||||||
<Button onClick={deleteAll}>删光</Button>
|
<LightButton onClick={deleteAll}>删光</LightButton>
|
||||||
{/* <Button label="朗读" onClick={readWordAloud}></Button> */}
|
{/* <Button label="朗读" onClick={readWordAloud}></Button> */}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|||||||
12
src/components/Window.tsx
Normal file
12
src/components/Window.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
interface WindowProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
export default function Window({ children }: WindowProps) {
|
||||||
|
return (
|
||||||
|
<div className="w-full bg-gray-200 h-screen flex justify-center items-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export default function Button({
|
import PlainButton from "./PlainButton";
|
||||||
|
|
||||||
|
export default function DarkButton({
|
||||||
onClick,
|
onClick,
|
||||||
className,
|
className,
|
||||||
selected,
|
selected,
|
||||||
@@ -10,11 +12,11 @@ export default function Button({
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<PlainButton
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`px-2 py-1 rounded shadow-2xs font-bold hover:bg-gray-300 hover:cursor-pointer ${selected ? "bg-gray-300" : "bg-white"} ${className}`}
|
className={`hover:bg-gray-600 text-white ${selected ? "bg-gray-600" : "bg-gray-800"} ${className}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</PlainButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
22
src/components/buttons/LightButton.tsx
Normal file
22
src/components/buttons/LightButton.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import PlainButton from "./PlainButton";
|
||||||
|
|
||||||
|
export default function LightButton({
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
selected,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
onClick?: () => void;
|
||||||
|
className?: string;
|
||||||
|
selected?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<PlainButton
|
||||||
|
onClick={onClick}
|
||||||
|
className={`hover:bg-gray-200 text-gray-800 ${selected ? "bg-gray-200" : "bg-white"} ${className}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</PlainButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
src/components/buttons/PlainButton.tsx
Normal file
18
src/components/buttons/PlainButton.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default function PlainButton({
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
onClick?: () => void;
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className={`px-2 py-1 rounded shadow font-bold hover:cursor-pointer ${className}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/components/cards/ACard.tsx
Normal file
16
src/components/cards/ACard.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
interface ACardProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ACard({ children, className }: ACardProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${className} w-[61vw] h-96 p-2 shadow-2xl bg-white rounded-xl`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
src/components/cards/BCard.tsx
Normal file
12
src/components/cards/BCard.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
interface BCardProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BCard({ children, className }: BCardProps) {
|
||||||
|
return (
|
||||||
|
<div className={`${className} rounded-xl p-2 shadow-xl`}>{children}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1209,4 +1209,12 @@ const VOICES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export { VOICES };
|
const LOCALES = Array.from(
|
||||||
|
new Set(
|
||||||
|
VOICES.map((v) => v.locale)
|
||||||
|
.filter((v) => v.length === 5)
|
||||||
|
.toSorted(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export { VOICES, LOCALES };
|
||||||
|
|||||||
Reference in New Issue
Block a user