gestion_table_tournante_libre/src/interface/propositions/SectionPropositions.svelte

286 lines
11 KiB
Svelte
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) -->
<script>
// La section des propositions (§ 2.1 étape 10, § 5.7, § 9) : la phrase de
// l'ordre du classement, le tableau, et la commande qui efface la liste en
// épargnant le retenu. Regarder n'écrit jamais (§ 8.4) : le tableau se lit
// en lecture et sur un plan bloqué, où la séance refuse elle-même
// « Retenir » et « Effacer », le refus posé à côté du bouton.
//
// La table d'évaluation se lit dans la charge de l'état observable, par
// evaluation (modele.js) : une seule mesure, celle d'evaluer (evaluation.js), refaite
// à chaque charge écrite ; les indicateurs du retenu suivent donc
// l'ajustement (§ 2.1 étape 11).
//
// « Voir sur le plan » demande la proposition au plan par l'état de
// navigation, sans écriture, et ouvre la section du plan ; voir(null) y
// montre le retenu. « Retenir » exécute retenirProposition, puis remet la
// demande à null : le plan montre le retenu. Quand le retenu est ajusté et
// que la séance accepterait le geste, retenir détruit ces ajustements : la
// fenêtre à confirmer le dit d'abord, nommant le retenu ajusté et la
// proposition qui le remplace (§ 2.1). Un geste que la séance refuserait
// s'exécute sans fenêtre, et son refus s'affiche. « Effacer » ne détruit
// aucune saisie — chaque proposition se régénère de sa graine, et le geste
// se défait — : il s'exécute sans fenêtre.
//
// La phrase de l'ordre se met bout à bout, morceau par morceau (modele.js) ;
// chaque nom de critère, et l'identifiant croissant, est un élément
// focalisable qui désigne sa colonne. Le survoler ou le focaliser surligne
// cette colonne dans le tableau. Le survol et le focus se tiennent à part,
// et surlignee (modele.js) choisit : le nom survolé, sinon celui qui a le
// focus. Quitter un nom — pointeur sorti, focus perdu — n'efface que l'état
// qu'il tenait, et laisse l'autre.
//
// Chaque nom de critère et chaque en-tête de colonne s'expliquent dans
// l'infobulle de la section, une seule (Infobulle, primitive) : le survol
// ou le focus l'ouvre sur le terme, le quitter la referme quand elle est
// encore la sienne, Échap aussi. Un critère et sa colonne partagent leur
// explication (explicationDeColonne, modele.js). Le terme expliqué porte
// aria-describedby vers elle, et lui seul.
//
// Habillage : le tableau et la phrase de l'ordre vivent dans une carte
// titrée, « Effacer les propositions » à droite de son en-tête ; le message
// d'une liste vide et le champ de la cible, chacun dans sa carte.
//
// Le champ de la cible de la somme de redondance montre la cible de
// l'événement (cibleRedondance), et exécute changerCibleRedondance à la
// sortie du champ — une entrée ; la saisie se lit par lireEntier, et un
// refus se pose contre le champ (Champ).
[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
import { getContext } from 'svelte';
import { lireEntier } from '../../application/capacite.js';
import { cibleRedondance } from '../../application/evaluation.js';
import { REGLAGES_GENERATION, reglagesDeSection } from '../generation/reglages.js';
import Carte from '../primitives/Carte.svelte';
import Champ from '../primitives/Champ.svelte';
import Commande from '../primitives/Commande.svelte';
import Confirmation from '../primitives/Confirmation.svelte';
import Infobulle from '../primitives/Infobulle.svelte';
import Refus from '../primitives/Refus.svelte';
import TableauPropositions from './TableauPropositions.svelte';
import { demandeConfirmation, evaluation, explicationDeColonne, nomDe, retenuAjuste, surlignee as colonneSurlignee, tableau } from './modele.js';
[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
const t = getContext('t');
// Le champ de la cible lit l'entrée que lit aussi le tableau des réglages
// de la documentation (§ 19.8).
const [entreeCible] = reglagesDeSection(REGLAGES_GENERATION, 'propositions');
const seance = getContext('seance');
const ui = getContext('ui');
const charge = $derived($seance.charge);
const evaluee = $derived(charge === null ? { table: null, refus: null } : evaluation(charge));
const ajuste = $derived(charge !== null && retenuAjuste(charge));
const vue = $derived(evaluee.table === null ? null : tableau(evaluee.table, { ajuste }));
// La retenue en attente de confirmation, telle que demandeConfirmation la
// rend : la proposition demandée, le titre et les éléments de la fenêtre ;
// null sans fenêtre.
let aConfirmer = $state(null);
function voir(id) {
ui.montrerProposition(id);
ui.allerA('plan');
}
async function retenirSansQuestion(id) {
await seance.executer('retenirProposition', { id });
ui.montrerProposition(null);
}
async function retenir(id) {
const demande = demandeConfirmation(seance.etat(), id);
if (demande !== null) {
aConfirmer = demande;
return;
}
await retenirSansQuestion(id);
}
async function confirmer() {
await retenirSansQuestion(aConfirmer.id);
aConfirmer = null;
}
// Les colonnes du nom survolé et du nom focalisé, null sans lui ; la
// colonne surlignée s'en déduit.
let survolee = $state(null);
let focalisee = $state(null);
const surlignee = $derived(colonneSurlignee(survolee, focalisee));
// Le terme expliqué : {ancre, terme, cle, details} — son élément, sa
// désignation, 'critere:<critère>' pour un nom de la phrase ou
// 'colonne:<nom>' pour un en-tête, et la clé de son explication — ; null
// sans infobulle. Ouvrir sur un terme remplace l'explication d'un autre ;
// quitter un terme ne ferme que la sienne.
const uid = $props.id();
const idExplication = `${uid}-explication`;
let expliquee = $state(null);
function expliquer(evenement, terme, colonne) {
expliquee = { ancre: evenement.currentTarget, terme, cle: explicationDeColonne(colonne), details: {} };
}
function taire(terme) {
if (expliquee?.terme === terme) expliquee = null;
}
const fermerExplication = () => (expliquee = null);
// L'aria-describedby d'un terme : l'infobulle quand elle l'explique.
const decrit = (terme) => (expliquee?.terme === terme ? idExplication : undefined);
// L'entrée sur un nom, par le pointeur ou par le focus, et sa sortie.
function entrer(evenement, { critere, colonne }, parFocus) {
if (parFocus) focalisee = colonne;
else survolee = colonne;
expliquer(evenement, `critere:${critere}`, colonne);
}
function sortir({ critere }, parFocus) {
if (parFocus) focalisee = null;
else survolee = null;
taire(`critere:${critere}`);
}
const effacer = () => seance.executer('effacerPropositions', {});
const changerCible = (cible) => seance.executer('changerCibleRedondance', { cible });
const nommer = (id) => nomDe(charge, id);
[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
</script>
{#snippet effacerListe()}
<Commande cle="propositions.commande.effacer" executer={effacer} />
{/snippet}
[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
<section data-section="propositions">
<h2>{t('coquille.section.propositions')}</h2>
{#if charge !== null && charge.propositions.length === 0}
<Carte>
<p>{t('propositions.aucune')}</p>
</Carte>
{/if}
{#if evaluee.refus !== null}
<Refus refus={evaluee.refus} />
{/if}
{#if vue !== null}
<Carte titre={t('propositions.tableau.titre')} niveau={3} plein actions={charge.propositions.length > 0 ? effacerListe : null}>
<div class="ordre" data-ordre>
{#each vue.ordre as morceaux, rang (rang)}
<p>
{#each morceaux as morceau, place (place)}
{#if morceau.colonne === null}
{t(morceau.cle, morceau.details)}
{:else}
<!-- Le nom est un terme, non une commande : aucun rôle n'annonce
d'action, et le focus ne sert qu'à montrer au clavier la
colonne que le pointeur montre au survol. -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_static_element_interactions -->
<span
class="critere"
class:actif={surlignee === morceau.colonne}
data-critere={morceau.critere}
tabindex="0"
aria-describedby={decrit(`critere:${morceau.critere}`)}
onmouseenter={(evenement) => entrer(evenement, morceau, false)}
onmouseleave={() => sortir(morceau, false)}
onfocus={(evenement) => entrer(evenement, morceau, true)}
onblur={() => sortir(morceau, true)}>{t(morceau.cle, morceau.details)}</span
>
{/if}
{/each}
</p>
{/each}
</div>
<TableauPropositions {vue} {surlignee} {nommer} {voir} {retenir} {expliquer} {taire} {decrit} />
</Carte>
{:else if charge !== null && charge.propositions.length > 0}
<div class="effacer">{@render effacerListe()}</div>
{/if}
<Infobulle id={idExplication} ouverte={expliquee} fermer={fermerExplication} />
{#if charge !== null}
<Carte>
<div class="cible" data-cible-redondance>
<Champ
cle={entreeCible.cle}
etiquette={entreeCible.etiquette}
valeur={cibleRedondance(charge)}
lire={lireEntier}
executer={changerCible}
/>
<p class="aide">{t('propositions.cible.aide')}</p>
</div>
</Carte>
{/if}
{#if aConfirmer !== null}
<Confirmation
titre={aConfirmer.titre.cle}
details={aConfirmer.titre.details}
elements={aConfirmer.elements.map((element) => t(element.cle, element.details))}
surConfirmer={confirmer}
surAnnuler={() => (aConfirmer = null)}
/>
{/if}
[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
</section>
<style>
section {
display: flex;
flex-direction: column;
gap: var(--espace-3);
min-width: 0;
}
h2 {
margin: 0;
font-size: var(--taille-titre);
}
p {
margin: 0;
}
/* La phrase de l'ordre, en tête de la carte, au retrait du contenu d'une
carte ; le tableau la suit, collé aux bords. */
.ordre {
display: flex;
flex-direction: column;
gap: var(--espace-1);
padding: var(--espace-3) var(--espace-5);
}
/* Un nom qui désigne une colonne se signale par un soulignement pointillé ;
survolé ou focalisé, par un soulignement plein. Il ne se coupe pas d'une
ligne à l'autre : sa boîte, que le pointeur survole, reste d'un tenant. */
.critere {
white-space: nowrap;
text-decoration: underline dotted;
text-underline-offset: 0.2em;
}
.critere.actif {
text-decoration-style: solid;
text-decoration-thickness: 2px;
}
.critere:focus-visible {
outline: 2px solid var(--couleur-focus);
outline-offset: 2px;
}
.effacer {
display: flex;
}
.cible {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--espace-1);
}
.aide {
color: var(--couleur-page-texte-secondaire);
font-size: var(--taille-texte-petit);
}
</style>