gestion_table_tournante_libre/src/interface/etat_ui.js

123 lines
4.5 KiB
JavaScript
Raw Normal View History

[ADD] interface: application shell — banner, sections, theme, about The window showed only a banner; it now holds the application. App mounts the session, the facade and the generation, settles the working folder and lists its events before it reports ready, re-reading the list once a question is answered or a refused determination succeeds. The banner carries version, mode and the event list command; an open event shows its sections in their first form; the theme follows the system at each start and the operator's choice lasts the session; À propos names the build. Errors are collected for a report, and Electron removes the session's locks at exit. Checked: 2189 node, 193 browser, 60 node-long; the Electron start check. --- FR --- [ADD] interface : coquille — bandeau, sections, thème, À propos La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application. App monte la séance, la façade et la génération, règle le dossier de travail et liste ses événements avant de se dire prête, et relit la liste une fois une question répondue ou une détermination refusée réussie. Le bandeau porte version, mode et la commande de la liste ; un événement ouvert montre ses sections dans leur première forme ; le thème suit le système à chaque démarrage et le choix de l'opérateur vaut pour la séance ; À propos nomme la construction. Les erreurs se collectent pour un signalement, et Electron efface à la sortie les verrous de la séance. Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron. Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// L'état de navigation de l'interface : la section ouverte, le panneau « À
// propos », le tour montré, la personne mise en évidence, la proposition
// demandée au plan, le texte d'un import en attente, les listes de noms, le
// plein écran, la place sélectionnée. Regarder n'écrit jamais (§ 8.4) : cet
// état ne reçoit ni la séance, ni la façade, ni un système de fichiers, et
// rien de ce qu'il tient ne quitte la mémoire.
//
// Il suit le contrat des magasins de Svelte : subscribe(fn) appelle fn(etat)
// tout de suite, puis à chaque nouvel état ; un état est figé, et une
// transition qui ne change rien n'en publie pas de nouveau. L'écran — liste
// ou événement — ne s'y lit pas : il suit la séance.
import { SECTION_OUVERTURE, SECTIONS } from './evenement/sections.js';
/**
* @typedef {Object} EtatUi
* @property {string} section l'une de SECTIONS
* @property {boolean} apropos le panneau « À propos » est ouvert
* @property {number} tour le tour montré, entier positif
* @property {number|null} miseEnEvidence l'identifiant de la personne mise en évidence
* @property {number|null} proposition la proposition demandée au plan
* @property {null|{texte: string, origine: string}} importEnAttente
* @property {boolean} listes les listes de noms sont montrées
* @property {boolean} pleinEcran
* @property {Object|null} selection la place sélectionnée
*/
// L'état de navigation d'un événement qui s'ouvre, panneau « À propos »
// excepté : celui-ci ne tient pas à l'événement.
const DEPART = Object.freeze({
section: SECTION_OUVERTURE,
tour: 1,
miseEnEvidence: null,
proposition: null,
importEnAttente: null,
listes: true,
pleinEcran: false,
selection: null,
});
/**
* Un état de navigation neuf, à son départ.
*
* @returns {{subscribe: Function, etat: () => EtatUi, allerA: Function,
* reinitialiser: Function, ouvrirApropos: Function, fermerApropos: Function,
* montrerTour: Function, mettreEnEvidence: Function,
* montrerProposition: Function, ouvrirImport: Function,
* prendreImport: () => null|{texte: string, origine: string},
* basculerListes: Function, basculerPleinEcran: Function,
* selectionner: Function}}
*/
export function creerEtatUi() {
let etat = Object.freeze({ ...DEPART, apropos: false });
const abonnes = new Set();
// Publie l'état que donnent les changements, s'il diffère du courant sur
// l'une de ses propriétés.
function changer(changements) {
const suivant = { ...etat, ...changements };
if (Object.keys(suivant).every((cle) => Object.is(suivant[cle], etat[cle]))) return;
etat = Object.freeze(suivant);
for (const fn of [...abonnes]) fn(etat);
}
return {
subscribe(fn) {
abonnes.add(fn);
fn(etat);
return () => {
abonnes.delete(fn);
};
},
etat: () => etat,
/** Ouvre une section ; RangeError hors de SECTIONS. */
allerA(section) {
if (!SECTIONS.includes(section)) throw new RangeError(`section inconnue : ${JSON.stringify(section)}`);
changer({ section });
},
/** Le départ d'un événement qui s'ouvre ; le panneau « À propos » reste tel. */
reinitialiser() {
changer(DEPART);
},
ouvrirApropos() {
changer({ apropos: true });
},
fermerApropos() {
changer({ apropos: false });
},
/** Montre le tour r, entier positif ; RangeError sinon. */
montrerTour(r) {
if (!Number.isInteger(r) || r < 1) throw new RangeError(`tour : entier positif attendu, reçu ${String(r)}`);
changer({ tour: r });
},
mettreEnEvidence(id) {
changer({ miseEnEvidence: id });
},
montrerProposition(id) {
changer({ proposition: id });
},
/** Garde un texte à importer et ouvre la section de l'import. */
ouvrirImport({ texte, origine }) {
changer({ importEnAttente: Object.freeze({ texte, origine }), section: 'import' });
},
/** Rend le texte en attente, une fois, puis null. */
prendreImport() {
const enAttente = etat.importEnAttente;
changer({ importEnAttente: null });
return enAttente;
},
basculerListes() {
changer({ listes: !etat.listes });
},
basculerPleinEcran() {
changer({ pleinEcran: !etat.pleinEcran });
},
selectionner(place) {
changer({ selection: place });
},
};
}