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
108
website/src/features/toolboxes/modules/timerUtils.js
Normal file
108
website/src/features/toolboxes/modules/timerUtils.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Rôle : regroupe les calculs purs de formatage et d'échéance des timers.
|
||||
export function getNowMs() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
export function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
export function formatDuration(ms) {
|
||||
const safeMs = Math.max(0, Math.floor(ms));
|
||||
const totalSeconds = Math.floor(safeMs / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
||||
}
|
||||
|
||||
export function getCentiseconds(ms) {
|
||||
return pad(Math.floor((Math.max(0, Math.floor(ms)) % 1000) / 10));
|
||||
}
|
||||
|
||||
export function formatDurationWithCentiseconds(ms) {
|
||||
return `${formatDuration(ms)}:${getCentiseconds(ms)}`;
|
||||
}
|
||||
|
||||
export function formatTargetTime(ms) {
|
||||
if (!Number.isFinite(ms)) return "";
|
||||
return new Intl.DateTimeFormat("fr-FR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit"
|
||||
}).format(new Date(ms));
|
||||
}
|
||||
|
||||
export function normalizeTimePart(value, max, allowWildcard = false) {
|
||||
const cleanValue = String(value || "").trim().toUpperCase();
|
||||
if (allowWildcard && !cleanValue) return "X";
|
||||
if (allowWildcard && /^X+$/.test(cleanValue)) return "X";
|
||||
const parsed = cleanValue ? Number.parseInt(cleanValue, 10) : 0;
|
||||
if (!Number.isFinite(parsed) || parsed < 0 || parsed > max) return "";
|
||||
return pad(parsed);
|
||||
}
|
||||
|
||||
export function normalizeTimeParts(parts, { allowWildcard = false, allowLargeHours = false } = {}) {
|
||||
const hours = normalizeTimePart(parts?.hours, allowLargeHours ? 999 : 23, allowWildcard);
|
||||
const minutes = normalizeTimePart(parts?.minutes, 59, allowWildcard);
|
||||
const seconds = normalizeTimePart(parts?.seconds, 59, allowWildcard);
|
||||
if (!hours || !minutes || !seconds) return null;
|
||||
return { hours, minutes, seconds };
|
||||
}
|
||||
|
||||
export function timePartsToDurationMs(parts) {
|
||||
const normalized = normalizeTimeParts(parts, { allowLargeHours: true });
|
||||
if (!normalized) return 0;
|
||||
return ((Number(normalized.hours) * 3600) + (Number(normalized.minutes) * 60) + Number(normalized.seconds)) * 1000;
|
||||
}
|
||||
|
||||
export function timePartsToString(parts, options) {
|
||||
const normalized = normalizeTimeParts(parts, options);
|
||||
return normalized ? `${normalized.hours}:${normalized.minutes}:${normalized.seconds}` : "";
|
||||
}
|
||||
|
||||
export function parseTimeString(value, options) {
|
||||
const [hours = "", minutes = "", seconds = ""] = String(value || "").split(":");
|
||||
return normalizeTimeParts({ hours, minutes, seconds }, options);
|
||||
}
|
||||
|
||||
export function getDailyTargetMs(time, nowMs) {
|
||||
const parsed = parseTimeString(time);
|
||||
if (!parsed) return 0;
|
||||
const target = new Date(nowMs);
|
||||
target.setHours(Number(parsed.hours), Number(parsed.minutes), Number(parsed.seconds), 0);
|
||||
if (target.getTime() <= nowMs) target.setDate(target.getDate() + 1);
|
||||
return target.getTime();
|
||||
}
|
||||
|
||||
export function getTimePatternTargetMs(pattern, nowMs) {
|
||||
const parsed = parseTimeString(pattern, { allowWildcard: true });
|
||||
if (!parsed) return 0;
|
||||
const now = new Date(nowMs);
|
||||
const candidate = new Date(nowMs);
|
||||
candidate.setMilliseconds(0);
|
||||
|
||||
for (let offset = 1; offset <= 86400; offset += 1) {
|
||||
candidate.setTime(now.getTime() + offset * 1000);
|
||||
const hours = parsed.hours === "X" || Number(parsed.hours) === candidate.getHours();
|
||||
const minutes = parsed.minutes === "X" || Number(parsed.minutes) === candidate.getMinutes();
|
||||
const seconds = parsed.seconds === "X" || Number(parsed.seconds) === candidate.getSeconds();
|
||||
if (hours && minutes && seconds) return candidate.getTime();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getIntervalTargetMs(intervalMs, anchorAt, nowMs) {
|
||||
const safeIntervalMs = Math.max(1000, Number(intervalMs) || 0);
|
||||
const safeAnchor = Number.isFinite(anchorAt) && anchorAt > 0 ? anchorAt : nowMs;
|
||||
if (safeAnchor > nowMs) return safeAnchor;
|
||||
return safeAnchor + Math.ceil((nowMs - safeAnchor + 1) / safeIntervalMs) * safeIntervalMs;
|
||||
}
|
||||
|
||||
export function getCountdownTargetMs(countdown, nowMs) {
|
||||
if (countdown.type === "duration") return Number(countdown.targetAt) || 0;
|
||||
if (countdown.type === "daily_time") return Number(countdown.targetAt) || 0;
|
||||
if (countdown.type === "time_pattern") return getTimePatternTargetMs(countdown.pattern, nowMs);
|
||||
if (countdown.type === "interval") return getIntervalTargetMs(countdown.intervalMs, countdown.anchorAt, nowMs);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue