Fix toolbox persistence, table evaluation, and onboarding panel styling
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s
This commit is contained in:
parent
0171c9fc98
commit
184314d913
9 changed files with 119 additions and 32 deletions
|
|
@ -7,6 +7,15 @@ import { resetSokkoStorage } from "./helpers/storage.js";
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const fixturePath = path.join(__dirname, "fixtures", "toolbox-export.json");
|
const fixturePath = path.join(__dirname, "fixtures", "toolbox-export.json");
|
||||||
|
|
||||||
|
async function collectBrowserErrors(page) {
|
||||||
|
const errors = [];
|
||||||
|
page.on("pageerror", (error) => errors.push(`pageerror: ${error.stack || error.message}`));
|
||||||
|
page.on("console", (message) => {
|
||||||
|
if (message.type() === "error") errors.push(`console: ${message.text()}`);
|
||||||
|
});
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
async function createToolbox(page, name) {
|
async function createToolbox(page, name) {
|
||||||
await page.goto("/#/toolboxes");
|
await page.goto("/#/toolboxes");
|
||||||
await expect(page.getByRole("main")).toBeVisible();
|
await expect(page.getByRole("main")).toBeVisible();
|
||||||
|
|
@ -35,6 +44,11 @@ function tableCell(table, address) {
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await resetSokkoStorage(page);
|
await resetSokkoStorage(page);
|
||||||
|
page.browserErrors = await collectBrowserErrors(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(async ({ page }) => {
|
||||||
|
expect(page.browserErrors, page.browserErrors.join("\n")).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("creates a toolbox and lazy-loads an added tool", async ({ page }) => {
|
test("creates a toolbox and lazy-loads an added tool", async ({ page }) => {
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,11 @@ export function DrawingOverlay({ active, erase, strokes, color, width, onChange,
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function syncSize() {
|
function syncSize() {
|
||||||
const nextSize = getSurfaceSize?.() || { width: 0, height: 0 };
|
const nextSize = getSurfaceSize?.() || { width: 0, height: 0 };
|
||||||
setSize({
|
const nextWidth = Math.max(0, Math.ceil(nextSize.width || 0));
|
||||||
width: Math.max(0, Math.ceil(nextSize.width || 0)),
|
const nextHeight = Math.max(0, Math.ceil(nextSize.height || 0));
|
||||||
height: Math.max(0, Math.ceil(nextSize.height || 0))
|
setSize((current) => current.width === nextWidth && current.height === nextHeight
|
||||||
});
|
? current
|
||||||
|
: { width: nextWidth, height: nextHeight });
|
||||||
}
|
}
|
||||||
|
|
||||||
syncSize();
|
syncSize();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { DrawingControls } from "./DrawingControls.jsx";
|
||||||
import { DrawingOverlay } from "./DrawingOverlay.jsx";
|
import { DrawingOverlay } from "./DrawingOverlay.jsx";
|
||||||
import { NotepadToolbar } from "./NotepadToolbar.jsx";
|
import { NotepadToolbar } from "./NotepadToolbar.jsx";
|
||||||
import { TextExchangeActions } from "./TextExchangeActions.jsx";
|
import { TextExchangeActions } from "./TextExchangeActions.jsx";
|
||||||
|
import { normalizeNotepadData } from "../storage/toolboxStorage.js";
|
||||||
import {
|
import {
|
||||||
DRAWING_WIDTHS,
|
DRAWING_WIDTHS,
|
||||||
HIGHLIGHT_COLORS,
|
HIGHLIGHT_COLORS,
|
||||||
|
|
@ -21,7 +22,7 @@ import {
|
||||||
|
|
||||||
export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }) {
|
export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }) {
|
||||||
const storedData = context.getModuleData(toolboxId, moduleId, { text: "" });
|
const storedData = context.getModuleData(toolboxId, moduleId, { text: "" });
|
||||||
const normalizedData = useMemo(() => context.normalizeNotepadData(storedData), [context, storedData]);
|
const normalizedData = useMemo(() => normalizeNotepadData(storedData), [storedData]);
|
||||||
const textContent = context.moduleText?.notepad || {};
|
const textContent = context.moduleText?.notepad || {};
|
||||||
const notify = context.notify;
|
const notify = context.notify;
|
||||||
const activePage = normalizedData.pages.find((page) => page.id === normalizedData.activePageId) || normalizedData.pages[0];
|
const activePage = normalizedData.pages.find((page) => page.id === normalizedData.activePageId) || normalizedData.pages[0];
|
||||||
|
|
@ -43,7 +44,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
const [textColor, setTextColor] = useState(TEXT_COLORS[0]);
|
const [textColor, setTextColor] = useState(TEXT_COLORS[0]);
|
||||||
const [highlightColor, setHighlightColor] = useState(HIGHLIGHT_COLORS[0]);
|
const [highlightColor, setHighlightColor] = useState(HIGHLIGHT_COLORS[0]);
|
||||||
const [editingPageId, setEditingPageId] = useState("");
|
const [editingPageId, setEditingPageId] = useState("");
|
||||||
const [pageTitleDrafts, setPageTitleDrafts] = useState({});
|
const [pageTitleDrafts, setPageTitleDrafts] = useState(() => Object.fromEntries(normalizedData.pages.map((page) => [page.id, page.title])));
|
||||||
const [activeFormats, setActiveFormats] = useState({
|
const [activeFormats, setActiveFormats] = useState({
|
||||||
bold: false,
|
bold: false,
|
||||||
italic: false,
|
italic: false,
|
||||||
|
|
@ -56,10 +57,6 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
||||||
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}:${activePage.id}`;
|
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}:${activePage.id}`;
|
||||||
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
|
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPageTitleDrafts(Object.fromEntries(normalizedData.pages.map((page) => [page.id, page.title])));
|
|
||||||
}, [normalizedData.pages]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pageKey = `${toolboxId}:${moduleId}:${activePage.id}`;
|
const pageKey = `${toolboxId}:${moduleId}:${activePage.id}`;
|
||||||
if (pageKeyRef.current !== pageKey) {
|
if (pageKeyRef.current !== pageKey) {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ import {
|
||||||
import { useTableInteractions } from "./useTableInteractions.js";
|
import { useTableInteractions } from "./useTableInteractions.js";
|
||||||
|
|
||||||
export function TableModule({ toolboxId, moduleId, context, textExchangeOpen }) {
|
export function TableModule({ toolboxId, moduleId, context, textExchangeOpen }) {
|
||||||
const data = context.normalizeTableData(context.getModuleData(toolboxId, moduleId, { rows: DEFAULT_ROWS, columns: DEFAULT_COLUMNS, cells: {} }));
|
const rawData = context.getModuleData(toolboxId, moduleId, { rows: DEFAULT_ROWS, columns: DEFAULT_COLUMNS, cells: {} });
|
||||||
|
const data = useMemo(() => context.normalizeTableData(rawData), [context, rawData]);
|
||||||
const textContent = context.moduleText?.table || {};
|
const textContent = context.moduleText?.table || {};
|
||||||
const evaluatedCells = useMemo(() => evaluateTable(data.cells, data), [data]);
|
const evaluatedCells = useMemo(() => evaluateTable(data.cells, data), [data]);
|
||||||
const columnWidths = useMemo(() => getColumnWidths(data, evaluatedCells), [data, evaluatedCells]);
|
const columnWidths = useMemo(() => getColumnWidths(data, evaluatedCells), [data, evaluatedCells]);
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,11 @@ export function evaluateFormula(expression, resolveReference) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evaluateTableCell(address, cells, dimensions, stack = []) {
|
export function evaluateTableCell(address, cells, dimensions, stack = []) {
|
||||||
|
return evaluateTableCellWithCache(address, cells, dimensions, stack, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateTableCellWithCache(address, cells, dimensions, stack, cache) {
|
||||||
|
if (!stack.length && cache.has(address)) return cache.get(address);
|
||||||
const raw = String(cells?.[address] || "").trim();
|
const raw = String(cells?.[address] || "").trim();
|
||||||
if (!raw) return { value: null, display: "", error: "empty" };
|
if (!raw) return { value: null, display: "", error: "empty" };
|
||||||
|
|
||||||
|
|
@ -176,25 +181,30 @@ export function evaluateTableCell(address, cells, dimensions, stack = []) {
|
||||||
|
|
||||||
const result = evaluateFormula(raw.slice(1), (reference) => {
|
const result = evaluateFormula(raw.slice(1), (reference) => {
|
||||||
if (!CELL_REFERENCE_RE.test(reference) || !isCellInBounds(reference, dimensions.rows, dimensions.columns)) throw new Error("reference");
|
if (!CELL_REFERENCE_RE.test(reference) || !isCellInBounds(reference, dimensions.rows, dimensions.columns)) throw new Error("reference");
|
||||||
const value = evaluateTableCell(reference, cells, dimensions, [...stack, address]);
|
const value = evaluateTableCellWithCache(reference, cells, dimensions, [...stack, address], cache);
|
||||||
if (value.error || value.value == null) throw new Error(value.error === "cycle" ? "cycle" : "reference");
|
if (value.error || value.value == null) throw new Error(value.error === "cycle" ? "cycle" : "reference");
|
||||||
return value.value;
|
return value.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
const label = result.error === "cycle" ? "#CYCLE" : result.error === "division" ? "#DIV/0" : "#ERREUR";
|
const label = result.error === "cycle" ? "#CYCLE" : result.error === "division" ? "#DIV/0" : "#ERREUR";
|
||||||
return { value: null, display: label, error: result.error };
|
const evaluated = { value: null, display: label, error: result.error };
|
||||||
|
if (!stack.length) cache.set(address, evaluated);
|
||||||
|
return evaluated;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { value: result.value, display: formatNumber(result.value), error: "" };
|
const evaluated = { value: result.value, display: formatNumber(result.value), error: "" };
|
||||||
|
if (!stack.length) cache.set(address, evaluated);
|
||||||
|
return evaluated;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evaluateTable(cells, dimensions) {
|
export function evaluateTable(cells, dimensions) {
|
||||||
const evaluated = {};
|
const evaluated = {};
|
||||||
|
const cache = new Map();
|
||||||
for (let rowIndex = 0; rowIndex < dimensions.rows; rowIndex += 1) {
|
for (let rowIndex = 0; rowIndex < dimensions.rows; rowIndex += 1) {
|
||||||
for (let columnIndex = 0; columnIndex < dimensions.columns; columnIndex += 1) {
|
for (let columnIndex = 0; columnIndex < dimensions.columns; columnIndex += 1) {
|
||||||
const address = cellAddress(rowIndex, columnIndex);
|
const address = cellAddress(rowIndex, columnIndex);
|
||||||
evaluated[address] = evaluateTableCell(address, cells, dimensions);
|
evaluated[address] = evaluateTableCellWithCache(address, cells, dimensions, [], cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evaluated;
|
return evaluated;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import {
|
||||||
setLinks as dbSetLinks,
|
setLinks as dbSetLinks,
|
||||||
setModuleData as dbSetModuleData,
|
setModuleData as dbSetModuleData,
|
||||||
setSetting as dbSetSetting,
|
setSetting as dbSetSetting,
|
||||||
setToolboxes as dbSetToolboxes
|
setToolboxes as dbSetToolboxes,
|
||||||
|
setToolboxSnapshot as dbSetToolboxSnapshot
|
||||||
} from "../../../utils/indexedDbStorage.js";
|
} from "../../../utils/indexedDbStorage.js";
|
||||||
import {
|
import {
|
||||||
compactToolboxesForStorage,
|
compactToolboxesForStorage,
|
||||||
|
|
@ -38,6 +39,8 @@ export function useIndexedToolboxes(onError) {
|
||||||
const toolboxesRef = useRef(toolboxes);
|
const toolboxesRef = useRef(toolboxes);
|
||||||
const linksRef = useRef(links);
|
const linksRef = useRef(links);
|
||||||
const moduleDataRef = useRef(moduleData);
|
const moduleDataRef = useRef(moduleData);
|
||||||
|
const confirmedModuleDataRef = useRef(moduleData);
|
||||||
|
const moduleWriteQueuesRef = useRef({});
|
||||||
const toastPositionRef = useRef(toastPosition);
|
const toastPositionRef = useRef(toastPosition);
|
||||||
const writeVersionsRef = useRef({
|
const writeVersionsRef = useRef({
|
||||||
toolboxes: 0,
|
toolboxes: 0,
|
||||||
|
|
@ -94,7 +97,9 @@ export function useIndexedToolboxes(onError) {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
commitToolboxes((Array.isArray(storedToolboxes) ? storedToolboxes : []).map(normalizeToolbox).filter(Boolean));
|
commitToolboxes((Array.isArray(storedToolboxes) ? storedToolboxes : []).map(normalizeToolbox).filter(Boolean));
|
||||||
commitLinks(storedLinks && typeof storedLinks === "object" ? storedLinks : {});
|
commitLinks(storedLinks && typeof storedLinks === "object" ? storedLinks : {});
|
||||||
commitModuleData(Object.fromEntries((storedModules || []).map((entry) => [entry.key, entry.data])));
|
const loadedModuleData = Object.fromEntries((storedModules || []).map((entry) => [entry.key, entry.data]));
|
||||||
|
confirmedModuleDataRef.current = loadedModuleData;
|
||||||
|
commitModuleData(loadedModuleData);
|
||||||
commitToastPosition(storedToastPosition === "left" ? "left" : "right");
|
commitToastPosition(storedToastPosition === "left" ? "left" : "right");
|
||||||
setReady(true);
|
setReady(true);
|
||||||
refreshQuota();
|
refreshQuota();
|
||||||
|
|
@ -148,11 +153,22 @@ export function useIndexedToolboxes(onError) {
|
||||||
writeVersionsRef.current.moduleDataByKey = commitModuleDataCheckpoints(writeVersionsRef.current.moduleDataByKey, [checkpoint]);
|
writeVersionsRef.current.moduleDataByKey = commitModuleDataCheckpoints(writeVersionsRef.current.moduleDataByKey, [checkpoint]);
|
||||||
const nextModuleData = applyModuleDataValue(moduleDataRef.current, key, compact);
|
const nextModuleData = applyModuleDataValue(moduleDataRef.current, key, compact);
|
||||||
commitModuleData(nextModuleData);
|
commitModuleData(nextModuleData);
|
||||||
const operation = compact == null ? dbRemoveModuleData(key) : dbSetModuleData(key, compact);
|
const previousQueue = moduleWriteQueuesRef.current[key] || Promise.resolve();
|
||||||
operation.then(refreshQuota).catch((error) => {
|
const operation = previousQueue.catch(() => {}).then(async () => {
|
||||||
commitModuleData(rollbackModuleDataCheckpoints(moduleDataRef.current, writeVersionsRef.current.moduleDataByKey, [checkpoint]));
|
if (compact == null) await dbRemoveModuleData(key);
|
||||||
|
else await dbSetModuleData(key, compact);
|
||||||
|
confirmedModuleDataRef.current = applyModuleDataValue(confirmedModuleDataRef.current, key, compact);
|
||||||
|
await refreshQuota();
|
||||||
|
}).catch((error) => {
|
||||||
|
if (moduleDataRef.current[key] === compact) {
|
||||||
|
commitModuleData(applyModuleDataValue(moduleDataRef.current, key, confirmedModuleDataRef.current[key]));
|
||||||
|
}
|
||||||
reportStorageError(error);
|
reportStorageError(error);
|
||||||
});
|
});
|
||||||
|
const queuedOperation = operation.finally(() => {
|
||||||
|
if (moduleWriteQueuesRef.current[key] === queuedOperation) delete moduleWriteQueuesRef.current[key];
|
||||||
|
});
|
||||||
|
moduleWriteQueuesRef.current[key] = queuedOperation;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,6 +195,30 @@ export function useIndexedToolboxes(onError) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function persistToolboxSnapshot(nextToolboxes, nextLinks, nextModuleData) {
|
||||||
|
const normalizedToolboxes = compactToolboxesForStorage(nextToolboxes).map(normalizeToolbox).filter(Boolean);
|
||||||
|
const previous = {
|
||||||
|
toolboxes: toolboxesRef.current,
|
||||||
|
links: linksRef.current,
|
||||||
|
moduleData: moduleDataRef.current
|
||||||
|
};
|
||||||
|
commitToolboxes(normalizedToolboxes);
|
||||||
|
commitLinks(nextLinks);
|
||||||
|
commitModuleData(nextModuleData);
|
||||||
|
try {
|
||||||
|
await dbSetToolboxSnapshot(compactToolboxesForStorage(normalizedToolboxes), nextLinks, nextModuleData);
|
||||||
|
confirmedModuleDataRef.current = nextModuleData;
|
||||||
|
await refreshQuota();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
commitToolboxes(previous.toolboxes);
|
||||||
|
commitLinks(previous.links);
|
||||||
|
commitModuleData(previous.moduleData);
|
||||||
|
reportStorageError(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setToastPosition(position) {
|
function setToastPosition(position) {
|
||||||
const normalized = position === "left" ? "left" : "right";
|
const normalized = position === "left" ? "left" : "right";
|
||||||
const previousToastPosition = toastPositionRef.current;
|
const previousToastPosition = toastPositionRef.current;
|
||||||
|
|
@ -206,6 +246,7 @@ export function useIndexedToolboxes(onError) {
|
||||||
removeModuleData,
|
removeModuleData,
|
||||||
removeToolboxModuleData,
|
removeToolboxModuleData,
|
||||||
setToastPosition,
|
setToastPosition,
|
||||||
refreshQuota
|
refreshQuota,
|
||||||
|
persistToolboxSnapshot
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,14 @@ export function useToolboxActions({ store, notify, creationTemplate, setConfirmM
|
||||||
|
|
||||||
function deleteToolbox(id) {
|
function deleteToolbox(id) {
|
||||||
const toolbox = store.toolboxes.find((item) => item.id === id);
|
const toolbox = store.toolboxes.find((item) => item.id === id);
|
||||||
store.removeToolboxModuleData(toolbox);
|
const nextToolboxes = store.toolboxes.filter((item) => item.id !== id);
|
||||||
store.setToolboxes(store.toolboxes.filter((item) => item.id !== id));
|
const nextModuleData = { ...store.moduleData };
|
||||||
|
(toolbox?.modules || []).forEach((module) => delete nextModuleData[`${id}:${module.id}`]);
|
||||||
const nextLinks = { ...store.links };
|
const nextLinks = { ...store.links };
|
||||||
Object.entries(nextLinks).forEach(([gameId, toolboxId]) => {
|
Object.entries(nextLinks).forEach(([gameId, toolboxId]) => {
|
||||||
if (toolboxId === id) delete nextLinks[gameId];
|
if (toolboxId === id) delete nextLinks[gameId];
|
||||||
});
|
});
|
||||||
store.setLinks(nextLinks);
|
return store.persistToolboxSnapshot(nextToolboxes, nextLinks, nextModuleData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkToolboxToGame(gameId, toolboxId) {
|
function linkToolboxToGame(gameId, toolboxId) {
|
||||||
|
|
@ -146,13 +147,19 @@ export function useToolboxActions({ store, notify, creationTemplate, setConfirmM
|
||||||
});
|
});
|
||||||
imported.moduleOrder = remapModuleOrderIds(imported.moduleOrder, moduleIdMap);
|
imported.moduleOrder = remapModuleOrderIds(imported.moduleOrder, moduleIdMap);
|
||||||
const normalizedImport = normalizeToolbox(imported);
|
const normalizedImport = normalizeToolbox(imported);
|
||||||
if (!store.setToolboxes([normalizedImport, ...store.toolboxes])) return null;
|
const nextModuleData = { ...store.moduleData };
|
||||||
Object.entries(payload.modules || {}).forEach(([oldId, data]) => {
|
Object.entries(payload.modules || {}).forEach(([oldId, data]) => {
|
||||||
const nextId = moduleIdMap.get(oldId);
|
const nextId = moduleIdMap.get(oldId);
|
||||||
const module = normalizedImport.modules.find((item) => item.id === nextId);
|
const module = normalizedImport.modules.find((item) => item.id === nextId);
|
||||||
if (nextId) store.updateModuleData(normalizedImport.id, nextId, data, module?.type);
|
if (nextId) {
|
||||||
|
const compact = compactModuleDataForStorage(module?.type, data);
|
||||||
|
if (compact) nextModuleData[`${normalizedImport.id}:${nextId}`] = compact;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (gameId) linkToolboxToGame(gameId, normalizedImport.id);
|
const nextLinks = { ...store.links };
|
||||||
|
if (gameId) nextLinks[gameId] = normalizedImport.id;
|
||||||
|
const saved = await store.persistToolboxSnapshot([normalizedImport, ...store.toolboxes], nextLinks, nextModuleData);
|
||||||
|
if (!saved) return null;
|
||||||
return normalizedImport;
|
return normalizedImport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,6 +188,7 @@ export function useToolboxActions({ store, notify, creationTemplate, setConfirmM
|
||||||
const nextToolboxId = toolboxIdMap.get(oldToolboxId);
|
const nextToolboxId = toolboxIdMap.get(oldToolboxId);
|
||||||
if (nextToolboxId) nextLinks[gameId] = nextToolboxId;
|
if (nextToolboxId) nextLinks[gameId] = nextToolboxId;
|
||||||
});
|
});
|
||||||
|
const nextModuleData = { ...store.moduleData };
|
||||||
Object.entries(payload.modules).forEach(([key, data]) => {
|
Object.entries(payload.modules).forEach(([key, data]) => {
|
||||||
const [oldToolboxId] = key.split(":");
|
const [oldToolboxId] = key.split(":");
|
||||||
const nextToolboxId = toolboxIdMap.get(oldToolboxId);
|
const nextToolboxId = toolboxIdMap.get(oldToolboxId);
|
||||||
|
|
@ -188,11 +196,10 @@ export function useToolboxActions({ store, notify, creationTemplate, setConfirmM
|
||||||
const toolbox = importedToolboxes.find((item) => item.id === nextToolboxId);
|
const toolbox = importedToolboxes.find((item) => item.id === nextToolboxId);
|
||||||
const module = toolbox?.modules.find((item) => item.id === nextModuleId);
|
const module = toolbox?.modules.find((item) => item.id === nextModuleId);
|
||||||
const compact = compactModuleDataForStorage(module?.type, data);
|
const compact = compactModuleDataForStorage(module?.type, data);
|
||||||
if (nextToolboxId && nextModuleId && compact) store.updateModuleData(nextToolboxId, nextModuleId, compact, module?.type);
|
if (nextToolboxId && nextModuleId && compact) nextModuleData[`${nextToolboxId}:${nextModuleId}`] = compact;
|
||||||
});
|
});
|
||||||
store.setToolboxes([...importedToolboxes, ...store.toolboxes]);
|
const saved = await store.persistToolboxSnapshot([...importedToolboxes, ...store.toolboxes], nextLinks, nextModuleData);
|
||||||
store.setLinks(nextLinks);
|
if (!saved) throw new Error("Impossible d'enregistrer l'import global.");
|
||||||
store.refreshQuota();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export function HomeOnboardingSection({ content, className = "", onCreateToolbox
|
||||||
const currentSlide = slides[activeSlide] || slides[0];
|
const currentSlide = slides[activeSlide] || slides[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={["home-onboarding nebula-panel", className].filter(Boolean).join(" ")} aria-labelledby="home-onboarding-title">
|
<section className={["home-onboarding nds-panel", className].filter(Boolean).join(" ")} aria-labelledby="home-onboarding-title">
|
||||||
<div className="home-section-heading home-onboarding-heading">
|
<div className="home-section-heading home-onboarding-heading">
|
||||||
<p className="eyebrow">{content.eyebrow}</p>
|
<p className="eyebrow">{content.eyebrow}</p>
|
||||||
<h2 id="home-onboarding-title">{content.title}</h2>
|
<h2 id="home-onboarding-title">{content.title}</h2>
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,22 @@ export async function removeModuleDataKeys(keys) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setToolboxSnapshot(toolboxes, links, moduleData) {
|
||||||
|
const db = await openDatabase();
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const transaction = db.transaction([KV_STORE, MODULE_STORE], "readwrite");
|
||||||
|
const kv = transaction.objectStore(KV_STORE);
|
||||||
|
const modules = transaction.objectStore(MODULE_STORE);
|
||||||
|
kv.put(toolboxes, "toolboxes");
|
||||||
|
kv.put(links, "links");
|
||||||
|
modules.clear();
|
||||||
|
Object.entries(moduleData || {}).forEach(([key, data]) => modules.put({ key, data }));
|
||||||
|
transaction.oncomplete = () => resolve();
|
||||||
|
transaction.onerror = () => reject(transaction.error);
|
||||||
|
transaction.onabort = () => reject(transaction.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function getStorageEstimate() {
|
export async function getStorageEstimate() {
|
||||||
if (!navigator.storage?.estimate) return { usage: 0, quota: 0 };
|
if (!navigator.storage?.estimate) return { usage: 0, quota: 0 };
|
||||||
return navigator.storage.estimate();
|
return navigator.storage.estimate();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue