优化代码,拆分组件
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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-tailwind/react": "^2.1.10",
|
||||
"clsx": "^2.1.1",
|
||||
"edge-tts-universal": "^1.3.2",
|
||||
"motion": "^12.23.24",
|
||||
"next": "15.5.3",
|
||||
"rc-modal-sheet": "^1.0.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"zod": "^3.25.76"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Button from "@/components/Button";
|
||||
import MyButton from "@/components/MyButton";
|
||||
import IconClick from "@/components/IconClick";
|
||||
import IMAGES from "@/config/images";
|
||||
import { Letter, SupportedAlphabets } from "@/interfaces";
|
||||
@@ -73,22 +73,22 @@ export default function MemoryCard({
|
||||
></IconClick>
|
||||
{more ? (
|
||||
<>
|
||||
<Button
|
||||
<MyButton
|
||||
className="w-20"
|
||||
onClick={() => {
|
||||
setLetterDisplay(!letterDisplay);
|
||||
}}
|
||||
>
|
||||
{letterDisplay ? "隐藏字母" : "显示字母"}
|
||||
</Button>
|
||||
<Button
|
||||
</MyButton>
|
||||
<MyButton
|
||||
className="w-20"
|
||||
onClick={() => {
|
||||
setIPADisplay(!ipaDisplay);
|
||||
}}
|
||||
>
|
||||
{ipaDisplay ? "隐藏IPA" : "显示IPA"}
|
||||
</Button>
|
||||
</MyButton>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Button from "@/components/Button";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { Letter, SupportedAlphabets } from "@/interfaces";
|
||||
import { useEffect, useState } from "react";
|
||||
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">
|
||||
<span className="text-2xl md:text-3xl">请选择您想学习的字符</span>
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
<Button onClick={() => setChosenAlphabet("japanese")}>
|
||||
<LightButton onClick={() => setChosenAlphabet("japanese")}>
|
||||
日语假名
|
||||
</Button>
|
||||
<Button onClick={() => setChosenAlphabet("english")}>
|
||||
</LightButton>
|
||||
<LightButton onClick={() => setChosenAlphabet("english")}>
|
||||
英文字母
|
||||
</Button>
|
||||
<Button onClick={() => setChosenAlphabet("uyghur")}>
|
||||
</LightButton>
|
||||
<LightButton onClick={() => setChosenAlphabet("uyghur")}>
|
||||
维吾尔字母
|
||||
</Button>
|
||||
<Button onClick={() => setChosenAlphabet("esperanto")}>
|
||||
</LightButton>
|
||||
<LightButton onClick={() => setChosenAlphabet("esperanto")}>
|
||||
世界语字母
|
||||
</Button>
|
||||
</LightButton>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -28,5 +28,3 @@ body {
|
||||
.code-block {
|
||||
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";
|
||||
|
||||
import Button from "@/components/Button";
|
||||
import { Select, Option } from "@material-tailwind/react";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import Main from "./Main";
|
||||
import Edit from "./Edit";
|
||||
import Start from "./Start";
|
||||
|
||||
interface ACardProps {
|
||||
children?: React.ReactNode;
|
||||
className?: 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 interface WordData {
|
||||
locales: [string, string];
|
||||
wordPairs: [string, string][];
|
||||
}
|
||||
|
||||
export default function Memorize() {
|
||||
const [pageState, setPageState] = useState<
|
||||
"choose" | "start" | "main" | "edit"
|
||||
>("edit");
|
||||
const [page, setPage] = useState<"start" | "main" | "edit">(
|
||||
"start",
|
||||
);
|
||||
const [wordData, setWordData] = useState<WordData>({
|
||||
locale1: "en-US",
|
||||
locale2: "zh-CN",
|
||||
data: { hello: "你好" },
|
||||
locales: ["en-US", "zh-CN"],
|
||||
wordPairs: [
|
||||
['hello', '你好'],
|
||||
['world', '世界'],
|
||||
['brutal', '残酷的'],
|
||||
['apple', '苹果'],
|
||||
['banana', '香蕉'],
|
||||
['orange', '橙子'],
|
||||
['grape', '葡萄'],
|
||||
]
|
||||
});
|
||||
if (pageState === "main") {
|
||||
if (page === "main")
|
||||
return <Main wordData={wordData} setPage={setPage}></Main>;
|
||||
if (page === "edit")
|
||||
return (
|
||||
<>
|
||||
<div className="w-full h-screen flex justify-center items-center">
|
||||
<ACard>
|
||||
<h1 className="text-center font-extrabold text-4xl text-white m-2 mb-4">
|
||||
Memorize
|
||||
</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>
|
||||
</>
|
||||
<Edit
|
||||
setPage={setPage}
|
||||
wordData={wordData}
|
||||
setWordData={setWordData}
|
||||
></Edit>
|
||||
);
|
||||
}
|
||||
if (pageState === "choose") {
|
||||
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> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (page === "start")
|
||||
return <Start setPage={setPage} wordData={wordData}></Start>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Button from "@/components/Button";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { useRef } from "react";
|
||||
|
||||
export default function UploadArea({
|
||||
@@ -38,8 +38,8 @@ export default function UploadArea({
|
||||
};
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2 m-2">
|
||||
<Button onClick={uploadVideo}>上传视频</Button>
|
||||
<Button onClick={uploadSRT}>上传字幕</Button>
|
||||
<LightButton onClick={uploadVideo}>上传视频</LightButton>
|
||||
<LightButton onClick={uploadSRT}>上传字幕</LightButton>
|
||||
<input type="file" className="hidden" ref={inputRef} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
|
||||
import SubtitleDisplay from "./SubtitleDisplay";
|
||||
import Button from "@/components/Button";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
||||
|
||||
type VideoPanelProps = {
|
||||
@@ -184,15 +184,15 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>(
|
||||
></video>
|
||||
<SubtitleDisplay subtitle={subtitle}></SubtitleDisplay>
|
||||
<div className="buttons flex mt-2 gap-2 flex-wrap">
|
||||
<Button onClick={togglePlayPause}>
|
||||
<LightButton onClick={togglePlayPause}>
|
||||
{isPlaying ? "暂停" : "播放"}
|
||||
</Button>
|
||||
<Button onClick={previous}>上句</Button>
|
||||
<Button onClick={next}>下句</Button>
|
||||
<Button onClick={restart}>句首</Button>
|
||||
<Button
|
||||
</LightButton>
|
||||
<LightButton onClick={previous}>上句</LightButton>
|
||||
<LightButton onClick={next}>下句</LightButton>
|
||||
<LightButton onClick={restart}>句首</LightButton>
|
||||
<LightButton
|
||||
onClick={handleAutoPauseToggle}
|
||||
>{`自动暂停(${autoPause ? "是" : "否"})`}</Button>
|
||||
>{`自动暂停(${autoPause ? "是" : "否"})`}</LightButton>
|
||||
</div>
|
||||
<input
|
||||
className="seekbar"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Button from "@/components/Button";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import IconClick from "@/components/IconClick";
|
||||
import IMAGES from "@/config/images";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
@@ -313,20 +313,20 @@ export default function TextSpeaker() {
|
||||
className={`${saving ? "bg-gray-200" : ""}`}
|
||||
></IconClick>
|
||||
<div className="w-full flex flex-row flex-wrap gap-2 justify-center items-center">
|
||||
<Button
|
||||
<LightButton
|
||||
selected={ipaEnabled}
|
||||
onClick={() => setIPAEnabled(!ipaEnabled)}
|
||||
>
|
||||
生成IPA
|
||||
</Button>
|
||||
<Button
|
||||
</LightButton>
|
||||
<LightButton
|
||||
onClick={() => {
|
||||
setShowSaveList(!showSaveList);
|
||||
}}
|
||||
selected={showSaveList}
|
||||
>
|
||||
查看保存项
|
||||
</Button>
|
||||
</LightButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import Button from "@/components/Button";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import IconClick from "@/components/IconClick";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
import IMAGES from "@/config/images";
|
||||
@@ -208,12 +208,12 @@ export default function Translator() {
|
||||
</div>
|
||||
<div className="option1 w-full flex flex-row justify-between items-center">
|
||||
<span>detect language</span>
|
||||
<Button
|
||||
<LightButton
|
||||
selected={ipaEnabled}
|
||||
onClick={() => setIPAEnabled(!ipaEnabled)}
|
||||
>
|
||||
generate ipa
|
||||
</Button>
|
||||
</LightButton>
|
||||
</div>
|
||||
</div>
|
||||
<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 className="option2 w-full flex gap-1 items-center flex-wrap">
|
||||
<span>translate into</span>
|
||||
<Button
|
||||
<LightButton
|
||||
onClick={() => {
|
||||
setTargetLang("Chinese");
|
||||
}}
|
||||
selected={targetLang === "Chinese"}
|
||||
>
|
||||
Chinese
|
||||
</Button>
|
||||
<Button
|
||||
</LightButton>
|
||||
<LightButton
|
||||
onClick={() => {
|
||||
setTargetLang("English");
|
||||
}}
|
||||
selected={targetLang === "English"}
|
||||
>
|
||||
English
|
||||
</Button>
|
||||
<Button
|
||||
</LightButton>
|
||||
<LightButton
|
||||
onClick={() => {
|
||||
setTargetLang("Italian");
|
||||
}}
|
||||
selected={targetLang === "Italian"}
|
||||
>
|
||||
Italian
|
||||
</Button>
|
||||
<Button onClick={inputLanguage} selected={!tl.includes(targetLang)}>
|
||||
</LightButton>
|
||||
<LightButton onClick={inputLanguage} selected={!tl.includes(targetLang)}>
|
||||
{"Other" + (tl.includes(targetLang) ? "" : ": " + targetLang)}
|
||||
</Button>
|
||||
</LightButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
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 { Word } from "@/interfaces";
|
||||
import {
|
||||
@@ -165,12 +165,12 @@ export default function WordBoard() {
|
||||
type="text"
|
||||
className="focus:outline-none border-b-2 border-black"
|
||||
/>
|
||||
<Button onClick={insertWord}>插入</Button>
|
||||
<Button onClick={deleteWord}>删除</Button>
|
||||
<Button onClick={searchWord}>搜索</Button>
|
||||
<Button onClick={importWords}>导入</Button>
|
||||
<Button onClick={exportWords}>导出</Button>
|
||||
<Button onClick={deleteAll}>删光</Button>
|
||||
<LightButton onClick={insertWord}>插入</LightButton>
|
||||
<LightButton onClick={deleteWord}>删除</LightButton>
|
||||
<LightButton onClick={searchWord}>搜索</LightButton>
|
||||
<LightButton onClick={importWords}>导入</LightButton>
|
||||
<LightButton onClick={exportWords}>导出</LightButton>
|
||||
<LightButton onClick={deleteAll}>删光</LightButton>
|
||||
{/* <Button label="朗读" onClick={readWordAloud}></Button> */}
|
||||
</div>
|
||||
<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,
|
||||
className,
|
||||
selected,
|
||||
@@ -10,11 +12,11 @@ export default function Button({
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
<PlainButton
|
||||
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}
|
||||
</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