diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index dff0297c6..5693a9aef 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -295,6 +295,8 @@ "text": "text", "addResource": "add resource", "addResourceFromUrlOrLocal": "add resource from url or local", + "resourceAdded": "Resource added", + "resourcesAdded": "{{fulfilled}} resources added, {{rejected}} failed to add.", "editResource": "edit resource", "deleteResource": "delete resource", "deleteResourceConfirmation": "Are you sure to delete {{name}}?", @@ -611,6 +613,14 @@ "sortBy": "Sort by", "createdAtDesc": "Created at desc", "createdAtAsc": "Created at asc", + "updatedAtDesc": "Updated at desc", + "updatedAtAsc": "Updated at asc", "scoreDesc": "Score desc", - "scoreAsc": "Score asc" + "scoreAsc": "Score asc", + "recordingsDurationDesc": "Recordings duration", + "recordingsCountDesc": "Recordings duration", + "all": "All", + "allLanguages": "All languages", + "search": "Search", + "noData": "No data" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 71bdeda23..bb8819306 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -295,6 +295,8 @@ "text": "文本", "addResource": "添加资源", "addResourceFromUrlOrLocal": "添加资源, 可以是 URL 或本地文件", + "resourceAdded": "资源添加成功", + "resourcesAdded": "成功添加 {{fulfilled}} 个资源, {{rejected}} 个资源添加失败", "editResource": "编辑资源", "deleteResource": "删除资源", "deleteResourceConfirmation": "您确定要删除资源 {{name}} 吗?", @@ -611,6 +613,14 @@ "sortBy": "排序", "createdAtDesc": "创建时间降序", "createdAtAsc": "创建时间升序", + "updatedAtDesc": "更新时间降序", + "updatedAtAsc": "更新时间升序", "scoreDesc": "得分从高到低", - "scoreAsc": "得分从低到高" + "scoreAsc": "得分从低到高", + "recordingsDurationDesc": "录音时长", + "recordingsCountDesc": "录音次数", + "all": "全部", + "allLanguages": "全部语言", + "search": "搜索", + "noData": "没有数据" } diff --git a/enjoy/src/main/db/handlers/audios-handler.ts b/enjoy/src/main/db/handlers/audios-handler.ts index b771ca488..3b773514e 100644 --- a/enjoy/src/main/db/handlers/audios-handler.ts +++ b/enjoy/src/main/db/handlers/audios-handler.ts @@ -1,6 +1,6 @@ import { ipcMain, IpcMainEvent } from "electron"; import { Audio, Transcription } from "@main/db/models"; -import { FindOptions, WhereOptions, Attributes } from "sequelize"; +import { FindOptions, WhereOptions, Attributes, Op } from "sequelize"; import downloader from "@main/downloader"; import log from "@main/logger"; import { t } from "i18next"; @@ -12,8 +12,17 @@ const logger = log.scope("db/handlers/audios-handler"); class AudiosHandler { private async findAll( _event: IpcMainEvent, - options: FindOptions> + options: FindOptions> & { query?: string } ) { + const { query, where = {} } = options || {}; + delete options.query; + delete options.where; + + if (query) { + (where as any).name = { + [Op.like]: `%${query}%`, + }; + } const audios = await Audio.findAll({ order: [["updatedAt", "DESC"]], include: [ @@ -24,6 +33,7 @@ class AudiosHandler { required: false, }, ], + where, ...options, }); diff --git a/enjoy/src/main/db/handlers/videos-handler.ts b/enjoy/src/main/db/handlers/videos-handler.ts index 32227ba54..260f310aa 100644 --- a/enjoy/src/main/db/handlers/videos-handler.ts +++ b/enjoy/src/main/db/handlers/videos-handler.ts @@ -1,6 +1,6 @@ import { ipcMain, IpcMainEvent } from "electron"; import { Video, Transcription } from "@main/db/models"; -import { FindOptions, WhereOptions, Attributes } from "sequelize"; +import { FindOptions, WhereOptions, Attributes, Op } from "sequelize"; import downloader from "@main/downloader"; import log from "@main/logger"; import { t } from "i18next"; @@ -12,8 +12,17 @@ const logger = log.scope("db/handlers/videos-handler"); class VideosHandler { private async findAll( _event: IpcMainEvent, - options: FindOptions> + options: FindOptions> & { query?: string } ) { + const { query, where = {} } = options || {}; + delete options.query; + delete options.where; + + if (query) { + (where as any).name = { + [Op.like]: `%${query}%`, + }; + } const videos = await Video.findAll({ order: [["updatedAt", "DESC"]], include: [ @@ -24,6 +33,7 @@ class VideosHandler { required: false, }, ], + where, ...options, }); if (!videos) { diff --git a/enjoy/src/renderer/components/audios/audios-component.tsx b/enjoy/src/renderer/components/audios/audios-component.tsx index 6feaf78da..a5230d62e 100644 --- a/enjoy/src/renderer/components/audios/audios-component.tsx +++ b/enjoy/src/renderer/components/audios/audios-component.tsx @@ -4,7 +4,6 @@ import { AddMediaButton, AudiosTable, AudioEditForm, - LoaderSpin, } from "@renderer/components"; import { t } from "i18next"; import { @@ -26,6 +25,13 @@ import { DialogHeader, DialogTitle, toast, + Input, + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectGroup, + SelectItem, } from "@renderer/components/ui"; import { DbProviderContext, @@ -33,59 +39,82 @@ import { } from "@renderer/context"; import { LayoutGridIcon, LayoutListIcon } from "lucide-react"; import { audiosReducer } from "@renderer/reducers"; -import { useNavigate } from "react-router-dom"; -import { useTranscribe } from "@renderer/hooks"; +import { useDebounce } from "@uidotdev/usehooks"; +import { LANGUAGES } from "@/constants"; export const AudiosComponent = () => { + const { addDblistener, removeDbListener } = useContext(DbProviderContext); + const { EnjoyApp } = useContext(AppSettingsProviderContext); + const [audios, dispatchAudios] = useReducer(audiosReducer, []); + const [hasMore, setHasMore] = useState(true); + + const [query, setQuery] = useState(""); + const [language, setLanguage] = useState("all"); + const [orderBy, setOrderBy] = useState("updatedAtDesc"); + const debouncedQuery = useDebounce(query, 500); const [editing, setEditing] = useState | null>(null); const [deleting, setDeleting] = useState | null>(null); - const [transcribing, setTranscribing] = useState | null>( - null - ); - const { transcribe } = useTranscribe(); - - const { addDblistener, removeDbListener } = useContext(DbProviderContext); - const { EnjoyApp } = useContext(AppSettingsProviderContext); - const [offset, setOffest] = useState(0); const [loading, setLoading] = useState(false); - const navigate = useNavigate(); - useEffect(() => { addDblistener(onAudiosUpdate); - fetchAudios(); return () => { removeDbListener(onAudiosUpdate); }; }, []); - const fetchAudios = async () => { + const fetchAudios = async (options?: { offset: number }) => { if (loading) return; - if (offset === -1) return; + const { offset = audios.length } = options || {}; setLoading(true); - const limit = 10; + const limit = 20; + + let order = []; + switch (orderBy) { + case "updatedAtDesc": + order = [["updatedAt", "DESC"]]; + break; + case "createdAtDesc": + order = [["createdAt", "DESC"]]; + break; + case "createdAtAsc": + order = [["createdAt", "ASC"]]; + break; + case "recordingsDurationDesc": + order = [["recordingsDuration", "DESC"]]; + break; + case "recordingsCountDesc": + order = [["recordingsCount", "DESC"]]; + break; + default: + order = [["updatedAt", "DESC"]]; + } + let where = {}; + if (language != "all") { + where = { language }; + } + EnjoyApp.audios .findAll({ offset, limit, + order, + where, + query: debouncedQuery, + }) .then((_audios) => { - if (_audios.length === 0) { - setOffest(-1); - return; - } + setHasMore(_audios.length >= limit); - if (_audios.length < limit) { - setOffest(-1); + if (offset === 0) { + dispatchAudios({ type: "set", records: _audios }); } else { - setOffest(offset + _audios.length); + dispatchAudios({ type: "append", records: _audios }); } - - dispatchAudios({ type: "append", records: _audios }); }) .catch((err) => { toast.error(err.message); @@ -100,14 +129,13 @@ export const AudiosComponent = () => { if (!record) return; if (model === "Audio") { - if (action === "create") { - dispatchAudios({ type: "create", record }); - navigate(`/audios/${record.id}`); - } else if (action === "destroy") { + if (action === "destroy") { dispatchAudios({ type: "destroy", record }); + } else if (action === "create") { + dispatchAudios({ type: "create", record }); + } else if (action === "update") { + dispatchAudios({ type: "update", record }); } - } else if (model === "Video" && action === "create") { - navigate(`/videos/${record.id}`); } else if (model === "Transcription" && action === "update") { dispatchAudios({ type: "update", @@ -120,54 +148,103 @@ export const AudiosComponent = () => { } }; - if (audios.length === 0) { - if (loading) return ; - - return ( -
- -
- ); - } + useEffect(() => { + fetchAudios({ offset: 0 }); + }, [debouncedQuery, language, orderBy]); return ( <>
-
-
- - - - - - - - -
- +
+ + + + + + + + + + + + + + setQuery(e.target.value)} + /> + +
- -
- {audios.map((audio) => ( - - ))} + + {audios.length === 0 ? ( +
+ {t("noData")}
- - - setEditing(audio)} - onDelete={(audio) => setDeleting(audio)} - onTranscribe={(audio) => setTranscribing(audio)} - /> - + ) : ( + <> + +
+ {audios.map((audio) => ( + + ))} +
+
+ + + setEditing(audio)} + onDelete={(audio) => setDeleting(audio)} + /> + + + )}
- {offset > -1 && ( + {hasMore && (
-
@@ -226,45 +303,6 @@ export const AudiosComponent = () => { - - { - if (value) return; - setTranscribing(null); - }} - > - - - {t("transcribe")} - -

- {t("transcribeAudioConfirmation", { - name: transcribing?.name || "", - })} -

-
-
- - {t("cancel")} - { - if (!transcribing) return; - - transcribe(transcribing.src, { - targetId: transcribing.id, - targetType: "Audio", - }).finally(() => { - setTranscribing(null); - }); - }} - > - {t("transcribe")} - - -
-
); }; diff --git a/enjoy/src/renderer/components/audios/audios-table.tsx b/enjoy/src/renderer/components/audios/audios-table.tsx index df1acd5ed..9a00e88cd 100644 --- a/enjoy/src/renderer/components/audios/audios-table.tsx +++ b/enjoy/src/renderer/components/audios/audios-table.tsx @@ -12,13 +12,9 @@ import { TooltipTrigger, Button, PingPoint, + Badge, } from "@renderer/components/ui"; -import { - EditIcon, - TrashIcon, - CheckCircleIcon, - AudioWaveformIcon, -} from "lucide-react"; +import { EditIcon, TrashIcon, CheckCircleIcon } from "lucide-react"; import dayjs from "@renderer/lib/dayjs"; import { secondsToTimestamp } from "@renderer/lib/utils"; import { Link } from "react-router-dom"; @@ -27,15 +23,15 @@ export const AudiosTable = (props: { audios: Partial[]; onEdit: (audio: Partial) => void; onDelete: (audio: Partial) => void; - onTranscribe: (audio: Partial) => void; }) => { - const { audios, onEdit, onDelete, onTranscribe } = props; + const { audios, onEdit, onDelete } = props; return ( {t("models.audio.name")} + {t("language")} {t("models.audio.duration")} @@ -75,6 +71,7 @@ export const AudiosTable = (props: { + {audio.language ? audio.language : "-"} {audio.duration ? secondsToTimestamp(audio.duration) : "-"} @@ -96,13 +93,6 @@ export const AudiosTable = (props: {
-
+ {files.length > 0 && ( +
+ {t("selectedFiles")}: {files.length} +
+ )} + + {files.length > 0 && submitting && ( +
+ + + {createdCount}/{files.length} + +
+ )} + @@ -226,45 +290,6 @@ export const VideosComponent = () => { - - { - if (value) return; - setTranscribing(null); - }} - > - - - {t("transcribe")} - -

- {t("transcribeMediaConfirmation", { - name: transcribing?.name || "", - })} -

-
-
- - {t("cancel")} - { - if (!transcribing) return; - - transcribe(transcribing.src, { - targetId: transcribing.id, - targetType: "Video", - }).finally(() => { - setTranscribing(null); - }); - }} - > - {t("transcribe")} - - -
-
); }; diff --git a/enjoy/src/renderer/components/videos/videos-table.tsx b/enjoy/src/renderer/components/videos/videos-table.tsx index 8977a082b..f5fe5fcfe 100644 --- a/enjoy/src/renderer/components/videos/videos-table.tsx +++ b/enjoy/src/renderer/components/videos/videos-table.tsx @@ -27,15 +27,15 @@ export const VideosTable = (props: { videos: Partial[]; onEdit: (video: Partial) => void; onDelete: (video: Partial) => void; - onTranscribe: (video: Partial) => void; }) => { - const { videos, onEdit, onDelete, onTranscribe } = props; + const { videos, onEdit, onDelete } = props; return (
{t("models.video.name")} + {t("language")} {t("models.video.duration")} @@ -75,6 +75,7 @@ export const VideosTable = (props: { + {video.language ? video.language : "-"} {video.duration ? secondsToTimestamp(video.duration) : "-"} @@ -96,13 +97,6 @@ export const VideosTable = (props: {
-