From 1d45b814e675bc1dd4fa6ff56deab81d5cc437c3 Mon Sep 17 00:00:00 2001 From: NilsUeter Date: Tue, 18 Mar 2025 17:25:25 +0100 Subject: [PATCH] Use IndexedDB for storage to avoid the 5MB limit of localStorage. --- src/10th/Roster.jsx | 4 +- src/helpers/useIndexedDB.js | 114 ++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/helpers/useIndexedDB.js diff --git a/src/10th/Roster.jsx b/src/10th/Roster.jsx index 3b1c1c7..9af97c1 100644 --- a/src/10th/Roster.jsx +++ b/src/10th/Roster.jsx @@ -3,7 +3,7 @@ import factionBackground from "../assets/factionBackground.png"; import adeptusAstartesIcon from "../assets/adeptusAstartesIcon.png"; import { Arrow, wavyLine } from "../assets/icons"; import { Weapons, hasDifferentProfiles } from "./Weapons"; -import { useLocalStorage } from "../helpers/useLocalStorage"; +import { useIndexedDB } from "../helpers/useIndexedDB"; // New hook for IndexedDB import { ImgEditor } from "./ImgEditor"; export const Roster = ({ roster, onePerPage, colorUserChoice }) => { @@ -91,7 +91,7 @@ const Unit = ({ modelList, cost, } = unit; - const [image, setImage] = useLocalStorage("image" + name); + const [image, setImage] = useIndexedDB("image" + name); // Use IndexedDB hook const hasImage = image && image !== "undefined"; const [bgRemoved, setBgRemoved] = useState(false); diff --git a/src/helpers/useIndexedDB.js b/src/helpers/useIndexedDB.js new file mode 100644 index 0000000..3070e4c --- /dev/null +++ b/src/helpers/useIndexedDB.js @@ -0,0 +1,114 @@ +import { useState, useEffect } from "react"; + +const DB_NAME = "fancyscribe"; +const STORE_NAME = "images"; + +const openDB = () => { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 1); + + request.onupgradeneeded = (event) => { + const db = event.target.result; + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME); + } + }; + + request.onsuccess = () => { + resolve(request.result); + }; + + request.onerror = () => { + reject(request.error); + }; + }); +}; + +const getItem = (db, key) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction([STORE_NAME], "readonly"); + const store = transaction.objectStore(STORE_NAME); + const request = store.get(key); + + request.onsuccess = () => { + resolve(request.result); + }; + + request.onerror = () => { + reject(request.error); + }; + }); +}; + +const setItem = (db, key, value) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction([STORE_NAME], "readwrite"); + const store = transaction.objectStore(STORE_NAME); + const request = store.put(value, key); + + request.onsuccess = () => { + resolve(); + }; + + request.onerror = () => { + reject(request.error); + }; + }); +}; + +export const useIndexedDB = (key) => { + const [value, setValue] = useState(null); + + useEffect(() => { + let db; + let isMounted = true; + + const fetchData = () => { + openDB() + .then((database) => { + db = database; + return getItem(db, key); + }) + .then((storedValue) => { + if (isMounted) { + setValue(storedValue); + } + }) + .catch((error) => { + console.error("Failed to open DB or get item", error); + }); + }; + + fetchData(); + + const handleStorageChange = (event) => { + if (event?.detail?.key === key) { + fetchData(); + } + }; + + window.addEventListener("storage", handleStorageChange); + + return () => { + isMounted = false; + window.removeEventListener("storage", handleStorageChange); + if (db) { + db.close(); + } + }; + }, [key]); + + const setStoredValue = (newValue) => { + openDB() + .then((db) => setItem(db, key, newValue)) + .then(() => { + setValue(newValue); + window.dispatchEvent(new CustomEvent("storage", { detail: { key } })); + }) + .catch((error) => { + console.error("Failed to set item", error); + }); + }; + + return [value, setStoredValue]; +};