Coalesce quota refreshes and consolidate shared helpers
Some checks failed
Deploy Sokko G / deploy (push) Failing after 2s

This commit is contained in:
Shinuwa 2026-09-10 20:03:57 +02:00
parent a4cf633ab4
commit 748415082b
19 changed files with 216 additions and 64 deletions

View file

@ -3,12 +3,10 @@ import { useEffect, useRef, useState } from "react";
import { DrawingControls } from "../features/toolboxes/modules/DrawingControls.jsx";
import { DrawingOverlay } from "../features/toolboxes/modules/DrawingOverlay.jsx";
import { lockBodyScroll } from "../utils/bodyScrollLock.js";
import { DRAWING_COLORS, DRAWING_WIDTHS } from "../utils/drawingOptions.js";
import { Icon } from "./Icon.jsx";
import { Tooltip } from "./Tooltip.jsx";
const DRAWING_COLORS = ["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef"];
const DRAWING_WIDTHS = [2, 4, 8, 12];
function clampPercent(value) {
const parsed = Number(value);
if (!Number.isFinite(parsed)) return 0;

View file

@ -1,4 +1,5 @@
// Rôle : fournit l'outil Combos avec palettes d'inputs, rendu visuel et échange texte.
import { toggleListValue } from "../../../utils/collectionUtils.js";
import { getGroupedEntries } from "../../../hooks/useGroupedReorder.js";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
import { CombosEditor } from "./CombosEditor.jsx";
@ -40,9 +41,7 @@ export function CombosModule({ toolboxId, moduleId, context, editing, textExchan
}
function toggleCategoryCollapsed(category) {
const collapsedCategories = data.collapsedCategories.includes(category)
? data.collapsedCategories.filter((item) => item !== category)
: [...data.collapsedCategories, category];
const collapsedCategories = toggleListValue(data.collapsedCategories, category);
save({ ...data, collapsedCategories });
}

View file

@ -1,4 +1,5 @@
// Rôle : fournit l'outil liens avec ajout manuel et échange texte.
import { toggleListValue } from "../../../utils/collectionUtils.js";
import { useState } from "react";
import { CategoryAutocompleteInput } from "../../../components/CategoryAutocompleteInput.jsx";
import { Icon } from "../../../components/Icon.jsx";
@ -143,9 +144,7 @@ export function LinksModule({ toolboxId, moduleId, context, editing, textExchang
}
function toggleCategoryCollapsed(category) {
const collapsedCategories = data.collapsedCategories.includes(category)
? data.collapsedCategories.filter((item) => item !== category)
: [...data.collapsedCategories, category];
const collapsedCategories = toggleListValue(data.collapsedCategories, category);
save({ ...data, collapsedCategories });
}

View file

@ -1,4 +1,5 @@
// Rôle : fournit l'outil task planner avec tâches récurrentes, parents, pré requis et échange texte.
import { toggleListValue } from "../../../utils/collectionUtils.js";
import { useEffect, useMemo, useRef, useState } from "react";
import { useDraftForm } from "../../../hooks/useDraftForm.js";
import { TaskPlannerBranch } from "./TaskPlannerBranch.jsx";
@ -131,9 +132,7 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing, textE
}
function toggleCategoryCollapsed(category) {
const collapsedCategories = data.collapsedCategories.includes(category)
? data.collapsedCategories.filter((item) => item !== category)
: [...data.collapsedCategories, category];
const collapsedCategories = toggleListValue(data.collapsedCategories, category);
save({ ...data, collapsedCategories });
}

View file

@ -1,7 +1,6 @@
// Rôle : regroupe les constantes et helpers DOM du Bloc Note riche.
export const TEXT_COLORS = ["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef"];
export { DRAWING_COLORS as TEXT_COLORS, DRAWING_WIDTHS } from "../../../utils/drawingOptions.js";
export const HIGHLIGHT_COLORS = ["#f6c453", "#22d3ee", "#8b5cf6", "#d946ef", "#202745"];
export const DRAWING_WIDTHS = [2, 4, 8, 12];
export function getPlainText(element) {
return (element?.innerText || "").replace(/\n{3,}/g, "\n\n").trim();

View file

@ -0,0 +1,38 @@
// Rôle : regroupe les estimations de quota et ignore les résultats devenus obsolètes.
export function createQuotaRefresh({ estimate, onResult, delay = 250, schedule = setTimeout, cancel = clearTimeout }) {
let timer = null;
let running = false;
let dirty = false;
let disposed = false;
function request() {
if (disposed) return;
dirty = true;
if (!running && timer === null) timer = schedule(run, delay);
}
async function run() {
timer = null;
if (disposed) return;
dirty = false;
running = true;
try {
const result = await estimate();
if (!disposed && !dirty) onResult(result);
} catch {
// Une estimation indisponible ne remet pas à zéro le dernier quota connu.
} finally {
running = false;
if (!disposed && dirty) request();
}
}
return {
request,
dispose() {
disposed = true;
if (timer !== null) cancel(timer);
timer = null;
}
};
}

View file

@ -1,5 +1,5 @@
// Rôle : synchronise l'état optimiste des toolboxes avec une file d'écritures IndexedDB.
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import {
getAllModuleData as dbGetAllModuleData,
getLinks as dbGetLinks,
@ -22,6 +22,7 @@ import {
} from "./toolboxStorage.js";
import { applyModuleDataValue, removeModuleDataKeys } from "./optimisticModuleData.js";
import { createPersistenceQueue } from "./persistenceQueue.js";
import { createQuotaRefresh } from "./quotaRefresh.js";
function normalizeToolboxes(toolboxes) {
return compactToolboxesForStorage(toolboxes).map(normalizeToolbox).filter(Boolean);
@ -32,20 +33,29 @@ export function useIndexedToolboxes(onError) {
const [state, setState] = useState({ toolboxes: [], links: {}, moduleData: {}, toastPosition: "right" });
const [queue] = useState(() => createPersistenceQueue(state, setState));
const [storageUsage, setStorageUsage] = useState({ used: 0, limit: 0, ratio: 0 });
const quotaRefreshRef = useRef(null);
const reportStorageError = useCallback((error) => {
onError(error?.message || "Erreur de stockage IndexedDB.");
}, [onError]);
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(() => {
const refresh = createQuotaRefresh({
estimate: getStorageEstimate,
onResult: (estimate) => {
const used = estimate.usage || 0;
const limit = estimate.quota || 0;
setStorageUsage((current) => current.used === used && current.limit === limit
? current
: { used, limit, ratio: limit ? Math.min(1, used / limit) : 0 });
}
});
quotaRefreshRef.current = refresh;
return () => {
refresh.dispose();
quotaRefreshRef.current = null;
};
}, []);
const refreshQuota = useCallback(() => quotaRefreshRef.current?.request(), []);
useEffect(() => {
let cancelled = false;

View file

@ -0,0 +1,4 @@
// Rôle : fournit les modifications immuables communes aux listes de valeurs.
export function toggleListValue(values, value) {
return values.includes(value) ? values.filter((item) => item !== value) : [...values, value];
}

View file

@ -0,0 +1,3 @@
// Rôle : partage les couleurs et épaisseurs des outils de dessin.
export const DRAWING_COLORS = ["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef"];
export const DRAWING_WIDTHS = [2, 4, 8, 12];