Split remaining toolbox modules by responsibility
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s
This commit is contained in:
parent
43a9e791c5
commit
a0d7e80a1a
25 changed files with 2199 additions and 1992 deletions
196
website/src/features/toolboxes/modules/TimerCountdown.jsx
Normal file
196
website/src/features/toolboxes/modules/TimerCountdown.jsx
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
// Rôle : rend le formulaire et la liste des comptes à rebours timer.
|
||||
import { useState } from "react";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { formatTargetTime } from "./timerUtils.js";
|
||||
import {
|
||||
AUTO_REFRESH_COUNTDOWN_TYPES,
|
||||
TOGGLE_REFRESH_COUNTDOWN_TYPES,
|
||||
canToggleAutoRefresh,
|
||||
getAlertGuardMessage,
|
||||
getCountdownMetaParts,
|
||||
handleEditableKeyDown,
|
||||
isAlertGuarded
|
||||
} from "./timerOptions.js";
|
||||
import { EditableTimerLabel, TimerAlertModeSwitch, TimerValue, TimePartsInput } from "./TimerSharedControls.jsx";
|
||||
|
||||
export function CountdownControls({ textContent, form, error, onChange, onSubmit }) {
|
||||
return (
|
||||
<form className="timer-control-panel timer-countdown-form" role="tabpanel" onSubmit={onSubmit} aria-describedby={error ? "timer-countdown-error" : undefined}>
|
||||
<label>
|
||||
<span>{textContent.countdownLabel || "Libellé"}</span>
|
||||
<input value={form.label} onChange={(event) => onChange({ label: event.target.value })} placeholder={textContent.countdownLabelPlaceholder || "Boss, event, craft..."} />
|
||||
</label>
|
||||
<label>
|
||||
<span>{textContent.countdownTypeLabel || "Type"}</span>
|
||||
<select value={form.type} onChange={(event) => onChange({ type: event.target.value })}>
|
||||
<option value="duration">{textContent.durationType || "Durée"}</option>
|
||||
<option value="daily_time">{textContent.dailyTimeType || "Heure précise"}</option>
|
||||
<option value="time_pattern">{textContent.timePatternType || "Pattern horaire"}</option>
|
||||
<option value="interval">{textContent.intervalType || "Intervalle"}</option>
|
||||
</select>
|
||||
</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" && (
|
||||
<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>
|
||||
)}
|
||||
{error && (
|
||||
<p id="timer-countdown-error" className="timer-form-error" role="alert">
|
||||
<Icon name="close" />
|
||||
<span>{error}</span>
|
||||
</p>
|
||||
)}
|
||||
<button className="primary" type="submit">{textContent.addCountdownButton || "Ajouter"}</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export 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 (
|
||||
<ul className="tool-split-entry-list timer-list">
|
||||
{countdowns.map((countdown) => {
|
||||
const remainingMs = countdown.targetMs - nowMs;
|
||||
const expired = remainingMs <= 0;
|
||||
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" : "",
|
||||
editingId === countdown.id ? "is-editing" : "",
|
||||
reorderEnabled && reorder.isItemDragging(countdown.id) ? "is-dragging" : "",
|
||||
reorderEnabled && reorder.isItemDropTarget(countdown.id) ? "is-drop-target" : "",
|
||||
reorderEnabled && reorder.getDropPlacement("item", countdown.id) === "after" ? "drop-after" : ""
|
||||
].filter(Boolean).join(" ");
|
||||
const reorderProps = reorderEnabled ? reorder.getItemProps({ itemId: countdown.id, parentId }) : {};
|
||||
|
||||
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 || canToggleRefresh) && editingId !== countdown.id ? "has-inline-reset" : ""}`}>
|
||||
{reorderEnabled && (
|
||||
<button
|
||||
className="timer-drag-handle"
|
||||
type="button"
|
||||
onPointerDown={(event) => reorder.itemReorder.startDrag(event, countdown.id)}
|
||||
aria-label={`${textContent.reorderTitle || "Déplacer"} ${countdown.label}`}
|
||||
title={textContent.reorderTitle || "Déplacer"}
|
||||
>
|
||||
<Icon name="drag" />
|
||||
</button>
|
||||
)}
|
||||
{editingId === countdown.id ? (
|
||||
<EditableTimerLabel
|
||||
value={countdown.label}
|
||||
placeholder={textContent.defaultCountdownLabel || "Timer"}
|
||||
ariaLabel={textContent.countdownLabel || "Libellé"}
|
||||
onRename={(label) => onRename(countdown.id, label)}
|
||||
onDone={() => setEditingId("")}
|
||||
>
|
||||
<TimerValue ms={remainingMs} showCentiseconds={false} />
|
||||
</EditableTimerLabel>
|
||||
) : (
|
||||
<span
|
||||
className="tool-split-entry-summary timer-entry-summary"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setEditingId(countdown.id)}
|
||||
onKeyDown={(event) => handleEditableKeyDown(event, () => setEditingId(countdown.id))}
|
||||
>
|
||||
<span className="timer-entry-line">
|
||||
<TimerValue ms={remainingMs} showCentiseconds={false} />
|
||||
<span>{countdown.label}</span>
|
||||
<span className="timer-inline-controls">
|
||||
{canReset && (
|
||||
<button
|
||||
className="timer-inline-reset"
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onReset(countdown);
|
||||
}}
|
||||
aria-label={textContent.resetTitle || "Réinitialiser"}
|
||||
title={textContent.resetTitle || "Réinitialiser"}
|
||||
>
|
||||
<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)}
|
||||
textContent={textContent}
|
||||
onChange={(alertMode) => onAlertModeChange(countdown.id, alertMode)}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<button className="danger" type="button" onClick={() => onDelete(countdown.id)} aria-label={textContent.deleteTitle || "Supprimer"} title={textContent.deleteTitle || "Supprimer"}>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="timer-entry-meta">
|
||||
<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>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue