From 10717432907d0ad86a1fef6a58567ebb126690ec Mon Sep 17 00:00:00 2001 From: Triston Armstrong Date: Thu, 10 Oct 2024 03:35:35 -0400 Subject: [PATCH] fix(images): adds a toaster and handles failed image inserts The image insert will throw a dom exception if the image size exceeds the alotted storage provided by local storage. This just lets the user know somethng fufuckedd up and atleast removeds the image after inserting (inknow, dont tell me) This iwill be solved in the future with IDB --- index.html | 3 +- src/App.tsx | 7 +- src/components/Toast.tsx | 112 ++++++++++++++++++ .../cardSelector/ImageCardButton.tsx | 15 ++- src/signals/images.ts | 1 + src/styles.css | 26 ++++ tailwind.config.js | 8 +- 7 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/components/Toast.tsx diff --git a/index.html b/index.html index 7690c63..9694cc5 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,8 @@ -
+
+
diff --git a/src/App.tsx b/src/App.tsx index c964f59..46d4d53 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,13 @@ import InfiniteCanvas from "./components/InfinateCanvas" +import { ToastContextProvider } from "./components/Toast" import { useThemeDetector } from "./utils/useThemeDetector" export function App() { useThemeDetector() - return + return ( + + + + ) } diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx new file mode 100644 index 0000000..2779928 --- /dev/null +++ b/src/components/Toast.tsx @@ -0,0 +1,112 @@ +import { + createContext, + memo, + Portal, + Transition, + TransitionState, + useContext, + useEffect, + useState, +} from "kaioken" + +type Toast = { + ts: number + type: "info" | "success" | "warning" | "error" + message: string + expires: number + expired?: boolean +} + +const defaultDuration = 3000 + +const ToastContext = createContext<{ + showToast: (type: Toast["type"], message: string) => void +}>(null as any) + +export const useToast = () => useContext(ToastContext) + +export const ToastContextProvider: Kaioken.FC = ({ children }) => { + const [toasts, setToasts] = useState([]) + + useEffect(() => { + const interval = setInterval(() => { + setToasts((prev) => + prev.map((toast) => { + if (toast.expired) { + return toast + } + return { + ...toast, + expired: Date.now() > toast.expires, + } + }) + ) + }, 500) + return () => clearInterval(interval) + }, []) + + const showToast = ( + type: Toast["type"], + message: string, + duration?: number + ) => { + const ts = Date.now() + const toast = { + ts, + type, + message, + expires: ts + (duration ?? defaultDuration), + } + setToasts((prev) => [...prev, toast]) + } + + return ( + + {children} + document.getElementById("toast-root")!}> + {toasts.map((toast, i) => ( + { + if (state === "exited") { + setToasts((prev) => prev.filter((t) => t.ts !== toast.ts)) + } + }} + element={(state) => } + /> + ))} + + + ) +} + +const ToastItem = memo( + ({ + toast, + state, + i, + }: { + toast: Toast + state: TransitionState + i: number + }) => { + if (state == "exited") return null + const translateX = state === "entered" ? 0 : 250 + const translateY = i * 70 + return ( +
+ {toast.message} +
+ ) + } +) diff --git a/src/components/cardSelector/ImageCardButton.tsx b/src/components/cardSelector/ImageCardButton.tsx index 4958b1b..d5491ef 100644 --- a/src/components/cardSelector/ImageCardButton.tsx +++ b/src/components/cardSelector/ImageCardButton.tsx @@ -1,10 +1,12 @@ import { ImagesSignal } from "../../signals" import images from "../../signals/images" import { updateLocalStorage } from "../../utils/localStorage" +import { useToast } from "../Toast" import { Tooltip } from "./Tooltip" import { defaultClassName } from "./utils" export function ImageCardButton() { + const toast = useToast() function _handleClick(mouseEvent: MouseEvent) { const input = document.createElement('input') input.type = 'file' @@ -30,7 +32,7 @@ export function ImageCardButton() { if (typeof content == 'string') img = content?.split(':')[1] if (!img) return - ImagesSignal.default.addImage({ + const imgId = ImagesSignal.default.addImage({ type: "image", title: "New Image", contents: content as string, @@ -43,7 +45,16 @@ export function ImageCardButton() { h: normalizedH } }) - updateLocalStorage("images", images.images.value) + + try { + updateLocalStorage("images", images.images.value) + } catch (e: unknown) { + if (e instanceof DOMException) { + if (e.name !== 'QuotaExceededError') return + toast.showToast("error", "Could not add such a girthy image!") + ImagesSignal.default.removeImage(imgId) + } + } } image.src = readerEvent.target?.result as string } diff --git a/src/signals/images.ts b/src/signals/images.ts index 381b2aa..3fe2f8c 100644 --- a/src/signals/images.ts +++ b/src/signals/images.ts @@ -18,6 +18,7 @@ function addImage(data: Omit) { images.value[newCard.id] = newCard images.notify() focusedItem.value = newCard.id + return newCard.id } function removeImage(id: ImageCardType["id"]) { diff --git a/src/styles.css b/src/styles.css index 2cad54e..ef9368e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -35,3 +35,29 @@ body { margin: 0; padding: 0; } + +.toast { + @apply flex flex-col items-center justify-center; + @apply fixed right-4 top-4 w-52 px-4; + @apply transition-all duration-300 rounded z-50; +} + +.toast-message { + color: #e5e5e5; +} + +.toast.toast-info { + @apply bg-info; +} + +.toast.toast-success { + @apply bg-success; +} + +.toast.toast-warning { + @apply bg-warning; +} + +.toast.toast-error { + @apply bg-error; +} diff --git a/tailwind.config.js b/tailwind.config.js index ee39c6c..f653b80 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,10 +4,12 @@ export default { theme: { extend: { colors: { - primary: "rgb(64 37 121)", - "primary-light": "rgb(195 177 232)", + info: "#4f46e5", + success: "#267d46", + warning: "#a46319", + error: "#963030", }, }, }, plugins: [], -}; +}