import { useEffect, useRef, useState } from "react"; import { ApiAccess } from "./ApiAccess"; import { api, type Channel, type Settings, type Share, type UpdateStatus } from "./api"; import { CopyIcon, TrashIcon } from "./icons"; import { fmtExpiry } from "./ShareDialog"; import { INSTALLED, pendingVersion, rebuildCommand } from "./update"; export const DEFAULT_TITLE = "Excalidraw"; const MAX_TITLE = 80; // voir storage.MaxTitleLen interface Props { settings: Settings; update: UpdateStatus | null; /** Ouvert depuis l'icône de mise à jour : affiche directement la section Excalidraw. */ focusExcalidraw?: boolean; onChange: (s: Settings) => void; onClose: () => void; } const CHANNELS: { id: Channel; label: string; hint: string }[] = [ { id: "latest", label: "Stable", hint: "latest : versions publiées officiellement (recommandé)." }, { id: "next", label: "Préversion", hint: "next : construite à chaque modification de la branche principale d'Excalidraw. Nouveautés en avance, stabilité non garantie. Son numéro (0.18.0-) est inférieur à la dernière stable alors que le code est plus récent.", }, ]; /** Paramètres partagés par tous les utilisateurs : titre, logo, version d'Excalidraw. */ export function SettingsDialog({ settings, update, focusExcalidraw, onChange, onClose }: Props) { const [title, setTitle] = useState(settings.title); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); const [shares, setShares] = useState(null); const fileInput = useRef(null); const exSection = useRef(null); useEffect(() => { if (focusExcalidraw) exSection.current?.scrollIntoView({ block: "start" }); }, [focusExcalidraw]); const loadShares = () => api .shares() .then(setShares) .catch((e) => setError(e.message)); useEffect(() => { loadShares(); }, []); const revoke = async (op: () => Promise, question: string) => { if (!confirm(question)) return; setError(null); try { await op(); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } loadShares(); }; useEffect(() => { const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose(); window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); const run = async (op: () => Promise, close = false) => { setBusy(true); setError(null); try { onChange(await op()); if (close) onClose(); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } }; const onFile = (f: File | undefined) => { if (!f) return; if (f.type !== "image/png") { setError("Seuls les fichiers PNG sont acceptés."); return; } run(() => api.uploadLogo(f)); }; const dirty = title.trim() !== settings.title; const pending = pendingVersion(update, settings.channel); const cmd = pending ? rebuildCommand(pending) : null; const copy = () => cmd && navigator.clipboard?.writeText(cmd).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); return (
e.stopPropagation()} onSubmit={(e) => { e.preventDefault(); run(() => api.setTitle(title), true); }} >

Paramètres

Apparence

Logo
{settings.logo ? ( Logo actuel ) : ( ? )}
{settings.logo && ( )} PNG, recadré au centre et réduit à 64×64 px, affiché en rond.
{ onFile(e.target.files?.[0]); e.target.value = ""; }} />

Excalidraw

Version à suivre
{CHANNELS.map((c) => ( ))}
{CHANNELS.find((c) => c.id === settings.channel)?.hint}

Version installée : {INSTALLED}
{!update?.enabled ? "Vérification des mises à jour désactivée (UPDATE_CHECK=false)." : !update.tags && !update.latest ? "Registre npm injoignable pour l'instant : versions publiées inconnues." : pending ? <>À installer : {pending} : "À jour pour ce canal."}

{cmd && ( <> Excalidraw est compilé dans l'image : changer de canal ou de version ne prend effet qu'après reconstruction, depuis le dossier du docker-compose :
{cmd}
{copied ? "Commande copiée." : <>Pour conserver ce choix aux reconstructions suivantes, mettre EXCALIDRAW_VERSION={pending} dans .env. Tester l'image avant la mise en service : les versions 0.x peuvent changer d'API.} )}

Liens de partage

{shares?.length ? (
    {shares.map((s) => (
  • {s.path}
    Créé le {new Date(s.created).toLocaleDateString(undefined, { dateStyle: "short" })} · {fmtExpiry(s)}
  • ))}
) : ( Aucun lien actif. Pour partager un schéma en lecture seule : icône de partage de la barre du haut, ou menu « … » de l'explorateur. )} {!!shares?.length && ( )}
{error &&
{error}
}
Notes de version
); }