From fe0542e8c6709eae49a319d33e0c36e98e6399ab Mon Sep 17 00:00:00 2001 From: an-lee Date: Sat, 13 Jan 2024 22:59:57 +0800 Subject: [PATCH] Fix: Improve UI (#103) * use sonner * fix ui * fix post audio player --- enjoy/package.json | 2 + enjoy/src/i18n/en.json | 1 + enjoy/src/i18n/zh-CN.json | 1 + enjoy/src/renderer/app.tsx | 28 +++-- .../components/audios/audio-detail.tsx | 12 +- .../components/audios/audios-component.tsx | 8 +- .../conversations/conversations-shortcut.tsx | 5 +- enjoy/src/renderer/components/login-form.tsx | 21 +--- .../components/messages/user-message.tsx | 12 +- .../components/posts/post-actions.tsx | 23 ++-- .../renderer/components/posts/post-audio.tsx | 108 ++++++++++++------ .../renderer/components/posts/post-medium.tsx | 2 +- enjoy/src/renderer/components/posts/posts.tsx | 18 +-- .../renderer/components/preferences/about.tsx | 7 +- .../components/preferences/basic-settings.tsx | 14 +-- .../src/renderer/components/record-button.tsx | 8 +- .../components/recordings/recording-card.tsx | 14 +-- enjoy/src/renderer/components/ui/index.ts | 7 +- enjoy/src/renderer/components/ui/sonner.tsx | 31 +++++ .../components/videos/video-detail.tsx | 20 +--- .../components/videos/videos-component.tsx | 8 +- .../components/whisper-model-options.tsx | 13 +-- enjoy/src/renderer/pages/conversation.tsx | 9 +- enjoy/src/renderer/pages/story-preview.tsx | 8 +- enjoy/src/renderer/pages/story.tsx | 11 +- yarn.lock | 23 ++++ 26 files changed, 210 insertions(+), 204 deletions(-) create mode 100644 enjoy/src/renderer/components/ui/sonner.tsx diff --git a/enjoy/package.json b/enjoy/package.json index fb6e6a16..2fb23413 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -117,6 +117,7 @@ "lucide-react": "^0.308.0", "mark.js": "^8.11.1", "microsoft-cognitiveservices-speech-sdk": "^1.34.0", + "next-themes": "^0.2.1", "openai": "^4.24.1", "pitchfinder": "^2.3.2", "postcss": "^8.4.33", @@ -133,6 +134,7 @@ "rimraf": "^5.0.5", "sequelize": "^6.35.2", "sequelize-typescript": "^2.1.6", + "sonner": "^1.3.1", "sqlite3": "^5.1.7", "tailwind-scrollbar-hide": "^1.1.7", "umzug": "^3.5.0", diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 8a69130f..41b1755f 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -197,6 +197,7 @@ "youAreReadyToGo": "You are ready to go", "welcomeBack": "Welcome back! {{name}}", "download": "Download", + "downloading": "Downloading {{file}}", "chooseAIModelDependingOnYourHardware": "Choose AI Model depending on your hardware", "areYouSureToDownload": "Are you sure to download {{name}}?", "yourModelsWillBeDownloadedTo": "Your models will be downloaded to {{path}}", diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 3de4e0bf..a9e2836e 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -197,6 +197,7 @@ "youAreReadyToGo": "您已准备就绪", "welcomeBack": "欢迎回来, {{name}}", "download": "下载", + "downloading": "正在下载 {{file}}", "chooseAIModelDependingOnYourHardware": "根据您的硬件选择合适的 AI 模型", "areYouSureToDownload": "您确定要下载 {{name}} 吗?", "yourModelsWillBeDownloadedTo": "您的模型将下载到目录 {{path}}", diff --git a/enjoy/src/renderer/app.tsx b/enjoy/src/renderer/app.tsx index b11e5c09..9bd7e558 100644 --- a/enjoy/src/renderer/app.tsx +++ b/enjoy/src/renderer/app.tsx @@ -6,19 +6,29 @@ import { } from "@renderer/context"; import router from "./router"; import { RouterProvider } from "react-router-dom"; -import { Toaster, useToast } from "@renderer/components/ui"; -import { t } from "i18next"; +import { Toaster, toast } from "@renderer/components/ui"; import { Tooltip } from "react-tooltip"; import { useHotkeys } from "react-hotkeys-hook"; function App() { - const { toast } = useToast(); window.__ENJOY_APP__.onNotification((_event, notification) => { - toast({ - title: t(notification.type), - description: notification.message, - variant: notification.type === "error" ? "destructive" : "default", - }); + switch (notification.type) { + case "success": + toast.success(notification.message); + break; + case "error": + toast.error(notification.message); + break; + case "info": + toast.info(notification.message); + break; + case "warning": + toast.warning(notification.message); + break; + default: + toast.message(notification.message); + break; + } }); const ControlOrCommand = navigator.platform.includes("Mac") @@ -43,7 +53,7 @@ function App() { - + diff --git a/enjoy/src/renderer/components/audios/audio-detail.tsx b/enjoy/src/renderer/components/audios/audio-detail.tsx index f8559845..96da1e2c 100644 --- a/enjoy/src/renderer/components/audios/audio-detail.tsx +++ b/enjoy/src/renderer/components/audios/audio-detail.tsx @@ -21,13 +21,12 @@ import { AlertDialogCancel, Button, ScrollArea, - useToast, + toast, } from "@renderer/components/ui"; import { t } from "i18next"; export const AudioDetail = (props: { id?: string; md5?: string }) => { const { id, md5 } = props; - const { toast } = useToast(); const { addDblistener, removeDbListener } = useContext(DbProviderContext); const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); @@ -62,8 +61,7 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => { try { await EnjoyApp.audios.upload(audio.id); } catch (err) { - toast({ - title: t("shareFailed"), + toast.error(t("shareFailed"), { description: err.message, }); return; @@ -75,14 +73,12 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => { targetId: audio.id, }) .then(() => { - toast({ - title: t("shared"), + toast.success(t("sharedSuccessfully"), { description: t("sharedAudio"), }); }) .catch((err) => { - toast({ - title: t("shareFailed"), + toast.error(t("shareFailed"), { description: err.message, }); }); diff --git a/enjoy/src/renderer/components/audios/audios-component.tsx b/enjoy/src/renderer/components/audios/audios-component.tsx index 0f6974bb..e58c8a8c 100644 --- a/enjoy/src/renderer/components/audios/audios-component.tsx +++ b/enjoy/src/renderer/components/audios/audios-component.tsx @@ -25,7 +25,7 @@ import { DialogContent, DialogHeader, DialogTitle, - useToast, + toast, } from "@renderer/components/ui"; import { DbProviderContext, @@ -48,7 +48,6 @@ export const AudiosComponent = () => { const { EnjoyApp } = useContext(AppSettingsProviderContext); const [offset, setOffest] = useState(0); const [loading, setLoading] = useState(false); - const { toast } = useToast(); const navigate = useNavigate(); @@ -87,10 +86,7 @@ export const AudiosComponent = () => { dispatchAudios({ type: "append", records: _audios }); }) .catch((err) => { - toast({ - description: err.message, - variant: "destructive", - }); + toast.error(err.message); }) .finally(() => { setLoading(false); diff --git a/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx b/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx index da7da3d6..081871b6 100644 --- a/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx +++ b/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect, useState } from "react"; import { AppSettingsProviderContext } from "@renderer/context"; -import { ScrollArea } from "@renderer/components/ui"; +import { ScrollArea, toast } from "@renderer/components/ui"; import { LoaderSpin } from "@renderer/components"; import { MessageCircleIcon } from "lucide-react"; @@ -20,11 +20,10 @@ export const ConversationsShortcut = (props: { content: prompt, }) .then((replies) => { - console.log(replies); onReply(replies); }) .catch((error) => { - console.error(error); + toast.error(error.message); }) .finally(() => { setLoading(false); diff --git a/enjoy/src/renderer/components/login-form.tsx b/enjoy/src/renderer/components/login-form.tsx index 9fdb38b8..2b7ab262 100644 --- a/enjoy/src/renderer/components/login-form.tsx +++ b/enjoy/src/renderer/components/login-form.tsx @@ -1,10 +1,9 @@ -import { Button, useToast } from "@renderer/components/ui"; +import { Button, toast } from "@renderer/components/ui"; import { useContext, useEffect } from "react"; import { AppSettingsProviderContext } from "@renderer/context"; import { t } from "i18next"; export const LoginForm = () => { - const { toast } = useToast(); const { EnjoyApp, login, webApi } = useContext(AppSettingsProviderContext); const handleMixinLogin = () => { @@ -21,11 +20,7 @@ export const LoginForm = () => { const { state, url, error } = event; if (error) { - toast({ - title: t("error"), - description: error, - variant: "destructive", - }); + toast.error(error); EnjoyApp.view.hide(); return; } @@ -35,11 +30,7 @@ export const LoginForm = () => { const code = new URL(url).searchParams.get("code"); if (!url.startsWith(webApi.baseUrl)) { - toast({ - title: t("error"), - description: t("invalidRedirectUrl"), - variant: "destructive", - }); + toast.error(t("invalidRedirectUrl")); EnjoyApp.view.hide(); } @@ -53,11 +44,7 @@ export const LoginForm = () => { EnjoyApp.view.hide(); }); } else { - toast({ - title: t("error"), - description: t("failedToLogin"), - variant: "destructive", - }); + toast.error(t("failedToLogin")); EnjoyApp.view.hide(); } } diff --git a/enjoy/src/renderer/components/messages/user-message.tsx b/enjoy/src/renderer/components/messages/user-message.tsx index 40a54391..c2cbf637 100644 --- a/enjoy/src/renderer/components/messages/user-message.tsx +++ b/enjoy/src/renderer/components/messages/user-message.tsx @@ -17,7 +17,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, - useToast, + toast, } from "@renderer/components/ui"; import { SpeechPlayer } from "@renderer/components"; import { useContext, useState } from "react"; @@ -45,7 +45,6 @@ export const UserMessageComponent = (props: { const { user, webApi } = useContext(AppSettingsProviderContext); const [_, copyToClipboard] = useCopyToClipboard(); const [copied, setCopied] = useState(false); - const { toast } = useToast(); const handleShare = async () => { if (message.role === "user") { @@ -58,15 +57,10 @@ export const UserMessageComponent = (props: { }, }) .then(() => { - toast({ - description: t("sharedPrompt"), - }); + toast(t("sharedSuccessfully"), { description: t("sharedPrompt") }); }) .catch((err) => { - toast({ - title: t("shareFailed"), - description: err.message, - }); + toast.error(t("shareFailed"), { description: err.message }); }); } }; diff --git a/enjoy/src/renderer/components/posts/post-actions.tsx b/enjoy/src/renderer/components/posts/post-actions.tsx index 44d7d914..c1a38f0e 100644 --- a/enjoy/src/renderer/components/posts/post-actions.tsx +++ b/enjoy/src/renderer/components/posts/post-actions.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext, useState } from "react"; import { AppSettingsProviderContext } from "@renderer/context"; import { ConversationsShortcut } from "@renderer/components"; import { @@ -18,7 +18,7 @@ import { DialogHeader, DialogTitle, ScrollArea, - useToast, + toast, } from "@renderer/components/ui"; import { t } from "i18next"; import Markdown from "react-markdown"; @@ -37,7 +37,6 @@ export const PostActions = (props: { post: PostType }) => { const [_, copyToClipboard] = useCopyToClipboard(); const [copied, setCopied] = useState(false); const { EnjoyApp } = useContext(AppSettingsProviderContext); - const { toast } = useToast(); const [asking, setAsking] = useState(false); const [aiReplies, setAiReplies] = useState([]); @@ -50,9 +49,7 @@ export const PostActions = (props: { post: PostType }) => { try { const video = await EnjoyApp.videos.findOne({ md5: medium.md5 }); if (video) { - toast({ - description: t("videoAlreadyAddedToLibrary"), - }); + toast.info(t("videoAlreadyAddedToLibrary")); return; } } catch (error) { @@ -65,21 +62,17 @@ export const PostActions = (props: { post: PostType }) => { md5: medium.md5, }) .then(() => { - toast({ - description: t("videoSuccessfullyAddedToLibrary"), - }); + toast.success(t("videoSuccessfullyAddedToLibrary")); }); } else if (medium.mediumType === "Audio") { try { const audio = await EnjoyApp.audios.findOne({ md5: medium.md5 }); if (audio) { - toast({ - description: t("audioAlreadyAddedToLibrary"), - }); + toast.info(t("audioAlreadyAddedToLibrary")); return; } } catch (error) { - console.error(error); + toast.error(error.message); } EnjoyApp.audios @@ -88,9 +81,7 @@ export const PostActions = (props: { post: PostType }) => { md5: medium.md5, }) .then(() => { - toast({ - description: t("audioSuccessfullyAddedToLibrary"), - }); + toast.success(t("audioSuccessfullyAddedToLibrary")); }); } }; diff --git a/enjoy/src/renderer/components/posts/post-audio.tsx b/enjoy/src/renderer/components/posts/post-audio.tsx index f681c43d..f2bee1e3 100644 --- a/enjoy/src/renderer/components/posts/post-audio.tsx +++ b/enjoy/src/renderer/components/posts/post-audio.tsx @@ -6,12 +6,83 @@ import { Button, Skeleton } from "@renderer/components/ui"; import { PlayIcon, PauseIcon } from "lucide-react"; import { useIntersectionObserver } from "@uidotdev/usehooks"; import { secondsToTimestamp } from "@renderer/lib/utils"; +import { MediaPlayer, MediaProvider } from "@vidstack/react"; +import { + DefaultAudioLayout, + defaultLayoutIcons, +} from "@vidstack/react/player/layouts/default"; +export const STORAGE_WORKER_ENDPOINT = "https://enjoy-storage.baizhiheizi.com"; export const PostAudio = (props: { audio: Partial; height?: number; }) => { const { audio, height = 80 } = props; + const [currentTime, setCurrentTime] = useState(0); + const { webApi } = useContext(AppSettingsProviderContext); + const [transcription, setTranscription] = useState(); + + const currentTranscription = (transcription?.result || []).find( + (s) => + currentTime >= s.offsets.from / 1000.0 && + currentTime <= s.offsets.to / 1000.0 + ); + + useEffect(() => { + webApi + .transcriptions({ + targetMd5: audio.md5, + }) + .then((response) => { + setTranscription(response?.transcriptions?.[0]); + }); + }, [audio.md5]); + + return ( +
+ {audio.sourceUrl.startsWith(STORAGE_WORKER_ENDPOINT) ? ( + + ) : ( + { + setCurrentTime(_currentTime); + }} + src={audio.sourceUrl} + > + + + + )} + + {currentTranscription && ( +
+
+ {currentTranscription.text} +
+
+ )} + + {audio.coverUrl && ( +
+ +
+ )} +
+ ); +}; + +const WavesurferPlayer = (props: { + audio: Partial; + height?: number; + currentTime: number; + setCurrentTime: (currentTime: number) => void; +}) => { + const { audio, height = 80, currentTime, setCurrentTime } = props; const [initialized, setInitialized] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [wavesurfer, setWavesurfer] = useState(null); @@ -20,15 +91,6 @@ export const PostAudio = (props: { threshold: 1, }); const [duration, setDuration] = useState(0); - const { webApi } = useContext(AppSettingsProviderContext); - const [currentTime, setCurrentTime] = useState(0); - const [transcription, setTranscription] = useState(); - - const currentTranscription = (transcription?.result || []).find( - (s) => - currentTime >= s.offsets.from / 1000.0 && - currentTime <= s.offsets.to / 1000.0 - ); const onPlayClick = useCallback(() => { wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play(); @@ -93,18 +155,8 @@ export const PostAudio = (props: { }; }, [wavesurfer]); - useEffect(() => { - webApi - .transcriptions({ - targetMd5: audio.md5, - }) - .then((response) => { - setTranscription(response?.transcriptions?.[0]); - }); - }, [audio.md5]); - return ( -
+ <>
{secondsToTimestamp(duration)} @@ -141,20 +193,6 @@ export const PostAudio = (props: { ref={containerRef} >
- - {currentTranscription && ( -
-
- {currentTranscription.text} -
-
- )} - - {audio.coverUrl && ( -
- -
- )} - + ); }; diff --git a/enjoy/src/renderer/components/posts/post-medium.tsx b/enjoy/src/renderer/components/posts/post-medium.tsx index bcb6280e..714765d5 100644 --- a/enjoy/src/renderer/components/posts/post-medium.tsx +++ b/enjoy/src/renderer/components/posts/post-medium.tsx @@ -37,7 +37,7 @@ export const PostMedium = (props: { medium: MediumType }) => {
{t("sharedAudio")}
- } /> + )} diff --git a/enjoy/src/renderer/components/posts/posts.tsx b/enjoy/src/renderer/components/posts/posts.tsx index 8ef6f9f6..a7fa8b5d 100644 --- a/enjoy/src/renderer/components/posts/posts.tsx +++ b/enjoy/src/renderer/components/posts/posts.tsx @@ -1,7 +1,7 @@ import { useContext, useEffect, useState } from "react"; import { AppSettingsProviderContext } from "@renderer/context"; import { PostCard, LoaderSpin } from "@renderer/components"; -import { useToast, Button } from "@renderer/components//ui"; +import { toast, Button } from "@renderer/components//ui"; import { t } from "i18next"; export const Posts = () => { @@ -9,23 +9,16 @@ export const Posts = () => { const [loading, setLoading] = useState(true); const [posts, setPosts] = useState([]); const [nextPage, setNextPage] = useState(1); - const { toast } = useToast(); const handleDelete = (id: string) => { webApi .deletePost(id) .then(() => { - toast({ - description: t("removeSharingSuccessfully"), - }); + toast.success(t("removeSharingSuccessfully")); setPosts(posts.filter((post) => post.id !== id)); }) .catch((error) => { - toast({ - title: t("removeSharingFailed"), - description: error.message, - variant: "destructive", - }); + toast.error(t("removeSharingFailed"), { description: error.message }); }); }; @@ -42,10 +35,7 @@ export const Posts = () => { setNextPage(res.next); }) .catch((err) => { - toast({ - description: err.message, - variant: "destructive", - }); + toast.error(err.message); }) .finally(() => { setLoading(false); diff --git a/enjoy/src/renderer/components/preferences/about.tsx b/enjoy/src/renderer/components/preferences/about.tsx index 4bdeac2f..9541bd9d 100644 --- a/enjoy/src/renderer/components/preferences/about.tsx +++ b/enjoy/src/renderer/components/preferences/about.tsx @@ -1,5 +1,5 @@ import { t } from "i18next"; -import { Button, useToast } from "@renderer/components/ui"; +import { Button, toast } from "@renderer/components/ui"; import { AppSettingsProviderContext } from "@renderer/context"; import { useState, useContext } from "react"; import { LoaderIcon } from "lucide-react"; @@ -7,14 +7,11 @@ import { LoaderIcon } from "lucide-react"; export const About = () => { const { version } = useContext(AppSettingsProviderContext); const [checking, setChecking] = useState(false); - const { toast } = useToast(); const checkUpdate = () => { setChecking(true); setTimeout(() => { setChecking(false); - toast({ - description: t("alreadyLatestVersion"), - }); + toast.info(t("alreadyLatestVersion")); }, 1000); }; diff --git a/enjoy/src/renderer/components/preferences/basic-settings.tsx b/enjoy/src/renderer/components/preferences/basic-settings.tsx index 3279b942..4cbb56f0 100644 --- a/enjoy/src/renderer/components/preferences/basic-settings.tsx +++ b/enjoy/src/renderer/components/preferences/basic-settings.tsx @@ -22,7 +22,7 @@ import { Input, Label, Separator, - useToast, + toast, } from "@renderer/components/ui"; import { WhisperModelOptions } from "@renderer/components"; import { @@ -196,7 +196,6 @@ const OpenaiSettings = () => { const { openai, setOpenai } = useContext(AISettingsProviderContext); const [editing, setEditing] = useState(false); const ref = useRef(); - const { toast } = useToast(); const handleSave = () => { if (!ref.current) return; @@ -206,10 +205,7 @@ const OpenaiSettings = () => { }); setEditing(false); - toast({ - title: t("success"), - description: t("openaiKeySaved"), - }); + toast.success(t("openaiKeySaved")); }; useEffect(() => { @@ -264,7 +260,6 @@ const GoogleGenerativeAiSettings = () => { ); const [editing, setEditing] = useState(false); const ref = useRef(); - const { toast } = useToast(); const handleSave = () => { if (!ref.current) return; @@ -274,10 +269,7 @@ const GoogleGenerativeAiSettings = () => { }); setEditing(false); - toast({ - title: t("success"), - description: t("googleGenerativeAiKeySaved"), - }); + toast.success(t("googleGenerativeAiKeySaved")); }; useEffect(() => { diff --git a/enjoy/src/renderer/components/record-button.tsx b/enjoy/src/renderer/components/record-button.tsx index f70c9178..7777e1b6 100644 --- a/enjoy/src/renderer/components/record-button.tsx +++ b/enjoy/src/renderer/components/record-button.tsx @@ -4,7 +4,7 @@ import { useState, useEffect, useRef } from "react"; import RecordPlugin from "wavesurfer.js/dist/plugins/record"; import WaveSurfer from "wavesurfer.js"; import { cn } from "@renderer/lib/utils"; -import { RadialProgress, useToast } from "@renderer/components/ui"; +import { RadialProgress, toast } from "@renderer/components/ui"; import { useHotkeys } from "react-hotkeys-hook"; export const RecordButton = (props: { @@ -16,7 +16,6 @@ export const RecordButton = (props: { const { className, disabled, onRecordBegin, onRecordEnd } = props; const [isRecording, setIsRecording] = useState(false); const [duration, setDuration] = useState(0); - const { toast } = useToast(); useHotkeys(["command+alt+r", "control+alt+r"], () => { if (disabled) return; @@ -67,10 +66,7 @@ export const RecordButton = (props: { if (duration > 1000) { onRecordEnd(blob, duration); } else { - toast({ - description: t("recordTooShort"), - variant: "warning", - }); + toast.warning(t("recordTooShort")); } }} /> diff --git a/enjoy/src/renderer/components/recordings/recording-card.tsx b/enjoy/src/renderer/components/recordings/recording-card.tsx index d910cd57..3590e2c0 100644 --- a/enjoy/src/renderer/components/recordings/recording-card.tsx +++ b/enjoy/src/renderer/components/recordings/recording-card.tsx @@ -16,7 +16,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, - useToast, + toast, } from "@renderer/components/ui"; import { MoreHorizontalIcon, @@ -36,7 +36,6 @@ export const RecordingCard = (props: { const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); const [isPlaying, setIsPlaying] = useState(false); - const { toast } = useToast(); const handleDelete = () => { EnjoyApp.recordings.destroy(recording.id); @@ -46,11 +45,7 @@ export const RecordingCard = (props: { try { await EnjoyApp.recordings.upload(recording.id); } catch (error) { - toast({ - title: t("shareFailed"), - description: error.message, - variant: "destructive", - }); + toast.error(t("shareFailed"), { description: error.message }); return; } } @@ -61,14 +56,13 @@ export const RecordingCard = (props: { targetType: "Recording", }) .then(() => { - toast({ + toast.success(t("sharedSuccessfully"), { description: t("sharedRecording"), }); }) .catch((error) => { - toast({ + toast.error(t("shareFailed"), { description: error.message, - variant: "destructive", }); }); }; diff --git a/enjoy/src/renderer/components/ui/index.ts b/enjoy/src/renderer/components/ui/index.ts index 32735b86..1d45f33d 100644 --- a/enjoy/src/renderer/components/ui/index.ts +++ b/enjoy/src/renderer/components/ui/index.ts @@ -13,9 +13,9 @@ export * from "./input"; export * from "./avatar"; export * from "./alert-dialog"; export * from "./card"; -export * from "./toast"; -export * from "./use-toast"; -export * from "./toaster"; +// export * from "./toast"; +// export * from "./use-toast"; +// export * from "./toaster"; export * from "./toggle"; export * from "./radio-group"; export * from "./scroll-area"; @@ -33,3 +33,4 @@ export * from "./select"; export * from "./sheet"; export * from "./hover-card"; export * from "./floating-toolbar"; +export * from "./sonner"; diff --git a/enjoy/src/renderer/components/ui/sonner.tsx b/enjoy/src/renderer/components/ui/sonner.tsx new file mode 100644 index 00000000..2958b9ae --- /dev/null +++ b/enjoy/src/renderer/components/ui/sonner.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { Toaster as Sonner, toast } from "sonner"; + +type ToasterProps = React.ComponentProps; + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme(); + + return ( + + ); +}; + +export { Toaster, toast }; diff --git a/enjoy/src/renderer/components/videos/video-detail.tsx b/enjoy/src/renderer/components/videos/video-detail.tsx index edfa8ae3..51d6b29a 100644 --- a/enjoy/src/renderer/components/videos/video-detail.tsx +++ b/enjoy/src/renderer/components/videos/video-detail.tsx @@ -21,13 +21,12 @@ import { AlertDialogCancel, Button, ScrollArea, - useToast, + toast, } from "@renderer/components/ui"; import { t } from "i18next"; export const VideoDetail = (props: { id?: string; md5?: string }) => { const { id, md5 } = props; - const { toast } = useToast(); const { addDblistener, removeDbListener } = useContext(DbProviderContext); const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); @@ -61,8 +60,7 @@ export const VideoDetail = (props: { id?: string; md5?: string }) => { const handleShare = async () => { if (!video.source.startsWith("http")) { - toast({ - title: t("shareFailed"), + toast.error(t("shareFailed"), { description: t("cannotShareLocalVideo"), }); return; @@ -72,10 +70,7 @@ export const VideoDetail = (props: { id?: string; md5?: string }) => { try { await EnjoyApp.videos.upload(video.id); } catch (err) { - toast({ - title: t("shareFailed"), - description: err.message, - }); + toast.error(t("shareFailed"), { description: err.message }); return; } } @@ -86,15 +81,10 @@ export const VideoDetail = (props: { id?: string; md5?: string }) => { targetId: video.id, }) .then(() => { - toast({ - description: t("sharedVideo"), - }); + toast.success(t("sharedSuccessfully"), { description: t("sharedVideo") }); }) .catch((err) => { - toast({ - title: t("shareFailed"), - description: err.message, - }); + toast.error(t("shareFailed"), { description: err.message }); }); setSharing(false); }; diff --git a/enjoy/src/renderer/components/videos/videos-component.tsx b/enjoy/src/renderer/components/videos/videos-component.tsx index 9516e82c..3f8a171f 100644 --- a/enjoy/src/renderer/components/videos/videos-component.tsx +++ b/enjoy/src/renderer/components/videos/videos-component.tsx @@ -25,7 +25,7 @@ import { DialogContent, DialogHeader, DialogTitle, - useToast, + toast, } from "@renderer/components/ui"; import { DbProviderContext, @@ -48,7 +48,6 @@ export const VideosComponent = () => { const { EnjoyApp } = useContext(AppSettingsProviderContext); const [offset, setOffest] = useState(0); const [loading, setLoading] = useState(false); - const { toast } = useToast(); const navigate = useNavigate(); @@ -87,10 +86,7 @@ export const VideosComponent = () => { dispatchVideos({ type: "append", records: _videos }); }) .catch((err) => { - toast({ - description: err.message, - variant: "destructive", - }); + toast.error(err.message); }) .finally(() => { setLoading(false); diff --git a/enjoy/src/renderer/components/whisper-model-options.tsx b/enjoy/src/renderer/components/whisper-model-options.tsx index 5ba74b99..5053a1f0 100644 --- a/enjoy/src/renderer/components/whisper-model-options.tsx +++ b/enjoy/src/renderer/components/whisper-model-options.tsx @@ -14,7 +14,7 @@ import { CardContent, CardFooter, ScrollArea, - useToast, + toast, Progress, } from "@renderer/components/ui"; import { t } from "i18next"; @@ -67,10 +67,8 @@ export const WhisperModelOptionsPanel = () => { export const WhisperModelOptions = () => { const [selectingModel, setSelectingModel] = useState(null); const [availableModels, setAvailableModels] = useState([]); - const { whisperModelsPath, whisperModel, setWhisperModel, EnjoyApp } = useContext( - AppSettingsProviderContext - ); - const { toast } = useToast(); + const { whisperModelsPath, whisperModel, setWhisperModel, EnjoyApp } = + useContext(AppSettingsProviderContext); useEffect(() => { updateAvailableModels(); @@ -126,10 +124,7 @@ export const WhisperModelOptions = () => { if (option.downloaded) { setWhisperModel(option.name); } else if (option.downloadState) { - toast({ - title: "Downloading", - description: `${option.name} is downloading...`, - }); + toast.warning(t("downloading", { file: option.name })); } else { setSelectingModel(option); } diff --git a/enjoy/src/renderer/pages/conversation.tsx b/enjoy/src/renderer/pages/conversation.tsx index ff60e62e..e427224d 100644 --- a/enjoy/src/renderer/pages/conversation.tsx +++ b/enjoy/src/renderer/pages/conversation.tsx @@ -6,7 +6,7 @@ import { Sheet, SheetContent, SheetTrigger, - useToast, + toast, } from "@renderer/components/ui"; import { MessageComponent, ConversationForm } from "@renderer/components"; import { SendIcon, BotIcon, LoaderIcon, SettingsIcon } from "lucide-react"; @@ -29,8 +29,6 @@ export default () => { const [content, setConent] = useState(""); const [submitting, setSubmitting] = useState(false); - const { toast } = useToast(); - const [messages, dispatchMessages] = useReducer(messagesReducer, []); const [offset, setOffest] = useState(0); const [loading, setLoading] = useState(false); @@ -79,10 +77,7 @@ export default () => { const handleSubmit = async (text?: string, file?: string) => { if (submitting) { - toast({ - title: t("warning"), - description: t("anotherRequestIsPending"), - }); + toast.warning(t("anotherRequestIsPending")); } text = text ? text : content; diff --git a/enjoy/src/renderer/pages/story-preview.tsx b/enjoy/src/renderer/pages/story-preview.tsx index ccb871dd..75d824ca 100644 --- a/enjoy/src/renderer/pages/story-preview.tsx +++ b/enjoy/src/renderer/pages/story-preview.tsx @@ -1,4 +1,4 @@ -import { Input, Button, ScrollArea, useToast } from "@renderer/components/ui"; +import { Input, Button, ScrollArea, toast } from "@renderer/components/ui"; import { LoaderSpin, StoryViewer, @@ -27,7 +27,6 @@ export default () => { const [loading, setLoading] = useState(true); const [readable, setReadable] = useState(true); const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); - const { toast } = useToast(); const [meanings, setMeanings] = useState([]); const [marked, setMarked] = useState(false); const [doc, setDoc] = useState(null); @@ -73,10 +72,7 @@ export default () => { if (state == "did-fail-load") { setLoading(false); if (error) { - toast({ - title: error, - variant: "destructive", - }); + toast.error(error); setError(error); } diff --git a/enjoy/src/renderer/pages/story.tsx b/enjoy/src/renderer/pages/story.tsx index 3384e2b9..b32c3011 100644 --- a/enjoy/src/renderer/pages/story.tsx +++ b/enjoy/src/renderer/pages/story.tsx @@ -1,5 +1,5 @@ import { t } from "i18next"; -import { ScrollArea, useToast } from "@renderer/components/ui"; +import { ScrollArea, toast } from "@renderer/components/ui"; import { LoaderSpin, PagePlaceholder, @@ -24,7 +24,6 @@ export default () => { const [scanning, setScanning] = useState(false); const [marked, setMarked] = useState(true); const [doc, setDoc] = useState(null); - const { toast } = useToast(); const fetchStory = async () => { webApi @@ -123,15 +122,11 @@ export default () => { webApi .createPost({ targetId: story.id, targetType: "Story" }) .then(() => { - toast({ - description: t("sharedStory"), - }); + toast.success(t("sharedStory")); }) .catch((error) => { - toast({ - title: t("shareFailed"), + toast.error(t("shareFailed"), { description: error.message, - variant: "destructive", }); }); }; diff --git a/yarn.lock b/yarn.lock index 279ab712..56914bdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5818,6 +5818,7 @@ __metadata: lucide-react: "npm:^0.308.0" mark.js: "npm:^8.11.1" microsoft-cognitiveservices-speech-sdk: "npm:^1.34.0" + next-themes: "npm:^0.2.1" octokit: "npm:^3.1.2" openai: "npm:^4.24.1" pitchfinder: "npm:^2.3.2" @@ -5835,6 +5836,7 @@ __metadata: rimraf: "npm:^5.0.5" sequelize: "npm:^6.35.2" sequelize-typescript: "npm:^2.1.6" + sonner: "npm:^1.3.1" sqlite3: "npm:^5.1.7" tailwind-merge: "npm:^2.2.0" tailwind-scrollbar-hide: "npm:^1.1.7" @@ -9561,6 +9563,17 @@ __metadata: languageName: node linkType: hard +"next-themes@npm:^0.2.1": + version: 0.2.1 + resolution: "next-themes@npm:0.2.1" + peerDependencies: + next: "*" + react: "*" + react-dom: "*" + checksum: 979dec0a2de049ce7d1b5da835e7f7dc3b7ec83ba9e464348f497a52a6a6e5b5c395c97f071f66a63f50f22cce89fb6d19061ec7e75643b0eab215b21794bde7 + languageName: node + linkType: hard + "nice-try@npm:^1.0.4": version: 1.0.5 resolution: "nice-try@npm:1.0.5" @@ -11552,6 +11565,16 @@ __metadata: languageName: node linkType: hard +"sonner@npm:^1.3.1": + version: 1.3.1 + resolution: "sonner@npm:1.3.1" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 324d2b2edb8bf15a38239f67e533bbf9f38425adf432c9542e7a9610b7451ec5d4082d2e8ddb564096f63f44ba7ec4d2e137f8b0af1786a0becac346fae2b28a + languageName: node + linkType: hard + "source-map-js@npm:^1.0.2": version: 1.0.2 resolution: "source-map-js@npm:1.0.2"