Improve timer interval refresh behavior
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
fdc81373c6
commit
a6b2388763
9 changed files with 305 additions and 39 deletions
|
|
@ -12,6 +12,7 @@ import {
|
|||
getCentiseconds,
|
||||
getCountdownTargetMs,
|
||||
getDailyTargetMs,
|
||||
getIntervalAnchorMs,
|
||||
getNowMs,
|
||||
getTimePatternTargetMs,
|
||||
getTimePatternRecurrenceMs,
|
||||
|
|
@ -32,7 +33,8 @@ const DEFAULT_COUNTDOWN_FORM = {
|
|||
duration: EMPTY_TIME_PARTS,
|
||||
time: EMPTY_TIME_PARTS,
|
||||
pattern: EMPTY_TIME_PARTS,
|
||||
interval: EMPTY_TIME_PARTS
|
||||
interval: EMPTY_TIME_PARTS,
|
||||
intervalStartTime: EMPTY_TIME_PARTS
|
||||
};
|
||||
const ALERT_MODES = [
|
||||
{ mode: "off", icon: "sound-mute" },
|
||||
|
|
@ -41,6 +43,7 @@ const ALERT_MODES = [
|
|||
];
|
||||
const ALERT_MIN_DELAY_MS = 5 * 60 * 1000;
|
||||
const AUTO_REFRESH_COUNTDOWN_TYPES = new Set(["time_pattern", "interval"]);
|
||||
const TOGGLE_REFRESH_COUNTDOWN_TYPES = new Set(["daily_time", "interval"]);
|
||||
|
||||
function isAlertGuarded(countdown) {
|
||||
if (countdown?.type === "interval") return Number(countdown.intervalMs) < ALERT_MIN_DELAY_MS;
|
||||
|
|
@ -52,6 +55,10 @@ function getAlertGuardMessage(textContent) {
|
|||
return textContent.alertTooSoonTitle || "Alerte indisponible pour une répétition inférieure à 5 minutes";
|
||||
}
|
||||
|
||||
function canToggleAutoRefresh(countdown) {
|
||||
return TOGGLE_REFRESH_COUNTDOWN_TYPES.has(countdown?.type) && !isAlertGuarded(countdown);
|
||||
}
|
||||
|
||||
function TimerValue({ ms, compact = false, showCentiseconds = true }) {
|
||||
if (!showCentiseconds) return <span className="timer-value">{formatDuration(ms)}</span>;
|
||||
if (!compact) return <span className="timer-value">{formatDurationWithCentiseconds(ms)}</span>;
|
||||
|
|
@ -64,12 +71,17 @@ function TimerValue({ ms, compact = false, showCentiseconds = true }) {
|
|||
);
|
||||
}
|
||||
|
||||
function getCountdownMeta(countdown, textContent) {
|
||||
if (countdown.type === "duration") return `${textContent.durationType || "Durée"} · ${formatDuration(countdown.durationMs || 0)}`;
|
||||
if (countdown.type === "daily_time") return `${textContent.dailyTimeType || "Heure précise"} · ${countdown.time}`;
|
||||
if (countdown.type === "time_pattern") return `${textContent.timePatternType || "Heure / minute / seconde"} · ${countdown.pattern}`;
|
||||
if (countdown.type === "interval") return `${textContent.intervalType || "Intervalle"} · ${formatDuration(countdown.intervalMs || 0)}`;
|
||||
return "";
|
||||
function getCountdownMetaParts(countdown, textContent) {
|
||||
if (countdown.type === "duration") return [`${textContent.durationType || "Durée"} ${formatDuration(countdown.durationMs || 0)}`];
|
||||
if (countdown.type === "daily_time") return [`${textContent.dailyTimeType || "Heure précise"} ${countdown.time}`];
|
||||
if (countdown.type === "time_pattern") return [`${textContent.timePatternType || "Heure / minute / seconde"} ${countdown.pattern}`];
|
||||
if (countdown.type === "interval") {
|
||||
const startLabel = countdown.startMode === "time" && countdown.startTime
|
||||
? `${textContent.intervalStartTimeLabel || "Départ"} ${countdown.startTime}`
|
||||
: textContent.intervalStartNowOption || "Maintenant";
|
||||
return [`${textContent.intervalType || "Intervalle"} ${formatDuration(countdown.intervalMs || 0)}`, startLabel];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function handleEditableKeyDown(event, callback) {
|
||||
|
|
@ -78,6 +90,10 @@ function handleEditableKeyDown(event, callback) {
|
|||
callback();
|
||||
}
|
||||
|
||||
function hasTimePartValue(parts) {
|
||||
return Boolean(String(parts?.hours || "").trim() || String(parts?.minutes || "").trim() || String(parts?.seconds || "").trim());
|
||||
}
|
||||
|
||||
export function TimerModule({ toolboxId, moduleId, context }) {
|
||||
const data = context.normalizeTimerData(context.getModuleData(toolboxId, moduleId, { activeTab: "stopwatch" }));
|
||||
const textContent = context.moduleText?.timer || {};
|
||||
|
|
@ -197,7 +213,23 @@ export function TimerModule({ toolboxId, moduleId, context }) {
|
|||
}
|
||||
if (type === "interval") {
|
||||
const intervalMs = timePartsToDurationMs(draft.interval);
|
||||
if (intervalMs > 0) countdown = { id: context.uid("timer"), label, type, intervalMs, anchorAt: now };
|
||||
const startTime = hasTimePartValue(draft.intervalStartTime) ? timePartsToString(draft.intervalStartTime) : "";
|
||||
const startMode = startTime ? "time" : "now";
|
||||
const anchorAt = getIntervalAnchorMs(startMode, startTime, now);
|
||||
const targetAt = getCountdownTargetMs({ type, intervalMs, anchorAt, autoRefresh: true }, now);
|
||||
if (intervalMs > 0) {
|
||||
countdown = {
|
||||
id: context.uid("timer"),
|
||||
label,
|
||||
type,
|
||||
intervalMs,
|
||||
anchorAt,
|
||||
targetAt,
|
||||
startMode,
|
||||
...(startTime ? { startTime } : {}),
|
||||
autoRefresh: intervalMs >= ALERT_MIN_DELAY_MS
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!countdown) return;
|
||||
|
|
@ -216,11 +248,39 @@ export function TimerModule({ toolboxId, moduleId, context }) {
|
|||
let nextCountdown = null;
|
||||
if (countdown.type === "duration") nextCountdown = { ...countdown, targetAt: now + countdown.durationMs };
|
||||
if (countdown.type === "daily_time") nextCountdown = { ...countdown, targetAt: getDailyTargetMs(countdown.time, now) };
|
||||
if (countdown.type === "interval") nextCountdown = { ...countdown, anchorAt: now };
|
||||
if (countdown.type === "interval") {
|
||||
const anchorAt = getIntervalAnchorMs(countdown.startMode, countdown.startTime, now);
|
||||
const targetAt = getCountdownTargetMs({ ...countdown, anchorAt, autoRefresh: true }, now);
|
||||
nextCountdown = { ...countdown, anchorAt, targetAt };
|
||||
}
|
||||
if (!nextCountdown) return;
|
||||
save({ ...data, countdowns: data.countdowns.map((item) => item.id === countdown.id ? nextCountdown : item) });
|
||||
}
|
||||
|
||||
function toggleCountdownAutoRefresh(countdownId) {
|
||||
const countdown = data.countdowns.find((item) => item.id === countdownId);
|
||||
if (!TOGGLE_REFRESH_COUNTDOWN_TYPES.has(countdown?.type)) return;
|
||||
if (!countdown.autoRefresh && !canToggleAutoRefresh(countdown)) {
|
||||
context.notify?.(getAlertGuardMessage(textContent));
|
||||
return;
|
||||
}
|
||||
const now = getNowMs();
|
||||
save({
|
||||
...data,
|
||||
countdowns: data.countdowns.map((item) => {
|
||||
if (item.id !== countdownId) return item;
|
||||
const autoRefresh = !item.autoRefresh;
|
||||
if (item.type === "daily_time") return { ...item, autoRefresh, targetAt: getDailyTargetMs(item.time, now) };
|
||||
if (item.type === "interval") {
|
||||
const anchorAt = getIntervalAnchorMs(item.startMode, item.startTime, now);
|
||||
const targetAt = getCountdownTargetMs({ ...item, anchorAt, autoRefresh: true }, now);
|
||||
return { ...item, autoRefresh, anchorAt, targetAt };
|
||||
}
|
||||
return item;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function renameCountdown(countdownId, label) {
|
||||
save({
|
||||
...data,
|
||||
|
|
@ -314,7 +374,7 @@ export function TimerModule({ toolboxId, moduleId, context }) {
|
|||
{activeTab === "stopwatch" ? (
|
||||
<StopwatchLapList laps={data.stopwatch.laps} textContent={textContent} onRename={renameLap} onDelete={deleteLap} />
|
||||
) : (
|
||||
<CountdownList countdowns={visibleCountdowns} nowMs={nowMs} textContent={textContent} reorder={countdownReorder} reorderEnabled={!data.sortResults} parentId={moduleId} onRename={renameCountdown} onAlertModeChange={setCountdownAlertMode} onReset={resetCountdown} onDelete={deleteCountdown} />
|
||||
<CountdownList countdowns={visibleCountdowns} nowMs={nowMs} textContent={textContent} reorder={countdownReorder} reorderEnabled={!data.sortResults} parentId={moduleId} onRename={renameCountdown} onAlertModeChange={setCountdownAlertMode} onReset={resetCountdown} onAutoRefreshToggle={toggleCountdownAutoRefresh} onDelete={deleteCountdown} />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -399,27 +459,36 @@ function CountdownControls({ textContent, form, onChange, onSubmit }) {
|
|||
</label>
|
||||
{form.type === "duration" && (
|
||||
<label>
|
||||
<span className="timer-field-spacer" aria-hidden="true" />
|
||||
<span className="sr-only">{textContent.durationLabel || "Durée"}</span>
|
||||
<TimePartsInput value={form.duration} onChange={(duration) => onChange({ duration })} />
|
||||
</label>
|
||||
)}
|
||||
{form.type === "daily_time" && (
|
||||
<label>
|
||||
<span className="timer-field-spacer" aria-hidden="true" />
|
||||
<span className="sr-only">{textContent.dailyTimeLabel || "Heure"}</span>
|
||||
<TimePartsInput value={form.time} onChange={(time) => onChange({ time })} />
|
||||
</label>
|
||||
)}
|
||||
{form.type === "time_pattern" && (
|
||||
<label>
|
||||
<span className="timer-field-spacer" aria-hidden="true" />
|
||||
<span className="sr-only">{textContent.timePatternLabel || "Heure / minute / seconde"}</span>
|
||||
<TimePartsInput value={form.pattern} onChange={(pattern) => onChange({ pattern })} allowWildcard />
|
||||
</label>
|
||||
)}
|
||||
{form.type === "interval" && (
|
||||
<label>
|
||||
<span className="sr-only">{textContent.intervalLabel || "Toutes les"}</span>
|
||||
<TimePartsInput value={form.interval} onChange={(interval) => onChange({ interval })} />
|
||||
</label>
|
||||
<div className="timer-interval-fields">
|
||||
<label>
|
||||
<span>{textContent.intervalLabel || "Toutes les"}</span>
|
||||
<TimePartsInput value={form.interval} onChange={(interval) => onChange({ interval })} />
|
||||
</label>
|
||||
<label>
|
||||
<span>{textContent.intervalStartTimeLabel || "Départ"}</span>
|
||||
<TimePartsInput value={form.intervalStartTime} onChange={(intervalStartTime) => onChange({ intervalStartTime })} />
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<button className="primary" type="submit">{textContent.addCountdownButton || "Ajouter"}</button>
|
||||
</form>
|
||||
|
|
@ -485,7 +554,7 @@ function StopwatchLapList({ laps, textContent, onRename, onDelete }) {
|
|||
);
|
||||
}
|
||||
|
||||
function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled, parentId, onRename, onAlertModeChange, onReset, onDelete }) {
|
||||
function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled, parentId, onRename, onAlertModeChange, onReset, onAutoRefreshToggle, onDelete }) {
|
||||
const [editingId, setEditingId] = useState("");
|
||||
if (!countdowns.length) return <p className="muted">{textContent.emptyCountdowns || "Aucun compte à rebours configuré."}</p>;
|
||||
return (
|
||||
|
|
@ -493,9 +562,12 @@ function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled
|
|||
{countdowns.map((countdown) => {
|
||||
const remainingMs = countdown.targetMs - nowMs;
|
||||
const expired = remainingMs <= 0;
|
||||
const canReset = countdown.type === "duration"
|
||||
|| countdown.type === "interval"
|
||||
|| (countdown.type === "daily_time" && expired);
|
||||
const canReset = countdown.type === "duration";
|
||||
const canToggleRefresh = TOGGLE_REFRESH_COUNTDOWN_TYPES.has(countdown.type);
|
||||
const refreshBlocked = canToggleRefresh && !canToggleAutoRefresh(countdown);
|
||||
const refreshTitle = refreshBlocked
|
||||
? getAlertGuardMessage(textContent)
|
||||
: textContent.autoRefreshTitle || "Répéter automatiquement";
|
||||
const className = [
|
||||
"timer-list-item",
|
||||
expired ? "is-expired" : "",
|
||||
|
|
@ -508,7 +580,7 @@ function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled
|
|||
|
||||
return (
|
||||
<li key={countdown.id} className={className} {...reorderProps}>
|
||||
<div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${canReset && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
|
||||
<div className={`tool-split-entry timer-entry has-single-action ${reorderEnabled ? "has-drag-handle" : ""} ${editingId !== countdown.id ? "has-inline-controls" : ""} ${(canReset || canToggleRefresh) && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
|
||||
{reorderEnabled && (
|
||||
<button
|
||||
className="timer-drag-handle"
|
||||
|
|
@ -556,6 +628,23 @@ function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled
|
|||
<Icon name="refresh" />
|
||||
</button>
|
||||
)}
|
||||
{canToggleRefresh && (
|
||||
<button
|
||||
className={`timer-inline-refresh-toggle ${countdown.autoRefresh ? "active" : ""} ${refreshBlocked ? "is-blocked" : ""}`}
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onAutoRefreshToggle(countdown.id);
|
||||
}}
|
||||
disabled={refreshBlocked}
|
||||
aria-pressed={Boolean(countdown.autoRefresh)}
|
||||
aria-label={refreshTitle}
|
||||
title={refreshTitle}
|
||||
>
|
||||
<Icon name="refresh" />
|
||||
<i aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
<TimerAlertModeSwitch
|
||||
mode={countdown.alertMode}
|
||||
blocked={AUTO_REFRESH_COUNTDOWN_TYPES.has(countdown.type) && isAlertGuarded(countdown)}
|
||||
|
|
@ -571,8 +660,14 @@ function CountdownList({ countdowns, nowMs, textContent, reorder, reorderEnabled
|
|||
</button>
|
||||
</div>
|
||||
<p className="timer-entry-meta">
|
||||
<span>{getCountdownMeta(countdown, textContent)}</span>
|
||||
<em>{expired ? textContent.finishedLabel || "Terminé" : `${textContent.targetLabel || "Prochaine occurrence"} · ${formatTargetTime(countdown.targetMs)}`}</em>
|
||||
<span>
|
||||
{getCountdownMetaParts(countdown, textContent).map((part, index) => (
|
||||
<b key={`${part}-${index}`}>{part}</b>
|
||||
))}
|
||||
</span>
|
||||
<em>
|
||||
<b>{expired ? textContent.finishedLabel || "Terminé" : `${textContent.targetLabel || "Prochaine occurrence"} ${formatTargetTime(countdown.targetMs)}`}</b>
|
||||
</em>
|
||||
</p>
|
||||
</li>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -113,10 +113,22 @@ export function getIntervalTargetMs(intervalMs, anchorAt, nowMs) {
|
|||
return safeAnchor + Math.ceil((nowMs - safeAnchor + 1) / safeIntervalMs) * safeIntervalMs;
|
||||
}
|
||||
|
||||
export function getIntervalAnchorMs(startMode, startTime, nowMs) {
|
||||
if (startMode !== "time") return nowMs;
|
||||
const parsed = parseTimeString(startTime);
|
||||
if (!parsed) return nowMs;
|
||||
const anchor = new Date(nowMs);
|
||||
anchor.setHours(Number(parsed.hours), Number(parsed.minutes), Number(parsed.seconds), 0);
|
||||
return anchor.getTime();
|
||||
}
|
||||
|
||||
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 === "daily_time") return countdown.autoRefresh ? getDailyTargetMs(countdown.time, nowMs) : 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);
|
||||
if (countdown.type === "interval") {
|
||||
if (countdown.autoRefresh) return getIntervalTargetMs(countdown.intervalMs, countdown.anchorAt, nowMs);
|
||||
return Number(countdown.targetAt) || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ const NOTEPAD_RGB_COLOR_MAP = {
|
|||
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 TIMER_INTERVAL_START_MODES = new Set(["now", "time"]);
|
||||
const TIMER_MIN_AUTO_REFRESH_MS = 5 * 60 * 1000;
|
||||
const TASK_TYPES = new Set(["unique", "daily", "weekly"]);
|
||||
const COMBO_DEVICES = new Set(["playstation", "xbox", "switch", "n64", "keyboardMouse"]);
|
||||
const COMBO_INPUT_KINDS = new Set(["button", "direction", "key", "mouse"]);
|
||||
|
|
@ -553,7 +555,7 @@ export function normalizeTimerData(data) {
|
|||
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;
|
||||
return isTimeString(time) && targetAt > 0 ? { ...normalized, time, targetAt, autoRefresh: countdown?.autoRefresh === true } : null;
|
||||
}
|
||||
if (type === "time_pattern") {
|
||||
const pattern = String(countdown?.pattern || "").trim().toUpperCase();
|
||||
|
|
@ -562,7 +564,21 @@ export function normalizeTimerData(data) {
|
|||
if (type === "interval") {
|
||||
const intervalMs = Math.max(0, Number(countdown?.intervalMs) || 0);
|
||||
const anchorAt = Math.max(0, Number(countdown?.anchorAt) || 0);
|
||||
return intervalMs > 0 ? { ...normalized, intervalMs, anchorAt } : null;
|
||||
const targetAt = Math.max(0, Number(countdown?.targetAt) || 0);
|
||||
const startMode = TIMER_INTERVAL_START_MODES.has(countdown?.startMode) ? countdown.startMode : "now";
|
||||
const startTime = String(countdown?.startTime || "").trim();
|
||||
const autoRefresh = countdown?.autoRefresh === true || (countdown?.autoRefresh !== false && !targetAt && intervalMs >= TIMER_MIN_AUTO_REFRESH_MS);
|
||||
return intervalMs > 0
|
||||
? {
|
||||
...normalized,
|
||||
intervalMs,
|
||||
anchorAt,
|
||||
targetAt: targetAt || anchorAt + intervalMs,
|
||||
startMode: startMode === "time" && isTimeString(startTime) ? "time" : "now",
|
||||
...(startMode === "time" && isTimeString(startTime) ? { startTime } : {}),
|
||||
autoRefresh: autoRefresh && intervalMs >= TIMER_MIN_AUTO_REFRESH_MS
|
||||
}
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
|
|
@ -822,11 +838,14 @@ export function compactModuleDataForStorage(type, value) {
|
|||
}
|
||||
if (normalized.countdowns.length) {
|
||||
compact.countdowns = normalized.countdowns.map((countdown) => {
|
||||
if (countdown.alertMode === "off") {
|
||||
const { alertMode, ...compactCountdown } = countdown;
|
||||
return compactCountdown;
|
||||
const { alertMode, autoRefresh, startMode, startTime, ...compactCountdown } = countdown;
|
||||
if (alertMode !== "off") compactCountdown.alertMode = alertMode;
|
||||
if (autoRefresh) compactCountdown.autoRefresh = true;
|
||||
if (startMode === "time" && startTime) {
|
||||
compactCountdown.startMode = startMode;
|
||||
compactCountdown.startTime = startTime;
|
||||
}
|
||||
return countdown;
|
||||
return compactCountdown;
|
||||
});
|
||||
}
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
|
|
|
|||
|
|
@ -3453,6 +3453,11 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
gap: 7px;
|
||||
}
|
||||
|
||||
.timer-field-spacer {
|
||||
display: block;
|
||||
min-height: 17px;
|
||||
}
|
||||
|
||||
.timer-countdown-form input,
|
||||
.timer-countdown-form select,
|
||||
.timer-control-panel input {
|
||||
|
|
@ -3578,6 +3583,32 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.timer-interval-fields {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.timer-interval-fields label {
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.timer-interval-fields .time-parts-input {
|
||||
grid-template-columns: minmax(22px, 1fr) 4px minmax(22px, 1fr) 4px minmax(22px, 1fr);
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.timer-interval-fields .time-parts-input input {
|
||||
min-height: 36px;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.timer-interval-fields .time-parts-input span {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.timer-control-panel > span,
|
||||
.timer-control-panel label span {
|
||||
color: var(--color-text-secondary);
|
||||
|
|
@ -3658,12 +3689,13 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
.timer-entry-meta {
|
||||
display: flex;
|
||||
grid-column: 1 / -2;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
gap: 4px 8px;
|
||||
margin: 0;
|
||||
padding: 1px 10px 0;
|
||||
opacity: 0.68;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timer-entry-summary .timer-value,
|
||||
|
|
@ -3735,7 +3767,49 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.timer-inline-reset .ui-icon {
|
||||
.timer-inline-refresh-toggle {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 13px 18px;
|
||||
width: 42px;
|
||||
min-width: 42px;
|
||||
min-height: 26px;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 0 4px;
|
||||
border-color: rgba(165, 180, 252, 0.08);
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(5, 7, 17, 0.26);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.timer-inline-refresh-toggle i {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
border: 1px solid rgba(165, 180, 252, 0.18);
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(7, 10, 24, 0.72);
|
||||
}
|
||||
|
||||
.timer-inline-refresh-toggle i::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-text-muted);
|
||||
transform: translateY(-50%);
|
||||
transition:
|
||||
left var(--duration-fast) var(--ease-standard),
|
||||
background-color var(--duration-fast) var(--ease-standard),
|
||||
box-shadow var(--duration-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.timer-inline-reset .ui-icon,
|
||||
.timer-inline-refresh-toggle .ui-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
|
@ -3766,16 +3840,47 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
}
|
||||
|
||||
.timer-entry-meta span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timer-list-item em {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
flex: 0 0 auto;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timer-entry-meta b {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
font: inherit;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timer-entry-meta b + b::before,
|
||||
.timer-entry-meta em::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
margin-right: 7px;
|
||||
background: rgba(165, 180, 252, 0.22);
|
||||
vertical-align: -1px;
|
||||
}
|
||||
|
||||
.timer-results-card {
|
||||
|
|
@ -4185,14 +4290,16 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
}
|
||||
|
||||
.timer-alert-mode-button:hover,
|
||||
.timer-alert-mode-button.active {
|
||||
.timer-alert-mode-button.active,
|
||||
.timer-inline-refresh-toggle:hover:not(:disabled) {
|
||||
border-color: rgba(196, 181, 253, 0.2);
|
||||
background: rgba(21, 26, 48, 0.42);
|
||||
color: var(--color-text-primary);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.timer-alert-mode-button.active {
|
||||
.timer-alert-mode-button.active,
|
||||
.timer-inline-refresh-toggle.active {
|
||||
border-color: rgba(246, 196, 83, 0.26);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(246, 196, 83, 0.08), rgba(139, 92, 246, 0.08)),
|
||||
|
|
@ -4200,9 +4307,23 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
color: var(--color-accent-gold);
|
||||
}
|
||||
|
||||
.timer-inline-refresh-toggle.active i {
|
||||
border-color: rgba(246, 196, 83, 0.36);
|
||||
background: rgba(246, 196, 83, 0.12);
|
||||
}
|
||||
|
||||
.timer-inline-refresh-toggle.active i::before {
|
||||
left: 7px;
|
||||
background: var(--color-accent-gold);
|
||||
box-shadow: 0 0 10px rgba(246, 196, 83, 0.28);
|
||||
}
|
||||
|
||||
.timer-alert-mode-button:disabled,
|
||||
.timer-alert-mode-button:disabled:hover,
|
||||
.timer-alert-mode-button.active:disabled {
|
||||
.timer-alert-mode-button.active:disabled,
|
||||
.timer-inline-refresh-toggle:disabled,
|
||||
.timer-inline-refresh-toggle:disabled:hover,
|
||||
.timer-inline-refresh-toggle.active:disabled {
|
||||
cursor: not-allowed;
|
||||
border-color: rgba(165, 180, 252, 0.025);
|
||||
background: rgba(5, 7, 17, 0.08);
|
||||
|
|
@ -4211,7 +4332,8 @@ button.combo-input-token.combo-value-grab .ui-icon {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.timer-alert-mode-button:disabled .ui-icon {
|
||||
.timer-alert-mode-button:disabled .ui-icon,
|
||||
.timer-inline-refresh-toggle:disabled .ui-icon {
|
||||
opacity: 0.34;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue