A very performant and light (2mb in memory) link shortener and tracker. Written in Rust and React and uses Postgres/SQLite.
at master 3.9 kB view raw
1import * as React from "react" 2 3import type { 4 ToastActionElement, 5 ToastProps, 6} from "@/components/ui/toast" 7 8const TOAST_LIMIT = 1 9const TOAST_REMOVE_DELAY = 1000000 10 11type ToasterToast = ToastProps & { 12 id: string 13 title?: React.ReactNode 14 description?: React.ReactNode 15 action?: ToastActionElement 16} 17 18const actionTypes = { 19 ADD_TOAST: "ADD_TOAST", 20 UPDATE_TOAST: "UPDATE_TOAST", 21 DISMISS_TOAST: "DISMISS_TOAST", 22 REMOVE_TOAST: "REMOVE_TOAST", 23} as const 24 25let count = 0 26 27function genId() { 28 count = (count + 1) % Number.MAX_SAFE_INTEGER 29 return count.toString() 30} 31 32type ActionType = typeof actionTypes 33 34type Action = 35 | { 36 type: ActionType["ADD_TOAST"] 37 toast: ToasterToast 38 } 39 | { 40 type: ActionType["UPDATE_TOAST"] 41 toast: Partial<ToasterToast> 42 } 43 | { 44 type: ActionType["DISMISS_TOAST"] 45 toastId?: ToasterToast["id"] 46 } 47 | { 48 type: ActionType["REMOVE_TOAST"] 49 toastId?: ToasterToast["id"] 50 } 51 52interface State { 53 toasts: ToasterToast[] 54} 55 56const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() 57 58const addToRemoveQueue = (toastId: string) => { 59 if (toastTimeouts.has(toastId)) { 60 return 61 } 62 63 const timeout = setTimeout(() => { 64 toastTimeouts.delete(toastId) 65 dispatch({ 66 type: "REMOVE_TOAST", 67 toastId: toastId, 68 }) 69 }, TOAST_REMOVE_DELAY) 70 71 toastTimeouts.set(toastId, timeout) 72} 73 74export const reducer = (state: State, action: Action): State => { 75 switch (action.type) { 76 case "ADD_TOAST": 77 return { 78 ...state, 79 toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 80 } 81 82 case "UPDATE_TOAST": 83 return { 84 ...state, 85 toasts: state.toasts.map((t) => 86 t.id === action.toast.id ? { ...t, ...action.toast } : t 87 ), 88 } 89 90 case "DISMISS_TOAST": { 91 const { toastId } = action 92 93 // ! Side effects ! - This could be extracted into a dismissToast() action, 94 // but I'll keep it here for simplicity 95 if (toastId) { 96 addToRemoveQueue(toastId) 97 } else { 98 state.toasts.forEach((toast) => { 99 addToRemoveQueue(toast.id) 100 }) 101 } 102 103 return { 104 ...state, 105 toasts: state.toasts.map((t) => 106 t.id === toastId || toastId === undefined 107 ? { 108 ...t, 109 open: false, 110 } 111 : t 112 ), 113 } 114 } 115 case "REMOVE_TOAST": 116 if (action.toastId === undefined) { 117 return { 118 ...state, 119 toasts: [], 120 } 121 } 122 return { 123 ...state, 124 toasts: state.toasts.filter((t) => t.id !== action.toastId), 125 } 126 } 127} 128 129const listeners: Array<(state: State) => void> = [] 130 131let memoryState: State = { toasts: [] } 132 133function dispatch(action: Action) { 134 memoryState = reducer(memoryState, action) 135 listeners.forEach((listener) => { 136 listener(memoryState) 137 }) 138} 139 140type Toast = Omit<ToasterToast, "id"> 141 142function toast({ ...props }: Toast) { 143 const id = genId() 144 145 const update = (props: ToasterToast) => 146 dispatch({ 147 type: "UPDATE_TOAST", 148 toast: { ...props, id }, 149 }) 150 const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) 151 152 dispatch({ 153 type: "ADD_TOAST", 154 toast: { 155 ...props, 156 id, 157 open: true, 158 onOpenChange: (open) => { 159 if (!open) dismiss() 160 }, 161 }, 162 }) 163 164 return { 165 id: id, 166 dismiss, 167 update, 168 } 169} 170 171function useToast() { 172 const [state, setState] = React.useState<State>(memoryState) 173 174 React.useEffect(() => { 175 listeners.push(setState) 176 return () => { 177 const index = listeners.indexOf(setState) 178 if (index > -1) { 179 listeners.splice(index, 1) 180 } 181 } 182 }, [state]) 183 184 return { 185 ...state, 186 toast, 187 dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 188 } 189} 190 191export { useToast, toast }