diff --git a/src-tauri/src/feed/channel.rs b/src-tauri/src/feed/channel.rs index 1f9052130..d505829da 100644 --- a/src-tauri/src/feed/channel.rs +++ b/src-tauri/src/feed/channel.rs @@ -398,7 +398,13 @@ pub fn add_feed(feed: models::NewFeed, articles: Vec) -> (us let result = match result { Ok(r) => (r, String::from("")), - Err(error) => (0, error.to_string()), + Err(error) => { + if let diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) = error { + return (0, "The feed you are trying to save already exists.".to_string()) + } else { + return (0, error.to_string()) + } + } }; println!(" new result {:?}", result); diff --git a/src/components/AddFeed/index.tsx b/src/components/AddFeed/index.tsx index d022f31f3..e21d1a5cb 100644 --- a/src/components/AddFeed/index.tsx +++ b/src/components/AddFeed/index.tsx @@ -47,7 +47,7 @@ export const AddFeedChannel = (props: any) => { variant: "destructive", title: "Unable to subscribe", description: message, - duration: 2, + duration: 2000, }); return; @@ -85,10 +85,17 @@ export const AddFeedChannel = (props: any) => { dataAgent .addChannel(feedUrl) .then((res) => { + console.log("%c Line:88 🥕 res", "color:#4fff4B", res); if (res[1] === "") { store.initCollectionMetas(); - busChannel.emit("getChannels"); handleCancel(); + } else { + toast({ + variant: "destructive", + title: "Unable to subscribe", + description: res[1], + duration: 2000, + }); } }) .finally(() => { diff --git a/src/components/SettingPanel/Content/DialogDeleteFolder.tsx b/src/components/SettingPanel/Content/DialogDeleteFolder.tsx index 26a5f880a..26e243983 100644 --- a/src/components/SettingPanel/Content/DialogDeleteFolder.tsx +++ b/src/components/SettingPanel/Content/DialogDeleteFolder.tsx @@ -49,7 +49,7 @@ export const DialogDeleteFolder = React.memo((props: DialogProps) => { variant: "destructive", title: "Ops! Something wrong~", description: err.message, - duration: 2, + duration: 2000, }); }); } diff --git a/src/components/SettingPanel/Content/DialogEditFeed.tsx b/src/components/SettingPanel/Content/DialogEditFeed.tsx index e227a4836..04c9cfe68 100644 --- a/src/components/SettingPanel/Content/DialogEditFeed.tsx +++ b/src/components/SettingPanel/Content/DialogEditFeed.tsx @@ -53,7 +53,7 @@ export const DialogEditFeed = React.memo((props: DialogEditFeedProps) => { variant: "destructive", title: "Ops! Something wrong~", description: err.message, - duration: 2, + duration: 2000, }); }); } diff --git a/src/components/SettingPanel/Content/DialogUnsubscribeFeed.tsx b/src/components/SettingPanel/Content/DialogUnsubscribeFeed.tsx index c3e6626e7..3b30ed112 100644 --- a/src/components/SettingPanel/Content/DialogUnsubscribeFeed.tsx +++ b/src/components/SettingPanel/Content/DialogUnsubscribeFeed.tsx @@ -49,7 +49,7 @@ export const DialogUnsubscribeFeed = React.memo((props: DialogProps) => { variant: "destructive", title: "Ops! Something wrong~", description: err.message, - duration: 2, + duration: 2000, }); }); } diff --git a/src/components/ui/Toaster.tsx b/src/components/ui/Toaster.tsx index 7d82ed557..a2209ba58 100644 --- a/src/components/ui/Toaster.tsx +++ b/src/components/ui/Toaster.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Toast, ToastClose, @@ -7,11 +5,11 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@/components/ui/toast"; -import { useToast } from "@/components/ui/use-toast"; +} from "@/components/ui/toast" +import { useToast } from "@/components/ui/use-toast" export function Toaster() { - const { toasts } = useToast(); + const { toasts } = useToast() return ( @@ -27,9 +25,9 @@ export function Toaster() { {action} - ); + ) })} - ); + ) } diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 6c2e6fcf2..d65c07c5c 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -1,11 +1,11 @@ -import * as React from "react"; -import * as ToastPrimitives from "@radix-ui/react-toast"; -import { VariantProps, cva } from "class-variance-authority"; -import { X } from "lucide-react"; +import * as React from "react" +import * as ToastPrimitives from "@radix-ui/react-toast" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" -import { cn } from "@/helpers/cn"; +import { cn } from "@/helpers/cn" -const ToastProvider = ToastPrimitives.Provider; +const ToastProvider = ToastPrimitives.Provider const ToastViewport = React.forwardRef< React.ElementRef, @@ -15,28 +15,28 @@ const ToastViewport = React.forwardRef< ref={ref} className={cn( "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", - className, + className )} {...props} /> -)); -ToastViewport.displayName = ToastPrimitives.Viewport.displayName; +)) +ToastViewport.displayName = ToastPrimitives.Viewport.displayName const toastVariants = cva( - "data-[swipe=move]:transition-none group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full", + "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", { variants: { variant: { - default: "bg-background border", + default: "border bg-background text-foreground", destructive: - "group destructive border-destructive bg-destructive text-destructive-foreground", + "destructive group border-destructive bg-destructive text-destructive-foreground", }, }, defaultVariants: { variant: "default", }, - }, -); + } +) const Toast = React.forwardRef< React.ElementRef, @@ -49,9 +49,9 @@ const Toast = React.forwardRef< className={cn(toastVariants({ variant }), className)} {...props} /> - ); -}); -Toast.displayName = ToastPrimitives.Root.displayName; + ) +}) +Toast.displayName = ToastPrimitives.Root.displayName const ToastAction = React.forwardRef< React.ElementRef, @@ -60,13 +60,13 @@ const ToastAction = React.forwardRef< -)); -ToastAction.displayName = ToastPrimitives.Action.displayName; +)) +ToastAction.displayName = ToastPrimitives.Action.displayName const ToastClose = React.forwardRef< React.ElementRef, @@ -76,15 +76,15 @@ const ToastClose = React.forwardRef< ref={ref} className={cn( "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", - className, + className )} toast-close="" {...props} > -)); -ToastClose.displayName = ToastPrimitives.Close.displayName; +)) +ToastClose.displayName = ToastPrimitives.Close.displayName const ToastTitle = React.forwardRef< React.ElementRef, @@ -95,8 +95,8 @@ const ToastTitle = React.forwardRef< className={cn("text-sm font-semibold", className)} {...props} /> -)); -ToastTitle.displayName = ToastPrimitives.Title.displayName; +)) +ToastTitle.displayName = ToastPrimitives.Title.displayName const ToastDescription = React.forwardRef< React.ElementRef, @@ -107,12 +107,12 @@ const ToastDescription = React.forwardRef< className={cn("text-sm opacity-90", className)} {...props} /> -)); -ToastDescription.displayName = ToastPrimitives.Description.displayName; +)) +ToastDescription.displayName = ToastPrimitives.Description.displayName -type ToastProps = React.ComponentPropsWithoutRef; +type ToastProps = React.ComponentPropsWithoutRef -type ToastActionElement = React.ReactElement; +type ToastActionElement = React.ReactElement export { type ToastProps, @@ -124,4 +124,4 @@ export { ToastDescription, ToastClose, ToastAction, -}; +} diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts index a0aa2a7f5..90d8959bf 100644 --- a/src/components/ui/use-toast.ts +++ b/src/components/ui/use-toast.ts @@ -1,73 +1,76 @@ // Inspired by react-hot-toast library -import * as React from "react"; +import * as React from "react" -import { ToastActionElement, type ToastProps } from "@/components/ui/toast"; +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} const actionTypes = { ADD_TOAST: "ADD_TOAST", UPDATE_TOAST: "UPDATE_TOAST", DISMISS_TOAST: "DISMISS_TOAST", REMOVE_TOAST: "REMOVE_TOAST", -} as const; +} as const -let count = 0; +let count = 0 function genId() { - count = (count + 1) % Number.MAX_VALUE; - return count.toString(); + count = (count + 1) % Number.MAX_VALUE + return count.toString() } -type ActionType = typeof actionTypes; +type ActionType = typeof actionTypes type Action = | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; + type: ActionType["ADD_TOAST"] + toast: ToasterToast } | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; + type: ActionType["UPDATE_TOAST"] + toast: Partial } | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] } | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } interface State { - toasts: ToasterToast[]; + toasts: ToasterToast[] } -const toastTimeouts = new Map>(); +const toastTimeouts = new Map>() const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { - return; + return } const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); + toastTimeouts.delete(toastId) dispatch({ type: "REMOVE_TOAST", toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); + }) + }, TOAST_REMOVE_DELAY) - toastTimeouts.set(toastId, timeout); -}; + toastTimeouts.set(toastId, timeout) +} export const reducer = (state: State, action: Action): State => { switch (action.type) { @@ -75,27 +78,27 @@ export const reducer = (state: State, action: Action): State => { return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; + } case "UPDATE_TOAST": return { ...state, toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, + t.id === action.toast.id ? { ...t, ...action.toast } : t ), - }; + } case "DISMISS_TOAST": { - const { toastId } = action; + const { toastId } = action // ! Side effects ! - This could be extracted into a dismissToast() action, // but I'll keep it here for simplicity if (toastId) { - addToRemoveQueue(toastId); + addToRemoveQueue(toastId) } else { state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); + addToRemoveQueue(toast.id) + }) } return { @@ -106,46 +109,46 @@ export const reducer = (state: State, action: Action): State => { ...t, open: false, } - : t, + : t ), - }; + } } case "REMOVE_TOAST": if (action.toastId === undefined) { return { ...state, toasts: [], - }; + } } return { ...state, toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; + } } -}; +} -const listeners: Array<(state: State) => void> = []; +const listeners: Array<(state: State) => void> = [] -let memoryState: State = { toasts: [] }; +let memoryState: State = { toasts: [] } function dispatch(action: Action) { - memoryState = reducer(memoryState, action); + memoryState = reducer(memoryState, action) listeners.forEach((listener) => { - listener(memoryState); - }); + listener(memoryState) + }) } -interface Toast extends Omit {} +type Toast = Omit function toast({ ...props }: Toast) { - const id = genId(); + const id = genId() const update = (props: ToasterToast) => dispatch({ type: "UPDATE_TOAST", toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) dispatch({ type: "ADD_TOAST", @@ -154,36 +157,36 @@ function toast({ ...props }: Toast) { id, open: true, onOpenChange: (open) => { - if (!open) dismiss(); + if (!open) dismiss() }, }, - }); + }) return { id: id, dismiss, update, - }; + } } function useToast() { - const [state, setState] = React.useState(memoryState); + const [state, setState] = React.useState(memoryState) React.useEffect(() => { - listeners.push(setState); + listeners.push(setState) return () => { - const index = listeners.indexOf(setState); + const index = listeners.indexOf(setState) if (index > -1) { - listeners.splice(index, 1); + listeners.splice(index, 1) } - }; - }, [state]); + } + }, [state]) return { ...state, toast, dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; + } } -export { useToast, toast }; +export { useToast, toast }