new tool timer & alert revamp
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
This commit is contained in:
parent
0c7ae46b4d
commit
68005ef918
38 changed files with 2048 additions and 111 deletions
|
|
@ -1,6 +1,9 @@
|
|||
// Rôle : normalise, compacte et sérialise les données toolbox persistées.
|
||||
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", calc: "r", marker: "k" };
|
||||
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", calc: "r", marker: "k", timer: "z" };
|
||||
const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const TIMER_TABS = new Set(["stopwatch", "countdown"]);
|
||||
const COUNTDOWN_TYPES = new Set(["duration", "daily_time", "time_pattern", "interval"]);
|
||||
const TIMER_ALERT_MODES = new Set(["off", "visible", "site"]);
|
||||
const TOOLBOX_ICON_BASE = "/static/img/toolbox-icons/";
|
||||
const TOOLBOX_ICON_FILES = [
|
||||
"toolbox.png",
|
||||
|
|
@ -30,6 +33,7 @@ const DEFAULT_MODULE_TITLES = {
|
|||
links: "Liens",
|
||||
counters: "Compteurs",
|
||||
calculator: "Calculateur",
|
||||
timer: "Timer",
|
||||
imageAnnotation: "Annotation d'images"
|
||||
};
|
||||
|
||||
|
|
@ -210,6 +214,16 @@ function normalizeCalculatorValue(value) {
|
|||
return Number.isFinite(parsed) ? parsed : 0;
|
||||
}
|
||||
|
||||
function isTimeString(value, allowWildcard = false) {
|
||||
const pattern = allowWildcard ? /^(?:\d{2}|X):(?:\d{2}|X):(?:\d{2}|X)$/ : /^\d{2}:\d{2}:\d{2}$/;
|
||||
if (!pattern.test(String(value || ""))) return false;
|
||||
return String(value).split(":").every((part, index) => {
|
||||
if (part === "X") return allowWildcard;
|
||||
const number = Number(part);
|
||||
return Number.isInteger(number) && number >= 0 && number <= (index === 0 ? 23 : 59);
|
||||
});
|
||||
}
|
||||
|
||||
export function labelFromFileName(name) {
|
||||
if (!name || name === "image.png") return "";
|
||||
return name.replace(/\.[^.]+$/, "").trim();
|
||||
|
|
@ -230,6 +244,62 @@ export function normalizeCalculatorData(data) {
|
|||
};
|
||||
}
|
||||
|
||||
export function normalizeTimerData(data) {
|
||||
const stopwatch = data?.stopwatch || {};
|
||||
const legacyAlertMode = TIMER_ALERT_MODES.has(data?.alertMode) ? data.alertMode : "off";
|
||||
const laps = (Array.isArray(stopwatch.laps) ? stopwatch.laps : [])
|
||||
.map((lap) => ({
|
||||
id: lap?.id || uid("timer"),
|
||||
label: String(lap?.label || "").trim(),
|
||||
elapsedMs: Math.max(0, Number(lap?.elapsedMs) || 0)
|
||||
}))
|
||||
.filter((lap) => lap.elapsedMs > 0);
|
||||
const countdowns = (Array.isArray(data?.countdowns) ? data.countdowns : [])
|
||||
.map((countdown) => {
|
||||
const type = COUNTDOWN_TYPES.has(countdown?.type) ? countdown.type : "duration";
|
||||
const normalized = {
|
||||
id: countdown?.id || uid("timer"),
|
||||
label: String(countdown?.label || "Timer").trim() || "Timer",
|
||||
type,
|
||||
alertMode: TIMER_ALERT_MODES.has(countdown?.alertMode) ? countdown.alertMode : legacyAlertMode
|
||||
};
|
||||
if (type === "duration") {
|
||||
const durationMs = Math.max(0, Number(countdown?.durationMs) || 0);
|
||||
const targetAt = Math.max(0, Number(countdown?.targetAt) || 0);
|
||||
return durationMs > 0 && targetAt > 0 ? { ...normalized, durationMs, targetAt } : null;
|
||||
}
|
||||
if (type === "daily_time") {
|
||||
const time = String(countdown?.time || "").trim();
|
||||
const targetAt = Math.max(0, Number(countdown?.targetAt) || 0);
|
||||
return isTimeString(time) && targetAt > 0 ? { ...normalized, time, targetAt } : null;
|
||||
}
|
||||
if (type === "time_pattern") {
|
||||
const pattern = String(countdown?.pattern || "").trim().toUpperCase();
|
||||
return isTimeString(pattern, true) ? { ...normalized, pattern } : null;
|
||||
}
|
||||
if (type === "interval") {
|
||||
const legacyIntervalMs = (Number.parseInt(countdown?.intervalMinutes, 10) || 0) * 60000;
|
||||
const intervalMs = Math.max(0, Number(countdown?.intervalMs) || legacyIntervalMs);
|
||||
const anchorAt = Math.max(0, Number(countdown?.anchorAt) || 0);
|
||||
return intervalMs > 0 ? { ...normalized, intervalMs, anchorAt } : null;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return {
|
||||
activeTab: TIMER_TABS.has(data?.activeTab) ? data.activeTab : "stopwatch",
|
||||
scrollResults: data?.scrollResults === true,
|
||||
sortResults: data?.sortResults === true,
|
||||
stopwatch: {
|
||||
elapsedMs: Math.max(0, Number(stopwatch.elapsedMs) || 0),
|
||||
startedAt: Math.max(0, Number(stopwatch.startedAt) || 0),
|
||||
laps
|
||||
},
|
||||
countdowns
|
||||
};
|
||||
}
|
||||
|
||||
function clampPercent(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return 0;
|
||||
|
|
@ -339,6 +409,30 @@ export function compactModuleDataForStorage(type, value) {
|
|||
if (!entries.length && !normalized.scrollResults) return null;
|
||||
return normalized.scrollResults ? { entries, scrollResults: true } : { entries };
|
||||
}
|
||||
if (type === "timer") {
|
||||
const normalized = normalizeTimerData(value);
|
||||
const compact = {};
|
||||
if (normalized.activeTab !== "stopwatch") compact.activeTab = normalized.activeTab;
|
||||
if (normalized.scrollResults) compact.scrollResults = true;
|
||||
if (normalized.sortResults) compact.sortResults = true;
|
||||
if (normalized.stopwatch.elapsedMs || normalized.stopwatch.startedAt || normalized.stopwatch.laps.length) {
|
||||
compact.stopwatch = {
|
||||
...(normalized.stopwatch.elapsedMs ? { elapsedMs: normalized.stopwatch.elapsedMs } : {}),
|
||||
...(normalized.stopwatch.startedAt ? { startedAt: normalized.stopwatch.startedAt } : {}),
|
||||
...(normalized.stopwatch.laps.length ? { laps: normalized.stopwatch.laps } : {})
|
||||
};
|
||||
}
|
||||
if (normalized.countdowns.length) {
|
||||
compact.countdowns = normalized.countdowns.map((countdown) => {
|
||||
if (countdown.alertMode === "off") {
|
||||
const { alertMode, ...compactCountdown } = countdown;
|
||||
return compactCountdown;
|
||||
}
|
||||
return countdown;
|
||||
});
|
||||
}
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -403,6 +497,20 @@ function remapModuleDataForExport(type, data, nextId) {
|
|||
return remapped;
|
||||
}
|
||||
|
||||
if (type === "timer") {
|
||||
const remapped = { ...compact };
|
||||
if (compact.stopwatch?.laps) {
|
||||
remapped.stopwatch = {
|
||||
...compact.stopwatch,
|
||||
laps: compact.stopwatch.laps.map((lap) => ({ ...lap, id: nextId("timer") }))
|
||||
};
|
||||
}
|
||||
if (compact.countdowns) {
|
||||
remapped.countdowns = compact.countdowns.map((countdown) => ({ ...countdown, id: nextId("timer") }));
|
||||
}
|
||||
return remapped;
|
||||
}
|
||||
|
||||
return compact;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue