From f1e961e8e4ecbbeccbc31203b301a3c1a8b9e6f7 Mon Sep 17 00:00:00 2001
From: alexsparkes
Date: Mon, 26 Jan 2026 16:48:47 +0000
Subject: [PATCH] feat(storage): implement dynamic storage quota estimation and
request persistence
---
src/features/background/options/Custom.jsx | 83 +++++++++++++++-------
1 file changed, 58 insertions(+), 25 deletions(-)
diff --git a/src/features/background/options/Custom.jsx b/src/features/background/options/Custom.jsx
index 167280c7..c916bdbd 100644
--- a/src/features/background/options/Custom.jsx
+++ b/src/features/background/options/Custom.jsx
@@ -56,10 +56,33 @@ const CustomSettings = memo(() => {
const [selectedImages, setSelectedImages] = useState(new Set());
const [sortBy, setSortBy] = useState(localStorage.getItem('customImageSort') || 'date_desc');
const [storageQuotaModal, setStorageQuotaModal] = useState(false);
+ const [storageQuota, setStorageQuota] = useState({ usage: 0, quota: 0 });
const customDnd = useRef(null);
const dragCounter = useRef(0);
- const STORAGE_LIMIT = 4850000; // 4.85MB
+ // IndexedDB typically has 50MB+ quota, we'll check dynamically
+ const FALLBACK_STORAGE_LIMIT = 50000000; // 50MB fallback if API unavailable
+
+ // Fetch storage quota
+ useEffect(() => {
+ const fetchQuota = async () => {
+ if (navigator.storage && navigator.storage.estimate) {
+ try {
+ const estimate = await navigator.storage.estimate();
+ setStorageQuota({
+ usage: estimate.usage || 0,
+ quota: estimate.quota || FALLBACK_STORAGE_LIMIT,
+ });
+ } catch (error) {
+ console.warn('Could not get storage estimate:', error);
+ setStorageQuota({ usage: 0, quota: FALLBACK_STORAGE_LIMIT });
+ }
+ } else {
+ setStorageQuota({ usage: 0, quota: FALLBACK_STORAGE_LIMIT });
+ }
+ };
+ fetchQuota();
+ }, [customBackground]);
// Load backgrounds from IndexedDB on mount
useEffect(() => {
@@ -145,8 +168,10 @@ const CustomSettings = memo(() => {
return total;
}, 0);
- // Request more storage if approaching limit (90%)
- if (storageSize / STORAGE_LIMIT > 0.9 && navigator.storage && navigator.storage.persist) {
+ const availableQuota = storageQuota.quota || FALLBACK_STORAGE_LIMIT;
+
+ // Request persistent storage if approaching limit (90%)
+ if (storageSize / availableQuota > 0.9 && navigator.storage && navigator.storage.persist) {
try {
const isPersisted = await navigator.storage.persist();
if (isPersisted) {
@@ -158,7 +183,7 @@ const CustomSettings = memo(() => {
}
if (videoCheck(file.type)) {
- if (storageSize + file.size > STORAGE_LIMIT) {
+ if (storageSize + file.size > availableQuota) {
throw new Error('no_storage');
}
@@ -186,7 +211,8 @@ const CustomSettings = memo(() => {
accuracy: 0.9,
});
- if (storageSize + compressed.size > STORAGE_LIMIT) {
+ const availableQuota = storageQuota.quota || FALLBACK_STORAGE_LIMIT;
+ if (storageSize + compressed.size > availableQuota) {
throw new Error('no_storage');
}
@@ -421,7 +447,8 @@ const CustomSettings = memo(() => {
}
return total;
}, 0);
- const storagePercent = (storageUsed / STORAGE_LIMIT) * 100;
+ const availableStorageLimit = storageQuota.quota || FALLBACK_STORAGE_LIMIT;
+ const storagePercent = (storageUsed / availableStorageLimit) * 100;
const totalStorageUsed = calculateTotalStorageSize();
const TOTAL_STORAGE_LIMIT = 5242880; // 5MB total localStorage limit (browser default)
@@ -570,26 +597,32 @@ const CustomSettings = memo(() => {
{customBackground.length} {customBackground.length === 1 ? 'image' : 'images'}
{' '}
- · {formatBytes(storageUsed)} / {formatBytes(STORAGE_LIMIT)}
+ · {formatBytes(storageUsed)} / {formatBytes(availableStorageLimit)}
{storagePercent > 80 && navigator.storage && navigator.storage.persist && (
-
+
)}
@@ -911,7 +944,7 @@ const CustomSettings = memo(() => {
Custom Backgrounds
- {formatBytes(storageUsed)} / {formatBytes(STORAGE_LIMIT)} (
+ {formatBytes(storageUsed)} / {formatBytes(availableStorageLimit)} (
{Math.round(storagePercent)}%)