211 lines
7.9 KiB
JavaScript
211 lines
7.9 KiB
JavaScript
// Rôle : synchronise les toolboxes, données d'outils, liens de jeux et quota via IndexedDB.
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import {
|
|
getAllModuleData as dbGetAllModuleData,
|
|
getLinks as dbGetLinks,
|
|
getSetting as dbGetSetting,
|
|
getStorageEstimate,
|
|
getToolboxes as dbGetToolboxes,
|
|
removeModuleData as dbRemoveModuleData,
|
|
removeModuleDataKeys as dbRemoveModuleDataKeys,
|
|
setLinks as dbSetLinks,
|
|
setModuleData as dbSetModuleData,
|
|
setSetting as dbSetSetting,
|
|
setToolboxes as dbSetToolboxes
|
|
} from "../../../utils/indexedDbStorage.js";
|
|
import {
|
|
compactToolboxesForStorage,
|
|
moduleStorageKey,
|
|
normalizeToolbox,
|
|
prepareModuleDataForStorage
|
|
} from "./toolboxStorage.js";
|
|
import {
|
|
applyModuleDataValue,
|
|
commitModuleDataCheckpoints,
|
|
createModuleDataCheckpoint,
|
|
createModuleDataCheckpoints,
|
|
removeModuleDataKeys,
|
|
rollbackModuleDataCheckpoints
|
|
} from "./optimisticModuleData.js";
|
|
|
|
export function useIndexedToolboxes(onError) {
|
|
const [ready, setReady] = useState(false);
|
|
const [toolboxes, setToolboxesState] = useState([]);
|
|
const [links, setLinksState] = useState({});
|
|
const [moduleData, setModuleDataState] = useState({});
|
|
const [toastPosition, setToastPositionState] = useState("right");
|
|
const [storageUsage, setStorageUsage] = useState({ used: 0, limit: 0, ratio: 0 });
|
|
const toolboxesRef = useRef(toolboxes);
|
|
const linksRef = useRef(links);
|
|
const moduleDataRef = useRef(moduleData);
|
|
const toastPositionRef = useRef(toastPosition);
|
|
const writeVersionsRef = useRef({
|
|
toolboxes: 0,
|
|
links: 0,
|
|
moduleDataByKey: {},
|
|
toastPosition: 0
|
|
});
|
|
|
|
const reportStorageError = useCallback((error) => {
|
|
onError(error?.message || "Erreur de stockage IndexedDB.");
|
|
}, [onError]);
|
|
|
|
function commitToolboxes(nextToolboxes) {
|
|
toolboxesRef.current = nextToolboxes;
|
|
setToolboxesState(nextToolboxes);
|
|
}
|
|
|
|
function commitLinks(nextLinks) {
|
|
linksRef.current = nextLinks;
|
|
setLinksState(nextLinks);
|
|
}
|
|
|
|
function commitModuleData(nextModuleData) {
|
|
moduleDataRef.current = nextModuleData;
|
|
setModuleDataState(nextModuleData);
|
|
}
|
|
|
|
function commitToastPosition(nextToastPosition) {
|
|
toastPositionRef.current = nextToastPosition;
|
|
setToastPositionState(nextToastPosition);
|
|
}
|
|
|
|
const refreshQuota = useCallback(async () => {
|
|
try {
|
|
const estimate = await getStorageEstimate();
|
|
const used = estimate.usage || 0;
|
|
const limit = estimate.quota || 0;
|
|
setStorageUsage({ used, limit, ratio: limit ? Math.min(1, used / limit) : 0 });
|
|
} catch {
|
|
setStorageUsage({ used: 0, limit: 0, ratio: 0 });
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
async function loadStore() {
|
|
try {
|
|
const [storedToolboxes, storedLinks, storedModules, storedToastPosition] = await Promise.all([
|
|
dbGetToolboxes(),
|
|
dbGetLinks(),
|
|
dbGetAllModuleData(),
|
|
dbGetSetting("toastPosition", "right")
|
|
]);
|
|
if (cancelled) return;
|
|
commitToolboxes((Array.isArray(storedToolboxes) ? storedToolboxes : []).map(normalizeToolbox).filter(Boolean));
|
|
commitLinks(storedLinks && typeof storedLinks === "object" ? storedLinks : {});
|
|
commitModuleData(Object.fromEntries((storedModules || []).map((entry) => [entry.key, entry.data])));
|
|
commitToastPosition(storedToastPosition === "left" ? "left" : "right");
|
|
setReady(true);
|
|
refreshQuota();
|
|
} catch (error) {
|
|
if (!cancelled) {
|
|
setReady(true);
|
|
reportStorageError(error);
|
|
}
|
|
}
|
|
}
|
|
loadStore();
|
|
return () => { cancelled = true; };
|
|
}, [refreshQuota, reportStorageError]);
|
|
|
|
function persistToolboxes(nextToolboxes) {
|
|
const normalized = compactToolboxesForStorage(nextToolboxes).map(normalizeToolbox).filter(Boolean);
|
|
const previousToolboxes = toolboxesRef.current;
|
|
const version = ++writeVersionsRef.current.toolboxes;
|
|
commitToolboxes(normalized);
|
|
dbSetToolboxes(compactToolboxesForStorage(normalized))
|
|
.then(refreshQuota)
|
|
.catch((error) => {
|
|
if (writeVersionsRef.current.toolboxes === version) commitToolboxes(previousToolboxes);
|
|
reportStorageError(error);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function persistLinks(nextLinks) {
|
|
const previousLinks = linksRef.current;
|
|
const version = ++writeVersionsRef.current.links;
|
|
commitLinks(nextLinks);
|
|
dbSetLinks(nextLinks)
|
|
.then(refreshQuota)
|
|
.catch((error) => {
|
|
if (writeVersionsRef.current.links === version) commitLinks(previousLinks);
|
|
reportStorageError(error);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function getModuleData(toolboxId, moduleId, fallback) {
|
|
const value = moduleDataRef.current[moduleStorageKey(toolboxId, moduleId)];
|
|
return value == null ? fallback : value;
|
|
}
|
|
|
|
function updateModuleData(toolboxId, moduleId, value, moduleType = "") {
|
|
const compact = prepareModuleDataForStorage(toolboxesRef.current, toolboxId, moduleId, value, moduleType);
|
|
const key = moduleStorageKey(toolboxId, moduleId);
|
|
const checkpoint = createModuleDataCheckpoint(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, key);
|
|
writeVersionsRef.current.moduleDataByKey = commitModuleDataCheckpoints(writeVersionsRef.current.moduleDataByKey, [checkpoint]);
|
|
const nextModuleData = applyModuleDataValue(moduleDataRef.current, key, compact);
|
|
commitModuleData(nextModuleData);
|
|
const operation = compact == null ? dbRemoveModuleData(key) : dbSetModuleData(key, compact);
|
|
operation.then(refreshQuota).catch((error) => {
|
|
commitModuleData(rollbackModuleDataCheckpoints(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, [checkpoint]));
|
|
reportStorageError(error);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function removeModuleData(toolboxId, moduleId) {
|
|
const key = moduleStorageKey(toolboxId, moduleId);
|
|
const checkpoint = createModuleDataCheckpoint(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, key);
|
|
writeVersionsRef.current.moduleDataByKey = commitModuleDataCheckpoints(writeVersionsRef.current.moduleDataByKey, [checkpoint]);
|
|
commitModuleData(removeModuleDataKeys(moduleDataRef.current, [key]));
|
|
dbRemoveModuleData(key).then(refreshQuota).catch((error) => {
|
|
commitModuleData(rollbackModuleDataCheckpoints(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, [checkpoint]));
|
|
reportStorageError(error);
|
|
});
|
|
}
|
|
|
|
function removeToolboxModuleData(toolbox) {
|
|
const keys = (toolbox?.modules || []).map((module) => moduleStorageKey(toolbox.id, module.id));
|
|
if (!keys.length) return;
|
|
const checkpoints = createModuleDataCheckpoints(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, keys);
|
|
writeVersionsRef.current.moduleDataByKey = commitModuleDataCheckpoints(writeVersionsRef.current.moduleDataByKey, checkpoints);
|
|
commitModuleData(removeModuleDataKeys(moduleDataRef.current, keys));
|
|
dbRemoveModuleDataKeys(keys).then(refreshQuota).catch((error) => {
|
|
commitModuleData(rollbackModuleDataCheckpoints(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, checkpoints));
|
|
reportStorageError(error);
|
|
});
|
|
}
|
|
|
|
function setToastPosition(position) {
|
|
const normalized = position === "left" ? "left" : "right";
|
|
const previousToastPosition = toastPositionRef.current;
|
|
const version = ++writeVersionsRef.current.toastPosition;
|
|
commitToastPosition(normalized);
|
|
dbSetSetting("toastPosition", normalized)
|
|
.then(refreshQuota)
|
|
.catch((error) => {
|
|
if (writeVersionsRef.current.toastPosition === version) commitToastPosition(previousToastPosition);
|
|
reportStorageError(error);
|
|
});
|
|
}
|
|
|
|
return {
|
|
ready,
|
|
toolboxes,
|
|
links,
|
|
moduleData,
|
|
toastPosition,
|
|
storageUsage,
|
|
setToolboxes: persistToolboxes,
|
|
setLinks: persistLinks,
|
|
getModuleData,
|
|
updateModuleData,
|
|
removeModuleData,
|
|
removeToolboxModuleData,
|
|
setToastPosition,
|
|
refreshQuota
|
|
};
|
|
}
|