[IMP] interface: full-width event screen, no mode frame, fewer columns
The 6 px mode frame around the event screen read as a dashed box that narrowed the work surface, and the screen stopped at the 87rem page width. The event screen now spans the window; the mode band under the app header, the header label and the pill beside « Modifier » carry the mode. The event list drops both « Collisions cumulées » columns, which repeated a measure the proposals screen already shows: 7 columns, not 9. Checked: node 4009, browser 872, long 105; full width at 1366 and 1920. --- FR --- [IMP] interface : événement pleine fenêtre, sans cadre, liste allégée Le cadre de mode de 6 px autour de l'écran d'événement se lisait comme un encadré pointillé qui rétrécissait la surface, et l'écran s'arrêtait à 87rem. L'écran de l'événement occupe la fenêtre ; la bande sous le bandeau, le libellé du bandeau et la pastille près de « Modifier » portent le mode. La liste des événements perd ses deux colonnes « Collisions cumulées », déjà montrées par les propositions : 7 colonnes au lieu de 9. Vérifié : node 4009, navigateur 872, long 105 ; pleine largeur à 1366 et 1920. Assisted-by: Claude Opus 5.5
This commit is contained in:
parent
40bdfc7d77
commit
8b0d5a9097
32 changed files with 444 additions and 517 deletions
|
|
@ -23,8 +23,9 @@ fichiers, lisez `GUIDE-WINDOWS.md`.
|
|||
## Avant de commencer : lecture, écriture, et trois garanties
|
||||
|
||||
**Tout événement s’ouvre en lecture.** Rien ne s’y modifie tant que vous n’avez
|
||||
pas cliqué sur « Modifier », toujours à la même place, en haut de l’écran. Un
|
||||
cadre autour du plan et le bandeau disent dans quel mode vous êtes. Après un
|
||||
pas cliqué sur « Modifier », toujours à la même place, en haut de l’écran. La
|
||||
bande de couleur sous le bandeau, le libellé à côté de « Modifier » et le
|
||||
bandeau disent dans quel mode vous êtes. Après un
|
||||
moment d’inactivité, le logiciel revient seul en lecture, entre deux gestes et
|
||||
jamais au milieu de l’un d’eux.
|
||||
|
||||
|
|
@ -71,10 +72,10 @@ s’affiche à côté du bouton, et propose « Modifier ».
|
|||

|
||||
|
||||
*Le refus apparaît là où vous avez cliqué, sans fenêtre. Le mot « Lecture »
|
||||
figure dans le bandeau et sur le cadre de l’événement.*
|
||||
figure dans le bandeau, dans la bande de mode et à côté de « Modifier ».*
|
||||
|
||||
Cliquez sur « Modifier » : le cadre passe en écriture, et le même geste
|
||||
réussit. « Revenir en lecture » fait l’inverse.
|
||||
Cliquez sur « Modifier » : la bande et le libellé passent en écriture, et le
|
||||
même geste réussit. « Revenir en lecture » fait l’inverse.
|
||||
|
||||
## Étape 2 — Importer la liste des participants
|
||||
|
||||
|
|
@ -306,8 +307,8 @@ réservations imposent de ce que la recherche a choisi.
|
|||
proposition alors que le retenu a été ajusté à la main détruit ces ajustements :
|
||||
une fenêtre le dit avant.
|
||||
|
||||
La liste des événements résume ensuite le retenu par ses collisions cumulées et
|
||||
par l’écart de sa somme de redondance d’appartenance à la cible de la soirée.
|
||||
La liste des événements résume ensuite le retenu par l’écart de sa somme de
|
||||
redondance d’appartenance à la cible de la soirée.
|
||||
|
||||
Retenir n’est pas obligatoire, mais tout ce qui s’imprime sans placement retenu
|
||||
porte la mention BROUILLON (étape 12).
|
||||
|
|
@ -329,9 +330,10 @@ Les indicateurs du tableau des propositions suivent l’ajustement.
|
|||
|
||||
En lecture, aucun de ces gestes ne passe :
|
||||
|
||||

|
||||

|
||||
|
||||
*Le cadre et le libellé « Lecture » entourent le plan, et le bandeau le répète.
|
||||
*La bande de mode et le libellé « Lecture » le disent au-dessus du plan, et le
|
||||
bandeau le répète.
|
||||
Glisser une personne ou une table, tirer une poignée, poser une réservation :
|
||||
chaque geste est refusé avec « Modifier » pour remède, et rien ne change sur le
|
||||
disque.*
|
||||
|
|
|
|||
95
spec.md
95
spec.md
|
|
@ -178,8 +178,8 @@ propositions sur le même axe — celui du **manque** (§ 12.10) —, retient ce
|
|||
qu'il préfère. Le logiciel sépare ce que le moteur a décidé de ce que les
|
||||
réservations imposent (§ 5.4). Retenir fait passer le plan à l'état *Retenu*
|
||||
(§ 9) ; les propositions non retenues restent disponibles. Le retenu se résume
|
||||
ensuite dans la liste des événements, par ses collisions cumulées et la
|
||||
différence absolue de sa somme de redondance à la cible de l'événement (§ 8.1).
|
||||
ensuite dans la liste des événements, par la différence absolue de sa somme de
|
||||
redondance à la cible de l'événement (§ 8.1).
|
||||
|
||||
**11. Ajuster à la main.** *Facultative, reprenable.* Le même geste qu'à
|
||||
l'étape 7 déplace maintenant une personne réelle dans le placement retenu. **Le
|
||||
|
|
@ -1265,24 +1265,24 @@ pastille de l'état et son compte —, ou ne les groupe pas, au choix ; ses
|
|||
en-têtes de colonnes se groupent en « Événement », « Placement retenu » et
|
||||
« Meilleure proposition du classement », et le nom de chaque ligne en est
|
||||
l'en-tête, la date et le chemin en petite ligne dessous. Sous le tableau, des
|
||||
cartes offrent la création, le dossier de travail et les démonstrations. Deux
|
||||
colonnes résument en plus le **placement retenu** : « Collisions cumulées (paire
|
||||
et tour) », les collisions cumulées du retenu (§ 5.4), et « Diff somme
|
||||
d'appartenance », |cible − Σ r(p)|, où Σ r(p) est la somme de redondance du
|
||||
retenu — le nombre de la colonne « Redondance d'appartenance, somme Σ r(p) » du
|
||||
tableau des propositions — et la cible celle de l'événement (§ 8.9), 1000 par
|
||||
défaut, réglable dans la section des propositions. Les deux se lisent dans la
|
||||
même mesure que le tableau, sans rien écrire ; un événement sans retenu, ou dont
|
||||
le retenu est fautif ou en dérive (§ 8.9, § 9), y porte « — ». Chaque ligne
|
||||
porte aussi la **« Meilleure proposition du classement »**, sous les libellés du
|
||||
bloc que la section de la génération montre en tête : l'identifiant de la
|
||||
proposition au rang 1 du classement (§ 5.7) et ses collisions cumulées — la même
|
||||
proposition et le même nombre que ce bloc et que la première ligne du tableau
|
||||
des propositions, retenue ou non ; une proposition fautive, que l'ouverture
|
||||
écarte (§ 8.9, point 3), n'y entre pas. Sans proposition classée — aucune, ou
|
||||
aucune qui décrive encore la configuration —, l'un et l'autre valent « — ».
|
||||
Chaque intitulé s'explique dans une infobulle, posée sur l'en-tête de sa
|
||||
colonne.
|
||||
cartes offrent la création, le dossier de travail et les démonstrations. Une
|
||||
colonne résume en plus le **placement retenu** : « Diff somme d'appartenance »,
|
||||
|cible − Σ r(p)|, où Σ r(p) est la somme de redondance du retenu — le nombre de
|
||||
la colonne « Redondance d'appartenance, somme Σ r(p) » du tableau des
|
||||
propositions — et la cible celle de l'événement (§ 8.9), 1000 par défaut,
|
||||
réglable dans la section des propositions. Elle se lit dans la même mesure que
|
||||
le tableau, sans rien écrire ; un événement sans retenu, ou dont le retenu est
|
||||
fautif ou en dérive (§ 8.9, § 9), y porte « — ». Chaque ligne porte aussi la
|
||||
**« Meilleure proposition du classement »**, sous les libellés du bloc que la
|
||||
section de la génération montre en tête : l'identifiant de la proposition au
|
||||
rang 1 du classement (§ 5.7) — la même proposition que ce bloc et que la
|
||||
première ligne du tableau des propositions, retenue ou non ; une proposition
|
||||
fautive, que l'ouverture écarte (§ 8.9, point 3), n'y entre pas. Sans
|
||||
proposition classée — aucune, ou aucune qui décrive encore la configuration —,
|
||||
elle vaut « — ». La liste ne montre les collisions cumulées ni du retenu ni de
|
||||
la meilleure proposition : le tableau des propositions et le bloc de la
|
||||
génération les portent. Chaque intitulé s'explique dans une infobulle, posée
|
||||
sur l'en-tête de sa colonne.
|
||||
**La liste paraît avant ses colonnes calculées** : ses lignes s'affichent dès le
|
||||
relevé du dossier, et les mesures du retenu et la meilleure proposition, qui
|
||||
évaluent chaque événement, se calculent ensuite, un événement après l'autre, la
|
||||
|
|
@ -1406,30 +1406,31 @@ ou refusé, une saisie validée ou abandonnée, une fenêtre à confirmer referm
|
|||
et tout appui relâché sur le plan — un clic qui n'exécute rien et un déplacement
|
||||
de vue compris. Regarder sans appuyer n'en est pas une : zoomer à la molette,
|
||||
une commande de vue, changer de tour affiché ne relancent rien. Le retour
|
||||
automatique se voit au cadre, au libellé, à la bande de mode et au bandeau, et
|
||||
un avertissement non modal le dit jusqu'au passage en écriture suivant.
|
||||
automatique se voit à la bande de mode, au libellé de mode et au bandeau, et un
|
||||
avertissement non modal le dit jusqu'au passage en écriture suivant.
|
||||
|
||||
**Le mode est visible sur la surface de travail elle-même** — un cadre et un
|
||||
libellé permanents autour du plan — et non dans un coin de barre d'outils :
|
||||
l'opérateur regarde le plan.
|
||||
**Le mode est visible sans quitter la surface de travail des yeux**, et non dans
|
||||
un coin de barre d'outils : l'opérateur regarde le plan. Sous le bandeau, quand
|
||||
un plan est ouvert, une **bande de mode** court de bord à bord de la fenêtre et
|
||||
dit le mode : son icône, son libellé, et une phrase qui dit ce qu'il permet en
|
||||
nommant la commande de la place fixe qui en sort — « Modifier » en lecture,
|
||||
« Revenir en lecture » en écriture, « Débloquer » sur un plan bloqué, et aucune
|
||||
devant un fichier d'un format plus récent (§ 8.8), qui n'en offre pas —, sur le
|
||||
fond de sa couleur. D'une hauteur constante, elle le dit dès l'entrée dans
|
||||
l'écran. Le **libellé de mode**, une pastille à côté de la place fixe, le redit
|
||||
là où la main agit. **Aucun cadre n'entoure la surface de travail** : l'écran de
|
||||
l'événement prend **toute la largeur de la fenêtre**, à son retrait près, sans
|
||||
la largeur de page qui borne la liste des événements, et le plan garde chaque
|
||||
pixel que la bande et la barre lui laissent.
|
||||
|
||||
S'y ajoute un **bandeau d'application présent sur tout écran**, qui porte la
|
||||
version et la provenance de la construction (§ 18.6) et, **quand un plan est
|
||||
ouvert**, le libellé de mode. Les deux ne font pas double emploi : le cadre
|
||||
signale le mode là où la main agit ; le bandeau porte la version jusque sur la
|
||||
liste des événements, qui est le premier écran d'un signalement sur deux. Le
|
||||
bandeau porte aussi la marque du logiciel, le réglage de thème et « À propos » ;
|
||||
il garde une hauteur d'une ligne de commande, de sorte que la surface de travail
|
||||
ne perd rien.
|
||||
|
||||
Sous le bandeau, quand un plan est ouvert, une **bande de mode** court de bord à
|
||||
bord de la fenêtre et redit le mode : son icône, son libellé, et une phrase qui
|
||||
dit ce qu'il permet en nommant la commande de la place fixe qui en sort —
|
||||
« Modifier » en lecture, « Revenir en lecture » en écriture, « Débloquer » sur
|
||||
un plan bloqué, et aucune devant un fichier d'un format plus récent (§ 8.8), qui
|
||||
n'en offre pas —, sur le fond de sa couleur. D'une hauteur constante, elle
|
||||
s'ajoute au cadre sans le remplacer : le cadre reste là où la main agit, la
|
||||
bande le dit dès l'entrée dans l'écran.
|
||||
ouvert**, le libellé de mode. Ils ne font pas double emploi : la bande et le
|
||||
libellé de la barre signalent le mode dans l'écran de l'événement ; le bandeau
|
||||
porte la version jusque sur la liste des événements, qui est le premier écran
|
||||
d'un signalement sur deux. Le bandeau porte aussi la marque du logiciel, le
|
||||
réglage de thème et « À propos » ; il garde une hauteur d'une ligne de commande,
|
||||
de sorte que la surface de travail ne perd rien.
|
||||
|
||||
**Le passage en écriture est une étape nommée du parcours** (§ 2.1, étape 1b),
|
||||
et non un sous-entendu : huit des douze étapes sont modifiantes, et toutes sont
|
||||
|
|
@ -3357,7 +3358,7 @@ des fonctions pures du module de géométrie.
|
|||
| tenir hors ligne | se compile en JavaScript ordinaire ; Vite est une dépendance de développement qui ne survit pas à la construction |
|
||||
| rester rapide | compile des mises à jour fines, sans arbre virtuel ni mémoïsation à poser à la main |
|
||||
| se tester sans navigateur | n'impose d'écrire aucun calcul dans un composant ; le composant ne contient que le câblage |
|
||||
| être joli sans y passer des semaines | un fichier de jetons — couleurs, rayons, ombres, échelle typographique, cible principale, largeur de page — décliné en deux thèmes ; ses familles de couleur : la page, le bandeau, la carte, l'action principale, les quatre états du plan, le neutre, les appartenances, la bande et le cadre de mode, les messages (avis, refus, information), le plan, le papier |
|
||||
| être joli sans y passer des semaines | un fichier de jetons — couleurs, rayons, ombres, échelle typographique, cible principale, largeur de page — décliné en deux thèmes ; ses familles de couleur : la page, le bandeau, la carte, l'action principale, les quatre états du plan, le neutre, les appartenances, la bande de mode, les messages (avis, refus, information), le plan, le papier |
|
||||
|
||||
**Les candidates écartées.** Vue 3 est le second choix défendable, écarté de peu
|
||||
sur le poids et sur la discipline qu'exige sa réactivité. React est écarté sur la
|
||||
|
|
@ -4934,10 +4935,10 @@ complément.** Un signalement arrive le plus souvent sous forme d'image, et une
|
|||
capture faite à la touche d'impression d'écran, ou cadrée sur le défaut, ne
|
||||
contient pas toujours la barre de titre du système — elle ne la contient jamais sur
|
||||
la plateforme `web`, où le titre est celui d'un onglet. Un bandeau dessiné par
|
||||
l'application est dans l'image quel que soit le cadrage. Le § 8.4 impose en plus un
|
||||
cadre permanent **autour du plan** pour le mode : les deux coexistent, le cadre
|
||||
signalant le mode là où la main agit, le bandeau portant la version jusque sur la
|
||||
liste des événements.
|
||||
l'application est dans l'image quel que soit le cadrage. Le § 8.4 impose en plus une
|
||||
bande de mode **sous le bandeau** et un libellé de mode à côté de la place fixe : ils
|
||||
coexistent, la bande et le libellé signalant le mode dans l'écran de l'événement, le
|
||||
bandeau portant la version jusque sur la liste des événements.
|
||||
|
||||
**Le bandeau annonce aussi la version de format du fichier ouvert** dès qu'elle
|
||||
diffère de celle que le binaire écrit : plus ancienne, « ce fichier se convertira à
|
||||
|
|
@ -5088,7 +5089,7 @@ n'interviennent qu'aux étapes où une configuration **déjà tendue** est le pr
|
|||
| fichier | configuration | ce que la capture d'écran montre | ce que son étape affirme |
|
||||
|---|---|---|---|
|
||||
| `01-premier-ecran.png` | répertoire neuf | le répertoire vide : la commande de création et les configurations de démonstration offertes, **jamais une liste vide** (§ 8.1) | chaque configuration que le catalogue déclare est nommée et cliquable, et **leur nombre est lu dans le catalogue, jamais écrit dans le scénario** |
|
||||
| `01b-passer-en-ecriture.png` | idem | la commande « Modifier » à place fixe, et le cadre de mode autour du plan (§ 8.4) | avant ce geste, une tentative de modification est refusée ; après, elle passe |
|
||||
| `01b-passer-en-ecriture.png` | idem | la commande « Modifier » à place fixe, la bande de mode et le libellé de mode, sans cadre autour du plan (§ 8.4) | avant ce geste, une tentative de modification est refusée ; après, elle passe |
|
||||
| `02-import-apercu.png` | `participants_cas_limites.csv` | l'aperçu obligatoire (§ 10.1) : encodage et séparateur détectés et **nommés**, association par en-tête, nombre de lignes refusées | l'encodage et le séparateur affichés sont ceux que le module d'import a retenus ; l'aperçu n'est pas contournable |
|
||||
| `02b-import-cp1252.png` | `participants_cas_limites_cp1252.csv` | l'aperçu du même contenu en windows-1252, séparé par des virgules : l'encodage détecté et **nommé**, les cellules accentuées intactes (§ 10.1) | l'encodage affiché est celui que le module d'import a retenu ; les cellules accentuées affichées sont celles que rend le décodage du fichier, sans caractère parasite |
|
||||
| `03-import-rapport.png` | idem | le rapport : lignes valides, orthographes d'appartenance fondues avec leur compte, lignes refusées avec leur motif | au moins un groupe d'orthographes fondu et au moins une ligne refusée sont visibles |
|
||||
|
|
@ -5111,7 +5112,7 @@ n'interviennent qu'aux étapes où une configuration **déjà tendue** est le pr
|
|||
| `17-comparaison-manque.png` | **variante sans exception** (§ 15.1) | les deux profils de manque qui se croisent, l'axe commun, le zéro libellé, les trois effectifs de rangs (§ 12.10.6) | la page écrit sa question en toutes lettres et les trois effectifs ; aucune échelle propre à une courbe |
|
||||
| `18-comparaison-escalier.png` | idem | l'escalier du manque, jumeau tabulaire de la vue précédente | les effectifs cumulés du tableau reproduisent la courbe |
|
||||
| `19-ajustement.png` | l'événement retenu | le déplacement d'une personne après la retenue, l'interface disant **lequel des deux gestes** est en cours (§ 5.8) | le libellé de l'état des données est visible |
|
||||
| `20-mode-lecture.png` | idem | le cadre et le libellé permanents autour du plan, et le bandeau d'application (§ 8.4) | un geste de chaque famille — glisser une personne, glisser une table, tirer une poignée, poser une réservation — est tenté, et l'empreinte d'état est inchangée après les quatre |
|
||||
| `20-mode-lecture.png` | idem | la bande de mode, le libellé de mode à côté de la place fixe, et le bandeau d'application (§ 8.4) | un geste de chaque famille — glisser une personne, glisser une table, tirer une poignée, poser une réservation — est tenté, et l'empreinte d'état est inchangée après les quatre |
|
||||
| `21-historique.png` | idem | la liste des instants, le fil courant distingué des fils abandonnés, un jalon nommé (§ 8.3) | au moins un fil abandonné est présent et marqué comme tel |
|
||||
| `22-plan-brouillon.png` / `23-plan-retenu.png` | idem | l'**aperçu d'impression** du plan (§ 11.7) avec son cartouche, son échelle graphique et son horodatage, **avec** puis **sans** le filigrane BROUILLON | **deux images, parce qu'une absence ne se photographie pas** : le filigrane présent sur l'un, absent sur l'autre, et la paire est le seul exposé lisible de la règle |
|
||||
| `24-badges-controle.png` / `25-badges-planche.png` | idem | la **feuille de contrôle** avec son échelle de 100 mm et ses comptes, puis une **planche** de quatre A6 et ses trois filets de coupe | voir § 19.6 — ces deux images ne viennent pas du navigateur. La paire est nécessaire : la feuille de contrôle est ce que l'opérateur mesure sous une règle avant d'engager la rame, et une planche seule ne le montre pas |
|
||||
|
|
|
|||
|
|
@ -1013,7 +1013,7 @@ describe('le bandeau de la maquette (§ 8.4, § 18.6)', () => {
|
|||
});
|
||||
|
||||
describe('la page de la maquette : fond, largeur bornée, styles de base', () => {
|
||||
test('au-delà de la largeur de page, l’écran se centre dans --largeur-page ; en deçà, il prend la fenêtre', async () => {
|
||||
test('au-delà de la largeur de page, la liste se centre dans --largeur-page ; en deçà, elle prend la fenêtre ; l’événement prend la fenêtre à toute largeur', async () => {
|
||||
const avant = { largeur: innerWidth, hauteur: innerHeight };
|
||||
try {
|
||||
await fixerCadre(1800, 900);
|
||||
|
|
@ -1027,6 +1027,14 @@ describe('la page de la maquette : fond, largeur bornée, styles de base', () =>
|
|||
await fixerCadre(1366, 700);
|
||||
flushSync();
|
||||
expect(main.getBoundingClientRect().width).toBeCloseTo(document.documentElement.clientWidth, 0);
|
||||
for (const largeur of [1800, 1366]) {
|
||||
await fixerCadre(largeur, 900);
|
||||
const ouvert = await avecEvenementOuvert();
|
||||
const principal = ecran(ouvert).parentElement;
|
||||
expect(ecran(ouvert).dataset.ecran, `${largeur}`).toBe('evenement');
|
||||
expect(principal.getBoundingClientRect().left, `${largeur}`).toBeCloseTo(0, 0);
|
||||
expect(principal.getBoundingClientRect().width, `${largeur}`).toBeCloseTo(document.documentElement.clientWidth, 0);
|
||||
}
|
||||
} finally {
|
||||
await fixerCadre(avant.largeur, avant.hauteur);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@
|
|||
//
|
||||
// La page porte le fond de la page ; l'écran — l'élément <main>, parent
|
||||
// direct de chaque [data-ecran] — se centre dans --largeur-page au-delà
|
||||
// de cette largeur et prend la fenêtre en deçà. App pose les styles de
|
||||
// de cette largeur et prend la fenêtre en deçà ; l'écran de l'événement
|
||||
// retire cette borne et prend la fenêtre à toute largeur
|
||||
// (Evenement.svelte). App pose les styles de
|
||||
// base d'un titre de premier niveau, des cellules d'un tableau et des
|
||||
// champs de saisie de l'écran, de spécificité nulle : le style propre d'un
|
||||
// composant l'emporte toujours, et le document imprimé (§ 11.7), sous
|
||||
|
|
|
|||
|
|
@ -1238,10 +1238,9 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1, § 8.9)', () => {
|
|||
return mesurer(instance, indexerPlan(instance, planDepuisFichier(charge.retenu)));
|
||||
}
|
||||
|
||||
test('un événement retenu : ses collisions cumulées et |1000 \u{2212} Σ r(p)| ; sans retenu : « — » ; rien d’écrit', async () => {
|
||||
test('un événement retenu : |1000 \u{2212} Σ r(p)|, sans colonne des collisions cumulées ; sans retenu : « — » ; rien d’écrit', async () => {
|
||||
const charge = chargeRetenue();
|
||||
const mesures = mesuresDe(charge);
|
||||
expect(mesures.collisionsCumulees).toBeGreaterThan(0);
|
||||
const m = await monter({
|
||||
fichiers: [fichier(`retenue${SUFFIXES.etat}`, etat(charge)), fichier(`club${SUFFIXES.etat}`, texteRenomme('Club des Merles'))],
|
||||
});
|
||||
|
|
@ -1249,21 +1248,17 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1, § 8.9)', () => {
|
|||
await chargerResumes(m);
|
||||
const avant = ecritures(m);
|
||||
const retenue = ligneDe(m, 'retenue');
|
||||
expect(colonne(retenue, 'collisionsCumulees')).toBe(format.nombre(mesures.collisionsCumulees));
|
||||
expect(colonne(retenue, 'diffRedondance')).toBe(format.nombre(Math.abs(1000 - mesures.totalRedondance)));
|
||||
const club = ligneDe(m, 'club');
|
||||
expect(colonne(club, 'collisionsCumulees')).toBe(TIRET);
|
||||
expect(colonne(club, 'diffRedondance')).toBe(TIRET);
|
||||
// Chaque intitulé est un en-tête de colonne, une seule fois pour toute la
|
||||
// liste.
|
||||
for (const [nom, cle] of [
|
||||
['collisionsCumulees', 'accueil.colonne.collisionsCumulees'],
|
||||
['diffRedondance', 'accueil.colonne.diffRedondance'],
|
||||
]) {
|
||||
expect(tous(m, `[data-terme="${nom}"]`).length, nom).toBe(1);
|
||||
expect(terme(accueil(m), nom).closest('thead'), nom).not.toBeNull();
|
||||
expect(terme(accueil(m), nom).textContent).toBe(t(cle));
|
||||
}
|
||||
// L'intitulé est un en-tête de colonne, une seule fois pour toute la
|
||||
// liste ; aucune colonne ni aucun intitulé ne nomme les collisions
|
||||
// cumulées.
|
||||
expect(tous(m, '[data-terme="diffRedondance"]').length).toBe(1);
|
||||
expect(terme(accueil(m), 'diffRedondance').closest('thead')).not.toBeNull();
|
||||
expect(terme(accueil(m), 'diffRedondance').textContent).toBe(t('accueil.colonne.diffRedondance'));
|
||||
expect(tous(m, '[data-colonne="collisionsCumulees"], [data-terme="collisionsCumulees"]')).toEqual([]);
|
||||
expect(tous(m, '[data-ecran="accueil"] thead th').filter((th) => th.textContent.includes(t('propositions.colonne.collisionsCumulees')))).toEqual([]);
|
||||
expect(visible(retenue.querySelector('[data-colonne="diffRedondance"]'))).toBe(true);
|
||||
expect(ecritures(m)).toBe(avant);
|
||||
});
|
||||
|
|
@ -1271,10 +1266,7 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1, § 8.9)', () => {
|
|||
test('chaque intitulé s’explique : le survol puis le focus ouvrent son infobulle, Échap et la sortie la ferment', async () => {
|
||||
const m = await monter({ fichiers: [fichier(`retenue${SUFFIXES.etat}`, etat(chargeRetenue()))] });
|
||||
await vi.waitFor(() => expect(lignes(m).length).toBe(1));
|
||||
await eprouverTermes(accueil(m), [
|
||||
['collisionsCumulees', 'accueil.explication.collisionsCumulees'],
|
||||
['diffRedondance', 'accueil.explication.diffRedondance'],
|
||||
]);
|
||||
await eprouverTermes(accueil(m), [['diffRedondance', 'accueil.explication.diffRedondance']]);
|
||||
});
|
||||
|
||||
test('la cible changée dans les propositions, puis la liste : la différence se lit contre la nouvelle cible', async () => {
|
||||
|
|
@ -1301,7 +1293,6 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1, § 8.9)', () => {
|
|||
await revenirALaListe(m, 1);
|
||||
await resumeRecu('retenue');
|
||||
expect(colonne(ligneDe(m, 'retenue'), 'diffRedondance')).toBe(format.nombre(Math.abs(25 - totalRedondance)));
|
||||
expect(colonne(ligneDe(m, 'retenue'), 'collisionsCumulees')).toBe(format.nombre(mesuresDe(charge).collisionsCumulees));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1323,12 +1314,9 @@ describe('la meilleure proposition du classement dans la liste (§ 5.4, § 5.7,
|
|||
return appliquerCommande('retenirProposition', charge, { id: autre.id }).charge;
|
||||
}
|
||||
|
||||
// Ce que la ligne, ou le bloc de la génération, écrit de la meilleure
|
||||
// proposition.
|
||||
const lu = (bloc) => ({
|
||||
id: bloc.querySelector('[data-meilleure-id]')?.textContent ?? null,
|
||||
collisions: bloc.querySelector('[data-meilleure-collisions]')?.textContent ?? null,
|
||||
});
|
||||
// Ce que la ligne, ou le bloc de la génération, écrit de l'identifiant de
|
||||
// la meilleure proposition.
|
||||
const lu = (bloc) => ({ id: bloc.querySelector('[data-meilleure-id]')?.textContent ?? null });
|
||||
// L'en-tête du groupe de colonnes de la meilleure proposition, et celui de
|
||||
// sa colonne d'identifiant.
|
||||
const groupeMeilleure = (m) => terme(accueil(m), 'meilleure').closest('th');
|
||||
|
|
@ -1342,7 +1330,7 @@ describe('la meilleure proposition du classement dans la liste (§ 5.4, § 5.7,
|
|||
};
|
||||
const blocGeneration = (m) => dans(m, '[data-section="generation"] [data-meilleure]');
|
||||
|
||||
test('un événement classé : le rang 1, son identifiant et ses collisions cumulées, à part du retenu ; sans proposition, « — » ; rien d’écrit', async () => {
|
||||
test('un événement classé : le rang 1 et son identifiant, à part du retenu, sans ses collisions cumulées ; sans proposition, « — » ; rien d’écrit', async () => {
|
||||
const charge = chargeClassee();
|
||||
const table = evaluer(charge);
|
||||
const [premiere] = table.propositions;
|
||||
|
|
@ -1355,35 +1343,29 @@ describe('la meilleure proposition du classement dans la liste (§ 5.4, § 5.7,
|
|||
await chargerResumes(m);
|
||||
const avant = ecritures(m);
|
||||
const classee = ligneDe(m, 'classee');
|
||||
expect(lu(classee)).toEqual({ id: format.nombre(premiere.id), collisions: format.nombre(premiere.mesures.collisionsCumulees) });
|
||||
expect(colonne(classee, 'collisionsCumulees')).toBe(format.nombre(table.retenu.mesures.collisionsCumulees));
|
||||
expect(lu(classee)).toEqual({ id: format.nombre(premiere.id) });
|
||||
// Les intitulés sont ceux du bloc de la génération, en en-têtes de
|
||||
// colonnes : le titre sur le groupe, l'identifiant et les collisions sur
|
||||
// ses deux colonnes ; chaque valeur se tient sous son groupe.
|
||||
// colonnes : le titre sur le groupe, l'identifiant sur sa seule colonne ;
|
||||
// la valeur se tient sous son groupe. Aucune cellule ni aucun intitulé
|
||||
// des collisions de la meilleure.
|
||||
expect(terme(accueil(m), 'meilleure').textContent).toBe(t('generation.meilleure.titre'));
|
||||
expect(groupeMeilleure(m).getAttribute('scope')).toBe('colgroup');
|
||||
expect(groupeMeilleure(m).colSpan).toBe(2);
|
||||
expect(groupeMeilleure(m).colSpan).toBe(1);
|
||||
expect(enteteProposition(m)).toBeDefined();
|
||||
expect(terme(accueil(m), 'meilleureCollisions').textContent).toBe(t('propositions.colonne.collisionsCumulees'));
|
||||
for (const valeur of ['[data-meilleure-id]', '[data-meilleure-collisions]']) {
|
||||
const cellule = classee.querySelector(valeur);
|
||||
expect(visible(cellule), valeur).toBe(true);
|
||||
expect(atteint(cellule), valeur).toBe(true);
|
||||
expect(sous(cellule, groupeMeilleure(m)), valeur).toBe(true);
|
||||
}
|
||||
expect(sous(classee.querySelector('[data-meilleure-id]'), enteteProposition(m))).toBe(true);
|
||||
expect(sous(classee.querySelector('[data-meilleure-collisions]'), terme(accueil(m), 'meilleureCollisions').closest('th'))).toBe(true);
|
||||
expect(lu(ligneDe(m, 'club'))).toEqual({ id: TIRET, collisions: TIRET });
|
||||
const cellule = classee.querySelector('[data-meilleure-id]');
|
||||
expect(visible(cellule)).toBe(true);
|
||||
expect(atteint(cellule)).toBe(true);
|
||||
expect(sous(cellule, groupeMeilleure(m))).toBe(true);
|
||||
expect(sous(cellule, enteteProposition(m))).toBe(true);
|
||||
expect(tous(m, '[data-ecran="accueil"] [data-meilleure-collisions], [data-terme="meilleureCollisions"]')).toEqual([]);
|
||||
expect(lu(ligneDe(m, 'club'))).toEqual({ id: TIRET });
|
||||
expect(ecritures(m)).toBe(avant);
|
||||
});
|
||||
|
||||
test('le titre et l’intitulé des collisions s’expliquent : le survol puis le focus ouvrent l’infobulle, Échap et la sortie la ferment', async () => {
|
||||
test('le titre s’explique : le survol puis le focus ouvrent l’infobulle, Échap et la sortie la ferment', async () => {
|
||||
const m = await monter({ fichiers: [fichier(`classee${SUFFIXES.etat}`, etat(chargeClassee()))] });
|
||||
await vi.waitFor(() => expect(lignes(m).length).toBe(1));
|
||||
await eprouverTermes(accueil(m), [
|
||||
['meilleure', 'accueil.explication.meilleure'],
|
||||
['meilleureCollisions', 'propositions.explication.collisionsCumulees'],
|
||||
]);
|
||||
await eprouverTermes(accueil(m), [['meilleure', 'accueil.explication.meilleure']]);
|
||||
});
|
||||
|
||||
test('la ligne écrit ce que le bloc de la génération écrit du même événement, sous les mêmes libellés, et de nouveau au retour', async () => {
|
||||
|
|
@ -1392,20 +1374,16 @@ describe('la meilleure proposition du classement dans la liste (§ 5.4, § 5.7,
|
|||
await chargerResumes(m);
|
||||
const deLaListe = lu(ligneDe(m, 'classee'));
|
||||
expect(deLaListe.id).toMatch(/\d/);
|
||||
const libelles = [terme(accueil(m), 'meilleure'), enteteProposition(m), terme(accueil(m), 'meilleureCollisions')].map(
|
||||
(element) => element.textContent.trim(),
|
||||
);
|
||||
const libelles = [terme(accueil(m), 'meilleure'), enteteProposition(m)].map((element) => element.textContent.trim());
|
||||
await ouvrirLigne(m, 'classee');
|
||||
await attendreEvenement(m);
|
||||
m.ui.allerA('generation');
|
||||
await vi.waitFor(() => expect(blocGeneration(m)?.querySelector('[data-meilleure-id]')).not.toBeNull());
|
||||
const bloc = blocGeneration(m);
|
||||
expect(lu(bloc)).toEqual(deLaListe);
|
||||
expect(
|
||||
[bloc.querySelector('h3'), bloc.querySelector('[data-meilleure-libelle-id]'), bloc.querySelector('[data-terme]')].map(
|
||||
(element) => element.textContent,
|
||||
),
|
||||
).toEqual(libelles);
|
||||
expect([bloc.querySelector('h3'), bloc.querySelector('[data-meilleure-libelle-id]')].map((element) => element.textContent)).toEqual(
|
||||
libelles,
|
||||
);
|
||||
// Le même résumé, mais reçu de la liste relue au retour : celui d'avant
|
||||
// l'ouverture écrirait les mêmes valeurs sans rien prouver.
|
||||
const resumeRecu = suivreResumes(m);
|
||||
|
|
@ -1423,7 +1401,7 @@ describe('la meilleure proposition du classement dans la liste (§ 5.4, § 5.7,
|
|||
const m = await monter({ fichiers: [fichier(`horsclassement${SUFFIXES.etat}`, etat(charge))] });
|
||||
await vi.waitFor(() => expect(lignes(m).length).toBe(1));
|
||||
await chargerResumes(m);
|
||||
expect(lu(ligneDe(m, 'horsclassement'))).toEqual({ id: TIRET, collisions: TIRET });
|
||||
expect(lu(ligneDe(m, 'horsclassement'))).toEqual({ id: TIRET });
|
||||
await ouvrirLigne(m, 'horsclassement');
|
||||
await attendreEvenement(m);
|
||||
m.ui.allerA('generation');
|
||||
|
|
@ -1473,16 +1451,15 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
return appliquerCommande('retenirProposition', charge, { id: charge.propositions[1].id }).charge;
|
||||
}
|
||||
|
||||
// Les quatre valeurs de résumé d'une ligne, dans l'ordre du rendu.
|
||||
const CELLULES = ['[data-colonne="collisionsCumulees"]', '[data-colonne="diffRedondance"]', '[data-meilleure-id]', '[data-meilleure-collisions]'];
|
||||
// Les deux valeurs de résumé d'une ligne, dans l'ordre du rendu.
|
||||
const CELLULES = ['[data-colonne="diffRedondance"]', '[data-meilleure-id]'];
|
||||
const cellules = (ligne) => CELLULES.map((selecteur) => ligne.querySelector(selecteur));
|
||||
// Ce que la ligne d'une charge écrit, par la table d'évaluation sollicitée
|
||||
// une seconde fois (§ 19.1).
|
||||
function attendues(charge) {
|
||||
const table = evaluer(charge);
|
||||
const [premiere] = table.propositions;
|
||||
const { collisionsCumulees, totalRedondance } = table.retenu.mesures;
|
||||
return [collisionsCumulees, Math.abs(1000 - totalRedondance), premiere.id, premiere.mesures.collisionsCumulees].map((n) => format.nombre(n));
|
||||
return [Math.abs(1000 - table.retenu.mesures.totalRedondance), premiere.id].map((n) => format.nombre(n));
|
||||
}
|
||||
// Chaque valeur de la ligne attend : la cellule se dit occupée, porte un
|
||||
// seul signe, muet pour l'accessibilité (aria-hidden, sans rôle ni nom),
|
||||
|
|
@ -1528,12 +1505,12 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
expect(colonne(ligne, 'participants')).toBe(format.nombre(charges[ligne.dataset.evenement].participants.length));
|
||||
enAttente(ligne);
|
||||
}
|
||||
expect(signes(m).length).toBe(8);
|
||||
expect(signes(m).length).toBe(4);
|
||||
const region = regionEtat(m);
|
||||
expect(region.textContent).toBe(t('accueil.chargement'));
|
||||
for (let image = 0; image < 3; image += 1) await uneImage();
|
||||
flushSync();
|
||||
expect(signes(m).length).toBe(8);
|
||||
expect(signes(m).length).toBe(4);
|
||||
m.horloge.avancer(0);
|
||||
await vi.waitFor(() => resolue(ligneDe(m, 'alpha'), attendues(charges.alpha)));
|
||||
enAttente(ligneDe(m, 'beta'));
|
||||
|
|
@ -1551,7 +1528,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
test('démontée avant le premier pas, l’application arrête le flux des résumés : aucun état ne se relit ensuite', async () => {
|
||||
const charges = { alpha: chargeResumee('evt-alpha'), beta: chargeResumee('evt-beta') };
|
||||
const m = await monter({ fichiers: Object.entries(charges).map(([base, charge]) => fichier(`${base}${SUFFIXES.etat}`, etat(charge))) });
|
||||
expect(signes(m).length).toBe(8);
|
||||
expect(signes(m).length).toBe(4);
|
||||
const relus = [];
|
||||
const lireTexte = m.fs.lireTexte;
|
||||
m.fs.lireTexte = (racine, chemin) => {
|
||||
|
|
@ -1568,7 +1545,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
expect(relus).toEqual([]);
|
||||
});
|
||||
|
||||
test('un état qui ne se relit pas pendant le chargement : « — » dans ses quatre valeurs, et l’autre ligne résolue', async () => {
|
||||
test('un état qui ne se relit pas pendant le chargement : « — » dans ses deux valeurs, et l’autre ligne résolue', async () => {
|
||||
const charges = { alpha: chargeResumee('evt-alpha'), beta: chargeResumee('evt-beta') };
|
||||
const m = await monter({ fichiers: Object.entries(charges).map(([base, charge]) => fichier(`${base}${SUFFIXES.etat}`, etat(charge))) });
|
||||
const lireTexte = m.fs.lireTexte;
|
||||
|
|
@ -1577,7 +1554,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
? Promise.reject(new ErreurStockage('LECTURE', { chemin, cause: 'EIO' }))
|
||||
: lireTexte.call(m.fs, racine, chemin);
|
||||
await chargerResumes(m);
|
||||
resolue(ligneDe(m, 'alpha'), [TIRET, TIRET, TIRET, TIRET]);
|
||||
resolue(ligneDe(m, 'alpha'), [TIRET, TIRET]);
|
||||
resolue(ligneDe(m, 'beta'), attendues(charges.beta));
|
||||
});
|
||||
|
||||
|
|
@ -1611,7 +1588,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
test('le signe se voit : il tient 3:1 sur ce qui l’entoure dans les deux thèmes, sans atténuation', async () => {
|
||||
const m = await monter({ fichiers: [fichier(`alpha${SUFFIXES.etat}`, etat(chargeResumee('evt-alpha')))] });
|
||||
const traces = tous(m, '[data-chargement] path');
|
||||
expect(traces.length).toBe(4);
|
||||
expect(traces.length).toBe(2);
|
||||
for (const theme of ['clair', 'sombre']) {
|
||||
await sousTheme(theme, async () => {
|
||||
for (const trace of traces) {
|
||||
|
|
@ -1822,7 +1799,7 @@ describe('la liste en tableau : en-tête, tuiles, groupes et cartes (§ 8.1, §
|
|||
await chargerResumes(m);
|
||||
const entetes = tous(m, '[data-ecran="accueil"] thead th[scope="colgroup"]');
|
||||
expect(entetes.map(texte)).toEqual([t('accueil.entete.evenement'), t('accueil.entete.retenu'), t('generation.meilleure.titre')]);
|
||||
expect(entetes.map((th) => th.colSpan)).toEqual([4, 2, 2]);
|
||||
expect(entetes.map((th) => th.colSpan)).toEqual([4, 1, 1]);
|
||||
expect(fond(entetes[1])).toEqual(jeton('--couleur-etat-retenu-fond'));
|
||||
expect(fond(entetes[2])).toEqual(jeton('--couleur-etat-propose-fond'));
|
||||
// Le groupe porte le signe de son état, de la marque de l'état.
|
||||
|
|
@ -1835,8 +1812,8 @@ describe('la liste en tableau : en-tête, tuiles, groupes et cartes (§ 8.1, §
|
|||
};
|
||||
const ligne = ligneDe(m, 'gala');
|
||||
for (const nom of ['nom', 'etat', 'participants', 'modification']) expect(dans2(ligne.querySelector(`[data-colonne="${nom}"]`), entetes[0]), nom).toBe(true);
|
||||
for (const nom of ['collisionsCumulees', 'diffRedondance']) expect(dans2(ligne.querySelector(`[data-colonne="${nom}"]`), entetes[1]), nom).toBe(true);
|
||||
for (const valeur of ['[data-meilleure-id]', '[data-meilleure-collisions]']) expect(dans2(ligne.querySelector(valeur), entetes[2]), valeur).toBe(true);
|
||||
expect(dans2(ligne.querySelector('[data-colonne="diffRedondance"]'), entetes[1])).toBe(true);
|
||||
expect(dans2(ligne.querySelector('[data-meilleure-id]'), entetes[2])).toBe(true);
|
||||
// Le nom est l'en-tête de sa ligne ; la date et le chemin le suivent,
|
||||
// dessous, en plus petit.
|
||||
const nom = ligne.querySelector('[data-colonne="nom"]');
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@
|
|||
//
|
||||
// Les en-têtes de colonnes sont groupés : l'événement — nom, état,
|
||||
// participants, dernière modification —, le placement retenu, à la teinte
|
||||
// de Retenu — ses deux mesures (COLONNES_DU_RETENU) —, la meilleure
|
||||
// proposition du classement, à la teinte de Proposé, sous le titre et les
|
||||
// libellés du bloc de la génération (MEILLEURE) — son identifiant et ses
|
||||
// collisions cumulées —, puis les actions. Chaque valeur est dans son
|
||||
// propre nœud : data-colonne, data-meilleure-id, data-meilleure-collisions,
|
||||
// « — » sans valeur. Le nom est l'en-tête de sa ligne ; la date et le
|
||||
// chemin le suivent en petite ligne, chacun précédé de son intitulé, que
|
||||
// de Retenu — sa différence à la cible (COLONNES_DU_RETENU) —, la
|
||||
// meilleure proposition du classement, à la teinte de Proposé, sous le
|
||||
// titre et le libellé du bloc de la génération (MEILLEURE) — son
|
||||
// identifiant —, puis les actions. Chaque valeur est dans son propre
|
||||
// nœud : data-colonne, data-meilleure-id, « — » sans valeur. Le nom est
|
||||
// l'en-tête de sa ligne ; la date et le chemin le suivent en petite
|
||||
// ligne, chacun précédé de son intitulé, que
|
||||
// lisent les technologies d'assistance et que l'œil ne voit pas, puis ses
|
||||
// signalements traduits — les bases qu'un doublon nomme suivent son
|
||||
// texte, chacune dans son propre nœud (data-doublon). L'état est une
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
//
|
||||
// lignes : LigneListe[] de la façade ; resumes : les résumés reçus de
|
||||
// facade.resumer, par base — une ligne sans le sien attend, et chacune de
|
||||
// ses quatre valeurs de résumé porte le signe du chargement (Chargement),
|
||||
// ses deux valeurs de résumé porte le signe du chargement (Chargement),
|
||||
// décoratif, dans une cellule qui se dit occupée (aria-busy), jusqu'à ce
|
||||
// qu'il arrive. La région d'état qui suit le tableau
|
||||
// (data-chargement-liste) existe tant que la liste a des lignes, et écrit
|
||||
|
|
@ -44,10 +44,9 @@
|
|||
// du dernier échec, ou null ; apresOperation() : à appeler quand une
|
||||
// opération a changé le dossier.
|
||||
//
|
||||
// L'intitulé de chaque mesure du retenu, le titre de la meilleure
|
||||
// proposition et l'intitulé de ses collisions s'expliquent dans
|
||||
// l'infobulle de la liste, une seule (Infobulle, primitive) : chaque terme
|
||||
// (data-terme, que pose le fragment termeExplique), un en-tête de colonne,
|
||||
// L'intitulé de la mesure du retenu et le titre de la meilleure
|
||||
// proposition s'expliquent dans l'infobulle de la liste, une seule
|
||||
// (Infobulle, primitive) : chaque terme (data-terme, que pose le fragment termeExplique), un en-tête de colonne,
|
||||
// se focalise au clavier, son survol ou son focus ouvre l'explication, le
|
||||
// quitter la referme quand elle est encore la sienne, Échap aussi. Le
|
||||
// terme expliqué porte aria-describedby vers elle, et lui seul.
|
||||
|
|
@ -73,9 +72,9 @@
|
|||
const enChargement = $derived(lignes.some((ligne) => ligneAffichee(ligne, format, resumes.get(ligne.base)).enAttente));
|
||||
const groupes = $derived(groupesDeLaListe(lignes, vue.filtre, vue.groupement));
|
||||
// Le nombre de colonnes du tableau, que l'intitulé d'un groupe couvre :
|
||||
// les quatre de l'événement, les deux du retenu, les deux de la meilleure
|
||||
// les quatre de l'événement, celle du retenu, celle de la meilleure
|
||||
// proposition, les actions.
|
||||
const COLONNES = 9;
|
||||
const COLONNES = 7;
|
||||
|
||||
// Le terme expliqué : {ancre, terme, cle, details} — son élément, son nom
|
||||
// et la clé de son explication — ; null sans infobulle.
|
||||
|
|
@ -137,18 +136,18 @@
|
|||
<div class="defilement">
|
||||
<table>
|
||||
<colgroup span="4"></colgroup>
|
||||
<colgroup span="2"></colgroup>
|
||||
<colgroup span="2"></colgroup>
|
||||
<colgroup></colgroup>
|
||||
<colgroup></colgroup>
|
||||
<colgroup></colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="4" class="groupe">{t('accueil.entete.evenement')}</th>
|
||||
<th scope="colgroup" colspan="2" class="groupe retenu"
|
||||
<th scope="colgroup" class="groupe retenu"
|
||||
><span class="entete-groupe"
|
||||
><span class="signe"><Icone nom="coche" taille={14} /></span><span>{t('accueil.entete.retenu')}</span></span
|
||||
></th
|
||||
>
|
||||
<th scope="colgroup" colspan="2" class="groupe propose"
|
||||
<th scope="colgroup" class="groupe propose"
|
||||
><span class="entete-groupe"
|
||||
><span class="signe"><Icone nom="listePoints" taille={14} /></span>{@render termeExplique(
|
||||
'meilleure',
|
||||
|
|
@ -168,9 +167,6 @@
|
|||
<th scope="col" class="nombre">{@render termeExplique(colonne, cle, explication)}</th>
|
||||
{/each}
|
||||
<th scope="col" class="nombre">{t(MEILLEURE.proposition)}</th>
|
||||
<th scope="col" class="nombre"
|
||||
>{@render termeExplique('meilleureCollisions', MEILLEURE.collisions, MEILLEURE.explicationCollisions)}</th
|
||||
>
|
||||
</tr>
|
||||
</thead>
|
||||
{#each groupes as groupe, rangGroupe (groupe.groupe ?? '')}
|
||||
|
|
@ -231,9 +227,6 @@
|
|||
<td class="nombre" data-colonne={colonne} aria-busy={occupee}>{@render resume(affichee, affichee[colonne])}</td>
|
||||
{/each}
|
||||
<td class="nombre" data-meilleure-id aria-busy={occupee}>{@render resume(affichee, affichee.meilleure.id)}</td>
|
||||
<td class="nombre" data-meilleure-collisions aria-busy={occupee}
|
||||
>{@render resume(affichee, affichee.meilleure.collisions)}</td
|
||||
>
|
||||
<td class="actions">
|
||||
<div class="commandes-ligne">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@
|
|||
// Le modèle de l'accueil, pur (§ 8.1, § 8.6, § 8.8) : ce que les composants
|
||||
// de la liste des événements affichent, sans arithmétique ni texte. Une
|
||||
// ligne de la liste (LigneListe de la façade, evenements.js) devient ses six
|
||||
// informations et ses signalements, mis en forme, puis, son résumé reçu, les
|
||||
// deux mesures de son retenu et la meilleure proposition de son
|
||||
// classement ; les lignes de la liste, les tuiles de ses filtres par état
|
||||
// du plan, le résumé de son en-tête et les groupes de son tableau ; une
|
||||
// question du dossier de travail, ses réponses ; un état illisible, ses
|
||||
// secours ; un refus de la façade, le refus affiché sur la liste. Chaque clé
|
||||
// rendue est écrite ici en entier : aucun composant ne compose une clé.
|
||||
import { COLONNE_ANNONCEE } from '../generation/meilleure.js';
|
||||
import { COLONNES } from '../propositions/modele.js';
|
||||
// informations et ses signalements, mis en forme, puis, son résumé reçu, la
|
||||
// différence à la cible de son retenu et l'identifiant de la meilleure
|
||||
// proposition de son classement ; les lignes de la liste, les tuiles de ses
|
||||
// filtres par état du plan, le résumé de son en-tête et les groupes de son
|
||||
// tableau ; une question du dossier de travail, ses réponses ; un état
|
||||
// illisible, ses secours ; un refus de la façade, le refus affiché sur la
|
||||
// liste. Chaque clé rendue est écrite ici en entier : aucun composant ne
|
||||
// compose une clé.
|
||||
|
||||
// Les états du plan (§ 9) et la clé de leur nom, dans l'ordre du § 9.
|
||||
const CLES_ETATS = new Map([
|
||||
|
|
@ -26,34 +25,29 @@ const CLES_ETATS = new Map([
|
|||
export const ETATS = Object.freeze([...CLES_ETATS.keys()]);
|
||||
|
||||
/**
|
||||
* Les deux mesures du placement retenu qu'une ligne montre (§ 5.4, § 8.9),
|
||||
* dans l'ordre d'affichage : chacune { colonne, cle, explication } — le nom
|
||||
* de sa valeur dans ligneAffichee et son data-colonne, la clé de son
|
||||
* intitulé, celle de son explication, que l'infobulle de l'intitulé montre.
|
||||
* La mesure du placement retenu qu'une ligne montre (§ 5.4, § 8.9) : la
|
||||
* différence à la cible, { colonne, cle, explication } — le nom de sa valeur
|
||||
* dans ligneAffichee et son data-colonne, la clé de son intitulé, celle de
|
||||
* son explication, que l'infobulle de l'intitulé montre. Les collisions
|
||||
* cumulées du retenu ne se montrent pas dans la liste.
|
||||
*/
|
||||
export const COLONNES_DU_RETENU = Object.freeze(
|
||||
['collisionsCumulees', 'diffRedondance'].map((colonne) =>
|
||||
['diffRedondance'].map((colonne) =>
|
||||
Object.freeze({ colonne, cle: `accueil.colonne.${colonne}`, explication: `accueil.explication.${colonne}` }),
|
||||
),
|
||||
);
|
||||
|
||||
// La colonne du tableau des propositions que le bloc de la génération
|
||||
// annonce : son intitulé et son explication.
|
||||
const COLONNE_MEILLEURE = COLONNES.find(({ nom }) => nom === COLONNE_ANNONCEE);
|
||||
|
||||
/**
|
||||
* Les clés du bloc de la meilleure proposition du classement qu'une ligne
|
||||
* montre (§ 5.4, § 5.7), celles du bloc de la génération : titre,
|
||||
* explication du titre — ce que désigne le rang 1, et quand le bloc écrit
|
||||
* « — » —, libellé de l'identifiant, intitulé des collisions cumulées et
|
||||
* leur explication, celles de la colonne du tableau des propositions.
|
||||
* Les clés de la meilleure proposition du classement qu'une ligne montre
|
||||
* (§ 5.4, § 5.7) : titre et libellé de l'identifiant, ceux du bloc de la
|
||||
* génération, et explication du titre — ce que désigne le rang 1, et quand
|
||||
* la ligne écrit « — ». Ses collisions cumulées ne se montrent pas dans la
|
||||
* liste.
|
||||
*/
|
||||
export const MEILLEURE = Object.freeze({
|
||||
titre: 'generation.meilleure.titre',
|
||||
explication: 'accueil.explication.meilleure',
|
||||
proposition: 'generation.meilleure.proposition',
|
||||
collisions: COLONNE_MEILLEURE.cle,
|
||||
explicationCollisions: COLONNE_MEILLEURE.explication,
|
||||
});
|
||||
|
||||
// La clé de la valeur absente, celle de la mise en forme.
|
||||
|
|
@ -133,9 +127,9 @@ export function signalementsDe(ligne) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Les six informations d'une ligne (§ 8.1), les deux mesures de son retenu
|
||||
* et la meilleure proposition de son classement, mises en forme, et ses
|
||||
* signalements. nom : celui inscrit dans le fichier, ou la base quand l'état
|
||||
* Les six informations d'une ligne (§ 8.1), la différence à la cible de son
|
||||
* retenu et l'identifiant de la meilleure proposition de son classement,
|
||||
* mis en forme, et ses signalements. nom : celui inscrit dans le fichier, ou la base quand l'état
|
||||
* ne se lit pas ; date, participants et dernière modification par format,
|
||||
* la valeur absente quand ils manquent — la dernière modification est
|
||||
* l'horodatage de la dernière entrée du journal, sur l'horloge de
|
||||
|
|
@ -143,13 +137,12 @@ export function signalementsDe(ligne) {
|
|||
* clé de son nom, que le composant traduit ; chemin : tel que la façade
|
||||
* l'affiche, en entier.
|
||||
*
|
||||
* Les quatre valeurs de résumé lisent resume, ce que facade.resumer a rendu
|
||||
* pour la ligne : collisionsCumulees et diffRedondance celles de
|
||||
* mesuresDuRetenu, par format, la valeur absente sans retenu mesurable — la
|
||||
* différence se lit telle quelle, jamais refaite ; meilleure, l'identifiant
|
||||
* et les collisions cumulées de meilleureDuClassement, par format, la valeur
|
||||
* absente l'un et l'autre sans proposition classée. Sans resume, la ligne
|
||||
* est enAttente : son résumé se calcule encore, et les quatre valeurs
|
||||
* Les deux valeurs de résumé lisent resume, ce que facade.resumer a rendu
|
||||
* pour la ligne : diffRedondance, celle de mesuresDuRetenu, par format, la
|
||||
* valeur absente sans retenu mesurable — elle se lit telle quelle, jamais
|
||||
* refaite ; meilleure.id, l'identifiant de meilleureDuClassement, par
|
||||
* format, la valeur absente sans proposition classée. Sans resume, la
|
||||
* ligne est enAttente : son résumé se calcule encore, et les deux valeurs
|
||||
* valent la valeur absente, que le composant remplace par le signe du
|
||||
* chargement.
|
||||
*
|
||||
|
|
@ -158,8 +151,7 @@ export function signalementsDe(ligne) {
|
|||
* @param {{mesuresDuRetenu: Object|null, meilleureDuClassement: Object|null}} [resume]
|
||||
* @returns {{nom: string, date: string, etat: string, participants: string,
|
||||
* modification: string, chemin: string, enAttente: boolean,
|
||||
* collisionsCumulees: string, diffRedondance: string,
|
||||
* meilleure: {id: string, collisions: string},
|
||||
* diffRedondance: string, meilleure: {id: string},
|
||||
* signalements: Array<{cle: string, details: Object, noms: string[]}>}}
|
||||
*/
|
||||
export function ligneAffichee(ligne, format, resume) {
|
||||
|
|
@ -171,12 +163,8 @@ export function ligneAffichee(ligne, format, resume) {
|
|||
modification: ligne.derniereModification === null ? format.nombre(null) : format.horodatage(ligne.derniereModification),
|
||||
chemin: ligne.chemin,
|
||||
enAttente: resume === undefined,
|
||||
collisionsCumulees: format.nombre(resume?.mesuresDuRetenu?.collisionsCumulees ?? null),
|
||||
diffRedondance: format.nombre(resume?.mesuresDuRetenu?.diffRedondance ?? null),
|
||||
meilleure: {
|
||||
id: format.nombre(resume?.meilleureDuClassement?.id ?? null),
|
||||
collisions: format.nombre(resume?.meilleureDuClassement?.collisionsCumulees ?? null),
|
||||
},
|
||||
meilleure: { id: format.nombre(resume?.meilleureDuClassement?.id ?? null) },
|
||||
signalements: signalementsDe(ligne),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ import { texteLivre } from '../../demo/livrees.js';
|
|||
import { rechercher } from '../../moteur/recherche.js';
|
||||
import { analyser, configurationDepuisCharge } from '../../stockage/document.js';
|
||||
import { VERSION } from '../../version.genere.js';
|
||||
import { COLONNE_ANNONCEE, meilleureProposition } from '../generation/meilleure.js';
|
||||
import { meilleureProposition } from '../generation/meilleure.js';
|
||||
import { TABLES_INTERFACE } from '../libelles.js';
|
||||
import { COLONNES, texteCellule } from '../propositions/modele.js';
|
||||
import {
|
||||
COLONNES_DU_RETENU,
|
||||
ETATS,
|
||||
|
|
@ -81,7 +80,6 @@ describe('ligneAffichee : les six informations (§ 8.1)', () => {
|
|||
const affichee = ligneAffichee(ligne(), format);
|
||||
assert.deepEqual(Object.keys(affichee).sort(), [
|
||||
'chemin',
|
||||
'collisionsCumulees',
|
||||
'date',
|
||||
'diffRedondance',
|
||||
'enAttente',
|
||||
|
|
@ -337,29 +335,28 @@ describe('saisieDe : la saisie d’un nom pour copier ou renommer (§ 8.7)', ()
|
|||
});
|
||||
|
||||
describe('ligneAffichee : les mesures du retenu (§ 5.4, § 8.1, § 8.9)', () => {
|
||||
test('les collisions cumulées et la différence à la cible, celles de la façade, mises en forme ; aucune ne se calcule ici', () => {
|
||||
const mesuresDuRetenu = { collisionsCumulees: 1234, totalRedondance: 1500, cibleRedondance: 1000, diffRedondance: 500 };
|
||||
test('la différence à la cible, celle de la façade, mise en forme, sans calcul ici ; les collisions cumulées du retenu ne se montrent pas', () => {
|
||||
const mesuresDuRetenu = { collisionsCumulees: 8765, totalRedondance: 1500, cibleRedondance: 1000, diffRedondance: 500 };
|
||||
const affichee = ligneAffichee(ligne(), format, { ...AUCUN, mesuresDuRetenu });
|
||||
assert.equal(affichee.collisionsCumulees, format.nombre(1234));
|
||||
assert.equal(affichee.diffRedondance, format.nombre(500));
|
||||
assert.ok(!Object.values(affichee).includes(format.nombre(8765)));
|
||||
// La différence se lit telle que la façade la rend, jamais refaite
|
||||
// depuis la cible et la somme.
|
||||
const telleQuelle = ligneAffichee(ligne(), format, { ...AUCUN, mesuresDuRetenu: { ...mesuresDuRetenu, diffRedondance: 7 } });
|
||||
assert.equal(telleQuelle.diffRedondance, format.nombre(7));
|
||||
});
|
||||
|
||||
test('sans retenu mesurable, et pour une ligne illisible, les deux valent « — »', () => {
|
||||
test('sans retenu mesurable, et pour une ligne illisible, la différence vaut « — »', () => {
|
||||
for (const champs of [{}, { nom: null, etat: null, participants: null, illisible: { raison: 'VIDE', chemin: null } }]) {
|
||||
const affichee = ligneAffichee(ligne(champs), format, AUCUN);
|
||||
assert.equal(affichee.collisionsCumulees, VIDE);
|
||||
assert.equal(affichee.diffRedondance, VIDE);
|
||||
}
|
||||
});
|
||||
|
||||
test('COLONNES_DU_RETENU : les deux colonnes, dans leur ordre, chacune avec son intitulé et son explication, qui existent', () => {
|
||||
test('COLONNES_DU_RETENU : la seule différence à la cible, avec son intitulé et son explication, qui existent ; aucune clé des collisions cumulées dans la liste', () => {
|
||||
assert.deepEqual(
|
||||
COLONNES_DU_RETENU.map(({ colonne }) => colonne),
|
||||
['collisionsCumulees', 'diffRedondance'],
|
||||
['diffRedondance'],
|
||||
);
|
||||
assert.ok(Object.isFrozen(COLONNES_DU_RETENU));
|
||||
for (const { colonne, cle, explication } of COLONNES_DU_RETENU) {
|
||||
|
|
@ -368,30 +365,29 @@ describe('ligneAffichee : les mesures du retenu (§ 5.4, § 8.1, § 8.9)', () =>
|
|||
assert.ok(t(cle).length > 0);
|
||||
assert.ok(t(explication).length > 0);
|
||||
}
|
||||
assert.equal(t('accueil.colonne.collisionsCumulees'), 'Collisions cumulées (paire et tour)');
|
||||
assert.equal(t('accueil.colonne.diffRedondance'), "Diff somme d'appartenance");
|
||||
for (const cle of ['accueil.colonne.collisionsCumulees', 'accueil.explication.collisionsCumulees']) assert.throws(() => t(cle), RangeError, cle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ligneAffichee : les résumés en attente (§ 8.1)', () => {
|
||||
test('sans résumé reçu, la ligne est en attente et ses quatre valeurs de résumé ne disent rien ; reçu, elle ne l’est plus', () => {
|
||||
test('sans résumé reçu, la ligne est en attente et ses deux valeurs de résumé ne disent rien ; reçu, elle ne l’est plus', () => {
|
||||
const attente = ligneAffichee(ligne(), format);
|
||||
assert.equal(attente.enAttente, true);
|
||||
assert.equal(attente.collisionsCumulees, VIDE);
|
||||
assert.equal(attente.diffRedondance, VIDE);
|
||||
assert.deepEqual(attente.meilleure, { id: VIDE, collisions: VIDE });
|
||||
assert.deepEqual(attente.meilleure, { id: VIDE });
|
||||
assert.equal(ligneAffichee(ligne(), format, AUCUN).enAttente, false);
|
||||
const recu = ligneAffichee(ligne(), format, {
|
||||
mesuresDuRetenu: { collisionsCumulees: 12, totalRedondance: 990, cibleRedondance: 1000, diffRedondance: 10 },
|
||||
meilleureDuClassement: { id: 3, collisionsCumulees: 9 },
|
||||
});
|
||||
assert.equal(recu.enAttente, false);
|
||||
assert.equal(recu.collisionsCumulees, format.nombre(12));
|
||||
assert.deepEqual(recu.meilleure, { id: format.nombre(3), collisions: format.nombre(9) });
|
||||
assert.equal(recu.diffRedondance, format.nombre(10));
|
||||
assert.deepEqual(recu.meilleure, { id: format.nombre(3) });
|
||||
});
|
||||
|
||||
test('les six informations ne dépendent pas des résumés : en attente ou non, elles sont les mêmes', () => {
|
||||
const sans = ({ enAttente, collisionsCumulees, diffRedondance, meilleure, ...reste }) => reste;
|
||||
const sans = ({ enAttente, diffRedondance, meilleure, ...reste }) => reste;
|
||||
assert.deepEqual(sans(ligneAffichee(ligne(), format)), sans(ligneAffichee(ligne(), format, AUCUN)));
|
||||
});
|
||||
});
|
||||
|
|
@ -405,29 +401,25 @@ describe('ligneAffichee : la meilleure proposition du classement (§ 5.4, § 5.7
|
|||
return executer(charge, 'enregistrerGeneration', { propositions, configuration, produitVersion: VERSION.affichee });
|
||||
}
|
||||
|
||||
test("l'identifiant et les collisions cumulées que rend la façade, mis en forme ; aucun ne se calcule ici", () => {
|
||||
test("l'identifiant que rend la façade, mis en forme, sans calcul ici ; ses collisions cumulées ne se montrent pas", () => {
|
||||
const affichee = ligneAffichee(ligne(), format, { ...AUCUN, meilleureDuClassement: { id: 1234, collisionsCumulees: 5678 } });
|
||||
assert.deepEqual(affichee.meilleure, { id: format.nombre(1234), collisions: format.nombre(5678) });
|
||||
assert.deepEqual(affichee.meilleure, { id: format.nombre(1234) });
|
||||
});
|
||||
|
||||
test('sans proposition classée, et pour une ligne illisible, les deux valent « — »', () => {
|
||||
test('sans proposition classée, et pour une ligne illisible, l’identifiant vaut « — »', () => {
|
||||
for (const champs of [{}, { nom: null, etat: null, participants: null, illisible: { raison: 'VIDE', chemin: null } }]) {
|
||||
assert.deepEqual(ligneAffichee(ligne(champs), format, AUCUN).meilleure, { id: VIDE, collisions: VIDE });
|
||||
assert.deepEqual(ligneAffichee(ligne(champs), format, AUCUN).meilleure, { id: VIDE });
|
||||
}
|
||||
});
|
||||
|
||||
test('MEILLEURE : les libellés du bloc de la génération, son explication des collisions, et l’explication du titre, qui existent', () => {
|
||||
const colonne = COLONNES.find(({ nom }) => nom === COLONNE_ANNONCEE);
|
||||
test('MEILLEURE : le titre et le libellé de l’identifiant du bloc de la génération, et l’explication du titre, qui existent', () => {
|
||||
assert.ok(Object.isFrozen(MEILLEURE));
|
||||
assert.deepEqual(MEILLEURE, {
|
||||
titre: 'generation.meilleure.titre',
|
||||
explication: 'accueil.explication.meilleure',
|
||||
proposition: 'generation.meilleure.proposition',
|
||||
collisions: colonne.cle,
|
||||
explicationCollisions: colonne.explication,
|
||||
});
|
||||
assert.equal(t(MEILLEURE.titre), 'Meilleure proposition du classement');
|
||||
assert.equal(t(MEILLEURE.collisions), 'Collisions cumulées (paire et tour)');
|
||||
for (const cle of Object.values(MEILLEURE)) assert.ok(t(cle).length > 0, cle);
|
||||
});
|
||||
|
||||
|
|
@ -441,11 +433,11 @@ describe('ligneAffichee : la meilleure proposition du classement (§ 5.4, § 5.7
|
|||
const affichee = ligneAffichee(ligne(), format, { ...AUCUN, meilleureDuClassement: resumeDuMeilleur(charge) });
|
||||
const bloc = meilleureProposition(charge);
|
||||
if (bloc === null) {
|
||||
assert.deepEqual(affichee.meilleure, { id: VIDE, collisions: VIDE });
|
||||
assert.deepEqual(affichee.meilleure, { id: VIDE });
|
||||
continue;
|
||||
}
|
||||
classees += 1;
|
||||
assert.deepEqual(affichee.meilleure, { id: format.nombre(bloc.id), collisions: texteCellule(bloc.cellule, { t, format }) });
|
||||
assert.deepEqual(affichee.meilleure, { id: format.nombre(bloc.id) });
|
||||
}
|
||||
assert.equal(classees, 2);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,30 +3,26 @@
|
|||
|
||||
<script>
|
||||
// La bande de mode (§ 8.4) : sous le bandeau, de bord à bord de la
|
||||
// fenêtre, l'icône du mode, son libellé — celui du cadre et du
|
||||
// bandeau — en gras, puis la phrase qui dit ce que le mode permet et la
|
||||
// commande qui en sort (bandeDeMode, modele.js). Elle s'ajoute au cadre de
|
||||
// mode, qui reste autour de la surface de travail, et ne le remplace pas.
|
||||
// fenêtre, l'icône du mode, son libellé — celui du bandeau et de la barre
|
||||
// de l'événement — en gras, puis la phrase qui dit ce que le mode permet
|
||||
// et la commande qui en sort (bandeDeMode, modele.js). Avec ces deux
|
||||
// libellés, elle porte seule le mode : aucun trait n'entoure la surface de
|
||||
// travail.
|
||||
//
|
||||
// Son fond et son filet sont ceux du mode, son icône de la teinte du
|
||||
// cadre, son texte de l'encre de la page (marques.js : bandeMode et les
|
||||
// signes bandeLecture, bandeEcriture, bandeBloque). Région d'état, elle
|
||||
// annonce le mode quand il change.
|
||||
// Son fond et son filet sont ceux du mode, son icône de la teinte du mode
|
||||
// (--couleur-cadre-…), son texte de l'encre de la page (marques.js :
|
||||
// bandeMode et les signes bandeLecture, bandeEcriture, bandeBloque).
|
||||
// Région d'état, elle annonce le mode quand il change.
|
||||
//
|
||||
// Sa marge haute négative vaut le retrait de l'écran (App.svelte) : la
|
||||
// bande touche le bandeau. Ses marges latérales négatives valent la
|
||||
// distance du contenu de la colonne au bord de la fenêtre : le retrait de
|
||||
// l'écran tant que la fenêtre ne dépasse pas --largeur-page, ce retrait
|
||||
// plus la moitié de l'excédent au-delà, où la colonne se centre. La bande
|
||||
// va donc d'un bord à l'autre de la fenêtre à toute largeur, et son retrait
|
||||
// intérieur, de même valeur, aligne son texte sur la colonne. 100vw égale
|
||||
// la largeur utile de la fenêtre parce que l'écran de l'événement tient
|
||||
// dans sa hauteur (Evenement.svelte) : aucune barre de défilement
|
||||
// verticale n'en retranche, et la page ne défile pas en largeur. Elle tient en
|
||||
// une ligne aux largeurs usuelles, de même hauteur dans les trois modes :
|
||||
// changer de mode ne déplace pas la place fixe du cadre, sous elle. Mince
|
||||
// — une ligne de petit corps —, elle prend au plan à peine plus que le
|
||||
// retrait de l'écran qu'elle remplace.
|
||||
// Ses marges négatives valent le retrait de l'écran (App.svelte), que
|
||||
// l'écran de l'événement garde à toute largeur de fenêtre sans être borné
|
||||
// par --largeur-page : la bande touche le bandeau et va d'un bord à
|
||||
// l'autre de la fenêtre, et son retrait intérieur, de même valeur, aligne
|
||||
// son texte sur l'écran. Elle tient en une ligne aux largeurs usuelles, de
|
||||
// même hauteur dans les trois modes : changer de mode ne déplace pas la
|
||||
// place fixe de la barre, sous elle. Mince — une ligne de petit corps —,
|
||||
// elle prend au plan à peine plus que le retrait de l'écran qu'elle
|
||||
// remplace.
|
||||
import { getContext } from 'svelte';
|
||||
import Icone from '../primitives/Icone.svelte';
|
||||
import { bandeDeMode } from './modele.js';
|
||||
|
|
@ -52,8 +48,8 @@
|
|||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--espace-1) var(--espace-2);
|
||||
margin: calc(-1 * var(--espace-4)) calc(-1 * max(var(--espace-4), (100vw - var(--largeur-page)) / 2 + var(--espace-4))) 0;
|
||||
padding: 0.0625rem max(var(--espace-4), (100vw - var(--largeur-page)) / 2 + var(--espace-4));
|
||||
margin: calc(-1 * var(--espace-4)) calc(-1 * var(--espace-4)) 0;
|
||||
padding: 0.0625rem var(--espace-4);
|
||||
border-bottom: 1px solid var(--couleur-mode-lecture-filet);
|
||||
background-color: var(--couleur-mode-lecture-fond);
|
||||
color: var(--couleur-page-texte);
|
||||
|
|
|
|||
|
|
@ -2,33 +2,34 @@
|
|||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
|
||||
|
||||
<script>
|
||||
// Le cadre de mode (§ 8.4, § 8.5, § 14.5) : un trait autour de la surface
|
||||
// de travail, dont la forme dit le mode — tirets en lecture, plein en
|
||||
// écriture, double sur un plan bloqué —, et une barre qui porte, de gauche
|
||||
// à droite, la place fixe, le libellé de mode, puis « Défaire » et
|
||||
// « Refaire » (§ 8.3), puis les outils, outils — la suite des états du
|
||||
// plan —, et les actions, actions — « Imprimer… », calée à droite —, quand
|
||||
// ils sont donnés. Outils et actions suivent la place fixe et ne la
|
||||
// déplacent donc jamais : un refus posé à côté de la place fixe les
|
||||
// repousse sur la ligne suivante, à côté du libellé, sans ligne de plus.
|
||||
// Une barre de moins de 80rem — la fenêtre de 1 366 px lui en donne
|
||||
// davantage — passe les outils sous les commandes et les actions, sur une
|
||||
// ligne à eux : la barre garde deux lignes là où, dans l'ordre du
|
||||
// document, elle en prendrait trois.
|
||||
// La barre, la tête et la surface de travail de l'événement (§ 8.4, § 8.5,
|
||||
// § 14.5), sans trait autour d'elles : le mode se lit dans la bande de
|
||||
// mode (BandeMode.svelte), dans le libellé de mode du bandeau, et dans
|
||||
// celui de la barre, à côté de la place fixe. data-mode porte le mode —
|
||||
// lecture, ecriture ou bloque. La barre porte, de gauche à droite, la
|
||||
// place fixe, le libellé de mode, puis « Défaire » et « Refaire » (§ 8.3),
|
||||
// puis les outils, outils — la suite des états du plan —, et les actions,
|
||||
// actions — « Imprimer… », calée à droite —, quand ils sont donnés.
|
||||
// Outils et actions suivent la place fixe et ne la déplacent donc jamais :
|
||||
// un refus posé à côté de la place fixe les repousse sur la ligne
|
||||
// suivante, à côté du libellé, sans ligne de plus. Une barre de moins de
|
||||
// 80rem — la fenêtre de 1 366 px lui en donne davantage — passe les outils
|
||||
// sous les commandes et les actions, sur une ligne à eux : la barre garde
|
||||
// deux lignes là où, dans l'ordre du document, elle en prendrait trois.
|
||||
//
|
||||
// Sous la barre vient la tête, tete, quand elle est donnée — l'en-tête de
|
||||
// l'événement et la navigation des sections —, puis les annonces,
|
||||
// annonces, quand il y en a — support rompu et avertissements —, puis la
|
||||
// surface, children. La barre ouvre le cadre, et seule la bande de mode,
|
||||
// de hauteur constante, le précède dans l'écran de l'événement : un refus
|
||||
// posé dans la barre ou la tête, ou un nom qui s'étend sur plusieurs
|
||||
// surface, children. La barre ouvre le composant, et seule la bande de
|
||||
// mode, de hauteur constante, le précède dans l'écran de l'événement : un
|
||||
// refus posé dans la barre ou la tête, ou un nom qui s'étend sur plusieurs
|
||||
// lignes, allonge la barre ou la tête sans déplacer la place fixe
|
||||
// (§ 8.4). Tête, annonces et surface défilent
|
||||
// chacune dans sa zone : des avertissements nombreux ne poussent pas la
|
||||
// surface hors du cadre, ni le cadre hors de la fenêtre. La surface porte
|
||||
// le plan, que l'opérateur regarde : elle réclame les trois quarts du
|
||||
// cadre avant que les annonces ne dépassent leur première ligne, sauf
|
||||
// devant un support rompu, dont les issues restent en vue.
|
||||
// (§ 8.4). Tête, annonces et surface défilent chacune dans sa zone : des
|
||||
// avertissements nombreux ne poussent pas la surface hors du composant, ni
|
||||
// le composant hors de la fenêtre. La surface porte le plan, que
|
||||
// l'opérateur regarde : elle réclame les trois quarts du composant avant
|
||||
// que les annonces ne dépassent leur première ligne, sauf devant un
|
||||
// support rompu, dont les issues restent en vue.
|
||||
//
|
||||
// Sous une tête, et hors d'un support rompu, les annonces se posent à
|
||||
// côté d'elle, dans la place que sa navigation laisse à droite. Leur zone
|
||||
|
|
@ -69,26 +70,23 @@
|
|||
//
|
||||
// La barre et la tête forment la carte d'en-tête de l'événement : la
|
||||
// barre sur le fond sourd de la carte, la tête sur son fond, séparées et
|
||||
// closes par le filet de la carte. Le cadre, lui, reste sur le fond de la
|
||||
// page, que son trait contraste (marques.js).
|
||||
// closes par le filet de la carte. La surface reste sur le fond de la
|
||||
// page.
|
||||
import { getContext } from 'svelte';
|
||||
import Commande from '../primitives/Commande.svelte';
|
||||
import { CONFIRMATION_DEBLOCAGE } from '../primitives/commande.js';
|
||||
import Refus from '../primitives/Refus.svelte';
|
||||
import { cadreDeMode, commandeFixe, repriseOfferte } from './modele.js';
|
||||
import { bandeDeMode, commandeFixe, repriseOfferte } from './modele.js';
|
||||
|
||||
let { children, tete = null, annonces = null, outils = null, actions = null } = $props();
|
||||
|
||||
const t = getContext('t');
|
||||
const seance = getContext('seance');
|
||||
|
||||
// Sans événement — l'instant où l'écran se retire —, le cadre garde la
|
||||
// forme de la lecture, et n'a pas de libellé.
|
||||
const cadre = $derived(cadreDeMode($seance));
|
||||
const forme = $derived(cadre?.forme ?? 'tirets');
|
||||
// Le trait du cadre est une marque (§ 14.5) : data-marque nomme l'entrée
|
||||
// du registre (marques.js) qui porte le jeton de sa couleur.
|
||||
const MARQUE_DE_LA_FORME = { tirets: 'cadreLecture', plein: 'cadreEcriture', double: 'cadreBloque' };
|
||||
// Le mode et la clé de son libellé, ceux de la bande. Sans événement —
|
||||
// l'instant où l'écran se retire —, le mode est la lecture, sans libellé.
|
||||
const libelle = $derived(bandeDeMode($seance));
|
||||
const mode = $derived(libelle?.mode ?? 'lecture');
|
||||
const commande = $derived(commandeFixe($seance));
|
||||
const verrou = $derived($seance.mode === 'lecture' ? $seance.verrouAutre : null);
|
||||
const reprise = $derived(repriseOfferte($seance));
|
||||
|
|
@ -96,7 +94,7 @@
|
|||
const aCote = $derived(tete !== null && annonces !== null && $seance.supportRompu === null);
|
||||
</script>
|
||||
|
||||
<div class="cadre" data-mode={forme} data-marque={MARQUE_DE_LA_FORME[forme]} class:tirets={forme === 'tirets'} class:double={forme === 'double'}>
|
||||
<div class="corps" data-mode={mode}>
|
||||
<div class="barre" role="group" aria-label={t('evenement.cadre.commandes')}>
|
||||
<div class="ligne">
|
||||
<div class="fixe" data-place-fixe>
|
||||
|
|
@ -115,10 +113,10 @@
|
|||
<Commande cle="communs.commande.lecture" nom="lecture" executer={() => seance.passerEnLecture()} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if cadre !== null}
|
||||
{#if libelle !== null}
|
||||
<span class="porte-libelle">
|
||||
<span class="libelle" class:plein={forme === 'plein'} class:double={forme === 'double'} data-mode-libelle="cadre"
|
||||
>{t(cadre.cle)}</span
|
||||
<span class="libelle" class:ecriture={mode === 'ecriture'} class:bloque={mode === 'bloque'} data-mode-libelle="cadre"
|
||||
>{t(libelle.cle)}</span
|
||||
>
|
||||
</span>
|
||||
{/if}
|
||||
|
|
@ -187,29 +185,16 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
/* Le trait du cadre : sa forme dit le mode, sa couleur le redit. */
|
||||
/* Une même épaisseur dans les trois formes : la place fixe ne bouge pas
|
||||
d'un mode à l'autre. */
|
||||
.cadre {
|
||||
/* Sans trait : rien autour de la surface ne change d'un mode à l'autre,
|
||||
et la place fixe ne bouge pas. */
|
||||
.corps {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
border: 6px solid var(--couleur-cadre-ecriture);
|
||||
border-radius: var(--rayon-2);
|
||||
}
|
||||
|
||||
.cadre.tirets {
|
||||
border-style: dashed;
|
||||
border-color: var(--couleur-cadre-lecture);
|
||||
}
|
||||
|
||||
.cadre.double {
|
||||
border-style: double;
|
||||
border-color: var(--couleur-cadre-bloque);
|
||||
}
|
||||
|
||||
/* La barre ne prend jamais toute la hauteur du cadre : au-delà, ses
|
||||
/* La barre ne prend jamais toute la hauteur du composant : au-delà, ses
|
||||
messages défilent, la place fixe en tête. */
|
||||
.barre {
|
||||
display: flex;
|
||||
|
|
@ -284,12 +269,12 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.libelle.plein {
|
||||
.libelle.ecriture {
|
||||
border-color: var(--couleur-mode-ecriture-filet);
|
||||
background-color: var(--couleur-mode-ecriture-fond);
|
||||
}
|
||||
|
||||
.libelle.double {
|
||||
.libelle.bloque {
|
||||
border-color: var(--couleur-mode-bloque-filet);
|
||||
background-color: var(--couleur-mode-bloque-fond);
|
||||
}
|
||||
|
|
@ -320,7 +305,7 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* La tête ne prend jamais plus de 35 % du cadre : au-delà, elle défile,
|
||||
/* La tête ne prend jamais plus de 35 % du composant : au-delà, elle défile,
|
||||
et la surface garde sa place. Elle n'a pas de retrait en bas : le trait
|
||||
des onglets de la navigation se pose sur son filet. */
|
||||
.tete {
|
||||
|
|
@ -397,7 +382,7 @@
|
|||
retrait compris : 7,25rem tiennent entier, dans la place de 36 % d'une
|
||||
fenêtre de 1 366 px, un avertissement de trois lignes de texte et son
|
||||
action dessous (Avertissements.svelte). Au-delà, la zone défile, et la
|
||||
tête, qui ne grandit pas plus, laisse le reste du cadre à la surface. */
|
||||
tête, qui ne grandit pas plus, laisse le reste du composant à la surface. */
|
||||
.place > .annonces {
|
||||
box-sizing: border-box;
|
||||
max-height: 7.25rem;
|
||||
|
|
@ -416,7 +401,7 @@
|
|||
}
|
||||
|
||||
/* Seules — sans tête, en plein écran, ou devant un support rompu —, les
|
||||
annonces ne prennent jamais plus du quart du cadre, et cèdent d'abord
|
||||
annonces ne prennent jamais plus du quart du composant, et cèdent d'abord
|
||||
leur hauteur à la surface : leur facteur de rétrécissement, mille fois
|
||||
celui de la surface, leur fait absorber presque tout le manque de
|
||||
place, jusqu'à leur hauteur minimale : un avertissement d'une ligne en
|
||||
|
|
@ -430,13 +415,13 @@
|
|||
|
||||
/* Le support rompu, en tête des annonces, porte les seules issues de
|
||||
l'opérateur : tant qu'il est là, les annonces ne cèdent rien à la
|
||||
surface, et gardent leur hauteur jusqu'au quart du cadre ; les
|
||||
surface, et gardent leur hauteur jusqu'au quart du composant ; les
|
||||
avertissements qui le suivent défilent sous lui. */
|
||||
.annonces.rompu {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* La surface part des trois quarts du cadre, s'étend à ce qui reste, et
|
||||
/* La surface part des trois quarts du composant, s'étend à ce qui reste, et
|
||||
ne rétrécit, après les annonces, que pour la barre et la tête. Sa
|
||||
hauteur ne dépend pas de son contenu : elle est un conteneur de taille,
|
||||
dont une section borne un panneau à la hauteur visible (unités cqb). */
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
|
||||
|
||||
<script>
|
||||
// L'en-tête de l'événement ouvert, en tête de la tête du cadre : son nom,
|
||||
// puis, sur la même ligne tant qu'elle a la place, son résumé — la date
|
||||
// L'en-tête de l'événement ouvert, en tête de la tête (CadreMode.svelte) :
|
||||
// son nom, puis, sur la même ligne tant qu'elle a la place, son résumé — la date
|
||||
// quand il en a une, les participants, les tables et leurs sièges, les
|
||||
// tours (resumeEvenement, modele.js) —, d'encre secondaire. Le nom se
|
||||
// coupe où il le faut, même sans espace : un nom long de glyphes larges
|
||||
|
|
|
|||
|
|
@ -146,11 +146,11 @@ async function enManqueDe(m, manque) {
|
|||
|
||||
// --- Les épreuves -----------------------------------------------------------------
|
||||
|
||||
describe('la bande de mode (§ 8.4)', () => {
|
||||
// 1 920 px dépasse --largeur-page : la colonne de la page n'y touche plus
|
||||
// les bords de la fenêtre, la bande si.
|
||||
describe('la bande de mode et la pleine fenêtre (§ 8.4)', () => {
|
||||
// 1 920 px dépasse --largeur-page, qui borne la liste des événements :
|
||||
// l'écran de l'événement n'y est pas borné.
|
||||
for (const largeur of [1366, 1920]) {
|
||||
test(`à ${largeur} px, sous le bandeau, de bord à bord de la fenêtre sans défilement en largeur, avant le cadre : l’icône, le libellé du cadre et son explication, aux couleurs de chaque mode, à la même hauteur`, async () => {
|
||||
test(`à ${largeur} px : la bande de bord à bord sous le bandeau, aux couleurs de chaque mode, à hauteur constante ; l’écran en pleine fenêtre, sans trait`, async () => {
|
||||
await fixerCadre(largeur, 700);
|
||||
const m = await neuf();
|
||||
const hauteurs = [];
|
||||
|
|
@ -163,6 +163,20 @@ describe('la bande de mode (§ 8.4)', () => {
|
|||
expect([b.left, b.right], mode).toEqual([0, innerWidth]);
|
||||
expect(document.documentElement.scrollWidth, mode).toBeLessThanOrEqual(innerWidth);
|
||||
expect(b.bottom, mode).toBeLessThanOrEqual(boite(cadre(m)).top);
|
||||
// L'écran va d'un bord à l'autre de la fenêtre, au retrait de
|
||||
// l'écran près, et la barre, la tête et la surface avec lui, sans
|
||||
// trait autour d'elles.
|
||||
const retrait = Number.parseFloat(style(ecran(m).parentElement).paddingLeft);
|
||||
expect(retrait, mode).toBeGreaterThan(0);
|
||||
expect(retrait, mode).toBeLessThanOrEqual(32);
|
||||
for (const element of [ecran(m), cadre(m)]) {
|
||||
const e = boite(element);
|
||||
expect(e.left, mode).toBeCloseTo(retrait, 0);
|
||||
expect(e.right, mode).toBeCloseTo(innerWidth - retrait, 0);
|
||||
}
|
||||
for (const cote of ['Top', 'Right', 'Bottom', 'Left']) {
|
||||
expect(Number.parseFloat(style(cadre(m))[`border${cote}Width`]), `${mode} ${cote}`).toBe(0);
|
||||
}
|
||||
expect(region.textContent, mode).toContain(t(cle));
|
||||
expect(region.textContent, mode).toContain(t(explication));
|
||||
expect(region.textContent, mode).toContain(dans(m, '[data-mode-libelle="cadre"]').textContent);
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
|
||||
|
||||
<script>
|
||||
// La suite des états du plan (§ 9), dans la barre du cadre : sous le nom
|
||||
// « État du plan », que redit un intertitre en petites capitales tant que
|
||||
// la barre a la place, les
|
||||
// quatre états dans l'ordre du § 9 (etapesEtat, modele.js), séparés de
|
||||
// chevrons cachés à l'assistance. Chaque étape est la Commande
|
||||
// changerEtat de son état, restylée en pilule, avec l'icône de sa pastille
|
||||
// d'état posée sur le bouton, à gauche de son nom, de la marque de sa
|
||||
// teinte. L'état courant porte en plus le fond de sa teinte, un filet
|
||||
// La suite des états du plan (§ 9), dans la barre de l'événement : sous
|
||||
// le nom « État du plan », que redit un intertitre en petites capitales
|
||||
// tant que la barre a la place, les quatre états dans l'ordre du § 9
|
||||
// (etapesEtat, modele.js), séparés de chevrons cachés à l'assistance.
|
||||
// Chaque étape est la Commande changerEtat de son état, restylée en
|
||||
// pilule, avec l'icône de sa pastille d'état posée sur le bouton, à
|
||||
// gauche de son nom, de la marque de sa teinte. L'état courant porte en plus le fond de sa teinte, un filet
|
||||
// plein et le gras ; les autres, un filet tireté, et restent cliquables
|
||||
// sous le même nom.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
// L'événement ouvert dans un vrai moteur de rendu (§ 2.1, § 8.3 à § 8.9,
|
||||
// § 9, § 14.5) : aucune section n'en exige une autre ; la place fixe de
|
||||
// « Modifier », de « Débloquer » et de « Revenir en lecture », plein écran
|
||||
// compris ; le cadre de mode et son libellé ; l'inactivité ; l'état bloqué
|
||||
// compris ; la surface de travail sans cadre, le mode porté par la bande et
|
||||
// le libellé ; l'inactivité ; l'état bloqué
|
||||
// et son déblocage confirmé en place ; les états du plan ; défaire et
|
||||
// refaire ; les annonces d'ouverture, du journal et de la dérive ; le verrou
|
||||
// d'une autre séance et sa reprise ; le support rompu ; le format plus
|
||||
|
|
@ -13,7 +14,7 @@
|
|||
// qu'atteint un point, le style calculé, le texte — jamais une classe.
|
||||
//
|
||||
// Les éléments s'atteignent par leurs attributs data-… : data-place-fixe, la
|
||||
// place fixe du cadre ; data-etat, un état du plan de l'en-tête ;
|
||||
// place fixe de la barre ; data-etat, un état du plan de l'en-tête ;
|
||||
// data-verrou, le verrou d'une autre séance ; data-support-rompu, le message
|
||||
// du support rompu. Le texte attendu vient de la même traduction que celle
|
||||
// de l'application ; les noms d'épreuve sont inventés.
|
||||
|
|
@ -67,6 +68,7 @@ afterEach(() => {
|
|||
const dans = (m, selecteur) => m.racine.querySelector(selecteur);
|
||||
const tous = (m, selecteur) => [...m.racine.querySelectorAll(selecteur)];
|
||||
const cadre = (m) => dans(m, '[data-mode]');
|
||||
const bande = (m) => dans(m, '[data-ecran="evenement"] > [role="status"]');
|
||||
const fixe = (m, nom) => dans(m, `[data-place-fixe] [data-commande="${nom}"]`);
|
||||
const historique = (m, nom) => dans(m, `[data-mode] [data-historique] [data-commande="${nom}"]`);
|
||||
const boutonEtat = (m, etat) => dans(m, `[data-etat="${etat}"] [data-commande="etat"]`);
|
||||
|
|
@ -327,39 +329,39 @@ describe('passer en écriture (§ 8.4, § 19.3 01b)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('le cadre de mode (§ 8.4, § 14.5)', () => {
|
||||
test('il contient la surface, sa bordure n’est pas nulle, son libellé se voit ; texte et trait diffèrent en lecture, en écriture et bloqué ; même boîte après défilement', async () => {
|
||||
describe('le mode sans cadre (§ 8.4, § 14.5)', () => {
|
||||
test('aucun trait autour de la surface ; le mode se lit dans la bande, le libellé de la barre et le bandeau, distincts en lecture, en écriture et bloqué ; même boîte après défilement', async () => {
|
||||
const m = await neuf();
|
||||
const releves = [];
|
||||
const relever = () => {
|
||||
const relever = (mode) => {
|
||||
flushSync();
|
||||
const boite = cadre(m).getBoundingClientRect();
|
||||
const surface = dans(m, '[data-section]').getBoundingClientRect();
|
||||
expect(boite.left).toBeLessThanOrEqual(surface.left);
|
||||
expect(boite.top).toBeLessThanOrEqual(surface.top);
|
||||
expect(boite.right).toBeGreaterThanOrEqual(surface.right);
|
||||
expect(boite.bottom).toBeGreaterThanOrEqual(surface.bottom);
|
||||
expect(boite.left, mode).toBeLessThanOrEqual(surface.left);
|
||||
expect(boite.top, mode).toBeLessThanOrEqual(surface.top);
|
||||
expect(boite.right, mode).toBeGreaterThanOrEqual(surface.right);
|
||||
expect(boite.bottom, mode).toBeGreaterThanOrEqual(surface.bottom);
|
||||
expect(cadre(m).dataset.mode, mode).toBe(mode);
|
||||
expect(cadre(m).hasAttribute('data-marque'), mode).toBe(false);
|
||||
const style = getComputedStyle(cadre(m));
|
||||
expect(Number.parseFloat(style.borderTopWidth)).toBeGreaterThan(0);
|
||||
expect(style.borderTopStyle).not.toBe('none');
|
||||
for (const cote of ['Top', 'Right', 'Bottom', 'Left']) {
|
||||
expect(Number.parseFloat(style[`border${cote}Width`]), `${mode} ${cote}`).toBe(0);
|
||||
}
|
||||
const libelle = libelleCadre(m);
|
||||
expect(libelle.checkVisibility()).toBe(true);
|
||||
expect(visible(libelle)).toBe(true);
|
||||
expect(libelle.contains(auCentre(libelle))).toBe(true);
|
||||
expect(libelle.textContent).toBe(libelleBandeau(m).textContent);
|
||||
releves.push({ texte: libelle.textContent, trait: style.borderTopStyle });
|
||||
expect(visible(bande(m)), mode).toBe(true);
|
||||
expect(bande(m).textContent, mode).toContain(libelle.textContent);
|
||||
releves.push(libelle.textContent);
|
||||
};
|
||||
relever();
|
||||
relever('lecture');
|
||||
await m.seance.passerEnEcriture();
|
||||
relever();
|
||||
relever('ecriture');
|
||||
await m.seance.executer('changerEtat', { etat: 'bloque' });
|
||||
relever();
|
||||
expect(releves.map(({ texte }) => texte)).toEqual([
|
||||
t('coquille.mode.lecture'),
|
||||
t('coquille.mode.ecriture'),
|
||||
t('coquille.mode.bloque'),
|
||||
]);
|
||||
expect(new Set(releves.map(({ trait }) => trait)).size).toBe(3);
|
||||
relever('bloque');
|
||||
expect(releves).toEqual([t('coquille.mode.lecture'), t('coquille.mode.ecriture'), t('coquille.mode.bloque')]);
|
||||
// Une surface plus haute que la fenêtre : la page et la surface
|
||||
// défilées jusqu'en bas, le libellé garde sa boîte.
|
||||
const avant = libelleCadre(m).getBoundingClientRect().toJSON();
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@
|
|||
|
||||
<script>
|
||||
// L'événement ouvert (§ 2.1, § 8.4) : la bande de mode, sous le bandeau,
|
||||
// puis le cadre de mode, qui entoure la surface de travail de toute
|
||||
// section. Sa barre porte la place fixe, le libellé, Défaire et Refaire,
|
||||
// puis les outils — la suite des états du plan — et les actions —
|
||||
// « Imprimer… », l'action principale de l'en-tête. Sous la barre, la tête
|
||||
// de l'événement : son en-tête — nom et résumé — et la navigation des
|
||||
// sections, dans l'ordre du § 2.1. La barre et la tête forment la carte
|
||||
// d'en-tête. Seule la bande précède le cadre dans l'écran, à hauteur
|
||||
// constante : ce que la tête montre ne déplace pas la place fixe. Aucune
|
||||
// section n'en exige une autre : la navigation les offre toutes, et la
|
||||
// séance refuse elle-même ce qui ne peut pas se faire. Le plein écran du
|
||||
// plan efface les outils, les actions et la tête, jamais la bande, le
|
||||
// cadre ni sa place fixe.
|
||||
// puis la barre, la tête et la surface de travail de toute section
|
||||
// (CadreMode.svelte), sans trait autour d'elles. La barre porte la place
|
||||
// fixe, le libellé de mode, Défaire et Refaire, puis les outils — la
|
||||
// suite des états du plan — et les actions — « Imprimer… », l'action
|
||||
// principale de l'en-tête. Sous la barre, la tête de l'événement : son
|
||||
// en-tête — nom et résumé — et la navigation des sections, dans l'ordre
|
||||
// du § 2.1. La barre et la tête forment la carte d'en-tête. Seule la
|
||||
// bande précède la barre dans l'écran, à hauteur constante : ce que la
|
||||
// tête montre ne déplace pas la place fixe. Aucune section n'en exige une
|
||||
// autre : la navigation les offre toutes, et la séance refuse elle-même
|
||||
// ce qui ne peut pas se faire. Le plein écran du plan efface les outils,
|
||||
// les actions et la tête, jamais la bande, la barre ni sa place fixe.
|
||||
//
|
||||
// « Imprimer… » ouvre l'aperçu avant impression (§ 11.7) sur le plan, au
|
||||
// tour montré : toute impression passe par lui. Vivante dans tout mode,
|
||||
|
|
@ -29,18 +29,21 @@
|
|||
// de l'action.
|
||||
//
|
||||
// L'écran de l'événement tient dans la fenêtre, sous le bandeau : la page
|
||||
// ne défile pas, la surface de travail défile dans le cadre, et le cadre,
|
||||
// son libellé et sa place fixe restent où ils sont (§ 8.4). La règle
|
||||
// globale ci-dessous donne pour cela une hauteur à l'ascendance de l'écran
|
||||
// — le conteneur de l'application et son <main> — tant qu'il porte cet
|
||||
// écran, et à lui seul.
|
||||
// ne défile pas, la surface de travail défile dans sa zone, et la barre,
|
||||
// son libellé et sa place fixe restent où ils sont (§ 8.4). Il prend
|
||||
// aussi toute la largeur de la fenêtre, au retrait de l'écran près, sans
|
||||
// la borne --largeur-page de la liste des événements. Les règles
|
||||
// globales ci-dessous donnent pour cela une hauteur à l'ascendance de
|
||||
// l'écran — le conteneur de l'application et son <main> — et retirent la
|
||||
// borne de largeur de son <main>, tant qu'il porte cet écran, et à lui
|
||||
// seul.
|
||||
//
|
||||
// Le support rompu et les avertissements se posent dans la zone des
|
||||
// annonces du cadre, qui défile seule au-delà de son plafond, et non
|
||||
// au-dessus de la section : la section ne cède à la zone que ce dont elle
|
||||
// dépasse la tête, quel que soit le nombre des avertissements, et le
|
||||
// support rompu, en tête de la zone, garde ses issues en vue
|
||||
// (CadreMode.svelte).
|
||||
// annonces, à côté de la tête ou sous elle, qui défile seule au-delà de
|
||||
// son plafond, et non au-dessus de la section : la section ne cède à la
|
||||
// zone que ce dont elle dépasse la tête, quel que soit le nombre des
|
||||
// avertissements, et le support rompu, en tête de la zone, garde ses
|
||||
// issues en vue (CadreMode.svelte).
|
||||
import { getContext } from 'svelte';
|
||||
import SectionBadges from '../badges/SectionBadges.svelte';
|
||||
import Diagnostic from '../diagnostic/Diagnostic.svelte';
|
||||
|
|
@ -79,7 +82,7 @@
|
|||
|
||||
const Section = $derived(COMPOSANTS.get($ui.section));
|
||||
// La zone des annonces n'existe que lorsqu'elle a quelque chose à dire :
|
||||
// sans annonce, le cadre ne lui garde ni hauteur ni trait.
|
||||
// sans annonce, aucune hauteur ni aucun trait ne lui est gardé.
|
||||
const annoncer = $derived($seance.supportRompu !== null || avertissementsOrdonnes($seance).length > 0);
|
||||
</script>
|
||||
|
||||
|
|
@ -155,9 +158,11 @@
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
/* La classe de l'écran (App.svelte) porte la spécificité de son retrait,
|
||||
que cette règle doit dépasser. */
|
||||
/* La classe de l'écran (App.svelte) porte la spécificité de son retrait
|
||||
et de sa borne de largeur, que cette règle doit dépasser : l'écran de
|
||||
l'événement prend la largeur de la fenêtre. */
|
||||
:global(main.ecran:has(> [data-ecran='evenement'])) {
|
||||
max-width: none;
|
||||
padding-bottom: var(--espace-2);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
// Le modèle de l'événement ouvert, pur : ce que ses composants montrent de
|
||||
// l'état observable de la séance (§ 8.4, § 8.5, § 8.8, § 8.9, § 9). Le cadre
|
||||
// de mode et sa forme, la commande de la place fixe, les états du plan et
|
||||
// l'état observable de la séance (§ 8.4, § 8.5, § 8.8, § 8.9, § 9). La
|
||||
// commande de la place fixe, les états du plan et
|
||||
// leurs clés, la reprise de verrou que l'écran offre, et les avertissements
|
||||
// dans l'ordre de la séance, chacun avec son remède ; puis ce que montre
|
||||
// l'enveloppe de l'événement : le résumé chiffré de son en-tête, les étapes
|
||||
|
|
@ -14,29 +14,6 @@ import { cleDeMode } from '../mode.js';
|
|||
import { modelePastille } from '../primitives/pastille.js';
|
||||
import { etatTable } from '../tables/modele.js';
|
||||
|
||||
// La forme du cadre pour chaque clé du libellé de mode : un trait différent
|
||||
// par mode, pour qu'aucun ne se distingue par la seule couleur (§ 14.5).
|
||||
const FORMES = new Map([
|
||||
['coquille.mode.lecture', 'tirets'],
|
||||
['coquille.mode.ecriture', 'plein'],
|
||||
['coquille.mode.bloque', 'double'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Libellé et forme du cadre de mode : cle, celle de cleDeMode (mode.js) — le
|
||||
* libellé du bandeau — ; forme : lecture (tirets), écriture (trait plein),
|
||||
* bloqué (trait double), quel que soit le mode d'un plan bloqué. null sans
|
||||
* événement.
|
||||
*
|
||||
* @param {Object} etatSeance EtatSeance
|
||||
* @returns {null|{cle: string, forme: 'tirets'|'plein'|'double'}}
|
||||
*/
|
||||
export function cadreDeMode(etatSeance) {
|
||||
const cle = cleDeMode(etatSeance);
|
||||
if (cle === null) return null;
|
||||
return { cle, forme: FORMES.get(cle) };
|
||||
}
|
||||
|
||||
/**
|
||||
* La commande de la place fixe (§ 8.4, § 8.5, § 8.8) : 'format' quand le
|
||||
* refus courant est celui d'un format plus récent — l'explication remplace
|
||||
|
|
@ -84,18 +61,20 @@ export function etapesEtat(etatSeance) {
|
|||
return ETATS_PLAN.map((etat) => ({ etat, cle: cleEtat(etat), icone: modelePastille('etat', etat, null).icone, courant: etat === courant }));
|
||||
}
|
||||
|
||||
// Le mode de la bande, son explication et son icône, pour chaque forme du
|
||||
// cadre : la bande dit ce que le cadre dit.
|
||||
// Le mode de la bande, son explication et son icône, pour chaque clé du
|
||||
// libellé de mode : une icône et une phrase différentes par mode, pour
|
||||
// qu'aucun ne se distingue par la seule couleur (§ 14.5).
|
||||
const BANDES = new Map([
|
||||
['tirets', { mode: 'lecture', explication: 'evenement.bande.lecture', icone: 'oeil' }],
|
||||
['plein', { mode: 'ecriture', explication: 'evenement.bande.ecriture', icone: 'stylo' }],
|
||||
['double', { mode: 'bloque', explication: 'evenement.bande.bloque', icone: 'cadenas' }],
|
||||
['coquille.mode.lecture', { mode: 'lecture', explication: 'evenement.bande.lecture', icone: 'oeil' }],
|
||||
['coquille.mode.ecriture', { mode: 'ecriture', explication: 'evenement.bande.ecriture', icone: 'stylo' }],
|
||||
['coquille.mode.bloque', { mode: 'bloque', explication: 'evenement.bande.bloque', icone: 'cadenas' }],
|
||||
]);
|
||||
|
||||
/**
|
||||
* La bande de mode, sous le bandeau (§ 8.4) : mode, 'lecture', 'ecriture' ou
|
||||
* 'bloque' — un plan bloqué l'est quel que soit le mode — ; cle, le libellé
|
||||
* du cadre (cadreDeMode) ; explication, la clé de la phrase qui le suit ;
|
||||
* 'bloque' — un plan bloqué l'est quel que soit le mode — ; cle, celle du
|
||||
* libellé de mode (cleDeMode, mode.js), que le bandeau et la barre de
|
||||
* l'événement montrent aussi ; explication, la clé de la phrase qui le suit ;
|
||||
* icone, celle des primitives qui le précède. La phrase nomme la commande
|
||||
* de la place fixe (commandeFixe) ; devant un format plus récent, qui n'en
|
||||
* offre aucune, plan bloqué compris, elle est evenement.bande.lectureSeule,
|
||||
|
|
@ -105,11 +84,11 @@ const BANDES = new Map([
|
|||
* @returns {null|{mode: 'lecture'|'ecriture'|'bloque', cle: string, explication: string, icone: string}}
|
||||
*/
|
||||
export function bandeDeMode(etatSeance) {
|
||||
const cadre = cadreDeMode(etatSeance);
|
||||
if (cadre === null) return null;
|
||||
const { mode, explication, icone } = BANDES.get(cadre.forme);
|
||||
const cle = cleDeMode(etatSeance);
|
||||
if (cle === null) return null;
|
||||
const { mode, explication, icone } = BANDES.get(cle);
|
||||
const phrase = commandeFixe(etatSeance) === 'format' ? 'evenement.bande.lectureSeule' : explication;
|
||||
return { mode, cle: cadre.cle, explication: phrase, icone };
|
||||
return { mode, cle, explication: phrase, icone };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
// Le modèle de l'événement ouvert (§ 8.4, § 8.5, § 8.8, § 8.9, § 9) : le cadre
|
||||
// de mode et sa forme, la commande de la place fixe, les états du plan et
|
||||
// Le modèle de l'événement ouvert (§ 8.4, § 8.5, § 8.8, § 8.9, § 9) : la
|
||||
// commande de la place fixe, les états du plan et
|
||||
// leurs clés, la reprise de verrou offerte, et les avertissements ordonnés,
|
||||
// chacun avec son remède ; puis l'enveloppe : le résumé chiffré de
|
||||
// l'en-tête et ses textes, les étapes des états, la bande de mode. Chaque
|
||||
|
|
@ -13,11 +13,11 @@ import { describe, test } from '../../../test/lanceur.js';
|
|||
import { TABLES_APPLICATION } from '../../application/libelles.js';
|
||||
import { creerTraduction } from '../../application/traduction.js';
|
||||
import { TABLES_INTERFACE } from '../libelles.js';
|
||||
import { cleDeMode } from '../mode.js';
|
||||
import {
|
||||
ETATS_PLAN,
|
||||
avertissementsOrdonnes,
|
||||
bandeDeMode,
|
||||
cadreDeMode,
|
||||
cleEtat,
|
||||
commandeFixe,
|
||||
etapesEtat,
|
||||
|
|
@ -53,25 +53,6 @@ function etat({
|
|||
};
|
||||
}
|
||||
|
||||
describe('cadreDeMode (§ 8.4, § 14.5)', () => {
|
||||
test('null sans événement', () => {
|
||||
assert.equal(cadreDeMode(etat({ plan: null })), null);
|
||||
});
|
||||
|
||||
test('lecture : tirets ; écriture : trait plein ; bloqué : trait double, quel que soit le mode', () => {
|
||||
assert.deepEqual(cadreDeMode(etat({ mode: 'lecture' })), { cle: 'coquille.mode.lecture', forme: 'tirets' });
|
||||
assert.deepEqual(cadreDeMode(etat({ mode: 'ecriture' })), { cle: 'coquille.mode.ecriture', forme: 'plein' });
|
||||
assert.deepEqual(cadreDeMode(etat({ mode: 'lecture', plan: 'bloque' })), { cle: 'coquille.mode.bloque', forme: 'double' });
|
||||
assert.deepEqual(cadreDeMode(etat({ mode: 'ecriture', plan: 'bloque' })), { cle: 'coquille.mode.bloque', forme: 'double' });
|
||||
});
|
||||
|
||||
test('trois formes distinctes pour trois libellés distincts, chaque libellé traduit', () => {
|
||||
const cadres = [etat(), etat({ mode: 'ecriture' }), etat({ plan: 'bloque' })].map(cadreDeMode);
|
||||
assert.equal(new Set(cadres.map(({ forme }) => forme)).size, 3);
|
||||
assert.equal(new Set(cadres.map(({ cle }) => t(cle))).size, 3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('commandeFixe (§ 8.4, § 8.5, § 8.8)', () => {
|
||||
test('null sans événement ; modifier en lecture ; lecture en écriture ; debloquer sur un plan bloqué', () => {
|
||||
assert.equal(commandeFixe(etat({ plan: null })), null);
|
||||
|
|
@ -316,7 +297,7 @@ describe('bandeDeMode : la bande sous le bandeau', () => {
|
|||
assert.equal(bandeDeMode(etat({ plan: null })), null);
|
||||
});
|
||||
|
||||
test('lecture, écriture, plan bloqué — quel que soit le mode : le libellé du cadre, une explication, une icône', () => {
|
||||
test('lecture, écriture, plan bloqué — quel que soit le mode : le libellé de mode, une explication, une icône', () => {
|
||||
assert.deepEqual(bandeDeMode(etat({ mode: 'lecture' })), {
|
||||
mode: 'lecture',
|
||||
cle: 'coquille.mode.lecture',
|
||||
|
|
@ -339,9 +320,10 @@ describe('bandeDeMode : la bande sous le bandeau', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('le libellé est celui du cadre ; trois explications traduites, distinctes, qui citent la commande qui en sort', () => {
|
||||
test('le libellé est celui du bandeau ; trois libellés et trois explications traduits, distincts ; chaque explication cite la commande qui en sort', () => {
|
||||
const etats = [etat(), etat({ mode: 'ecriture' }), etat({ plan: 'bloque' })];
|
||||
for (const e of etats) assert.equal(bandeDeMode(e).cle, cadreDeMode(e).cle);
|
||||
for (const e of etats) assert.equal(bandeDeMode(e).cle, cleDeMode(e));
|
||||
assert.equal(new Set(etats.map((e) => t(bandeDeMode(e).cle))).size, 3);
|
||||
const explications = etats.map((e) => t(bandeDeMode(e).explication));
|
||||
for (const texte of explications) assert.notEqual(texte.trim(), '');
|
||||
assert.equal(new Set(explications).size, 3);
|
||||
|
|
@ -349,7 +331,7 @@ describe('bandeDeMode : la bande sous le bandeau', () => {
|
|||
explications.forEach((texte, rang) => assert.ok(texte.includes(`« ${t(commandes[rang])} »`), texte));
|
||||
});
|
||||
|
||||
test('un format plus récent, plan bloqué ou non : le mode du cadre, et une explication qui ne cite aucune commande', () => {
|
||||
test('un format plus récent, plan bloqué ou non : son mode, et une explication qui ne cite aucune commande', () => {
|
||||
const attendus = [
|
||||
[etat({ refus: FORMAT_REFUS }), { mode: 'lecture', cle: 'coquille.mode.lecture', icone: 'oeil' }],
|
||||
[etat({ plan: 'bloque', refus: FORMAT_REFUS }), { mode: 'bloque', cle: 'coquille.mode.bloque', icone: 'cadenas' }],
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
import { estBrouillon, piedDePage, refusImpression } from '../../application/sorties.js';
|
||||
import { constante } from '../../geometrie/constantes.js';
|
||||
import { DECOUPES, VUES_IMPRESSION } from '../etat_ui.js';
|
||||
import { cadreDeMode } from '../evenement/modele.js';
|
||||
import { cleDeMode } from '../mode.js';
|
||||
import Commande from '../primitives/Commande.svelte';
|
||||
import Papier from '../sorties/Papier.svelte';
|
||||
import FeuillesTables from './FeuillesTables.svelte';
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
);
|
||||
const feuille = $derived(feuilleDe(pied, estBrouillon(charge, { source: sortie.source, tour: sortie.tour })));
|
||||
const Vue = $derived(VUES.get(options.vue));
|
||||
const cadre = $derived(cadreDeMode($seance));
|
||||
const cleMode = $derived(cleDeMode($seance));
|
||||
const tours = $derived(toursOfferts(charge.evenement.tours));
|
||||
|
||||
// La bande mesurée sur le gabarit, null avant la première mesure ; ce qui
|
||||
|
|
@ -113,8 +113,8 @@
|
|||
<div class="barre" role="group" aria-label={t('impression.barre')}>
|
||||
<div class="tete">
|
||||
<h2>{t('impression.titre')}</h2>
|
||||
{#if cadre !== null}
|
||||
<span class="mode" data-mode-libelle="cadre">{t(cadre.cle)}</span>
|
||||
{#if cleMode !== null}
|
||||
<span class="mode" data-mode-libelle="cadre">{t(cleMode)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="groupe vues" role="group" aria-label={t('impression.vue.titre')}>
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@
|
|||
lignes d'un tableau — ; l'action principale — son fond, son texte, sa
|
||||
teinte claire et son encre, celle d'un lien — ; les quatre états du plan,
|
||||
le neutre et les appartenances, chacun un fond teinté et une marque, que
|
||||
lisent les pastilles ; la bande de mode, un fond et un filet par mode, sa
|
||||
marque étant la teinte du cadre de mode ; les messages — avis, refus,
|
||||
lisent les pastilles ; la bande de mode, un fond, un filet et une
|
||||
teinte par mode, celle de son icône ; les messages — avis, refus,
|
||||
information —, chacun son encre, un fond, un filet et une marque.
|
||||
|
||||
Le texte d'une pastille ou de la bande de mode garde l'encre de la page
|
||||
|
|
@ -203,8 +203,8 @@ textarea {
|
|||
--couleur-appartenance-6-fond: #f3f0fc;
|
||||
--couleur-appartenance-6-marque: #8f6dd5;
|
||||
|
||||
/* Cadre de mode (§ 8.4), et bande de mode : fond et filet ; sa marque est
|
||||
la teinte du cadre. */
|
||||
/* Bande de mode (§ 8.4) : la teinte de chaque mode, que porte son icône,
|
||||
puis son fond et son filet. */
|
||||
--couleur-cadre-lecture: #5c6878;
|
||||
--couleur-cadre-ecriture: #2e7d32;
|
||||
--couleur-cadre-bloque: #7b3fa0;
|
||||
|
|
|
|||
|
|
@ -74,18 +74,10 @@ export const TABLE = new Map([
|
|||
['accueil.colonne.participants', 'Participants'],
|
||||
['accueil.colonne.modification', 'Dernière modification'],
|
||||
['accueil.colonne.chemin', 'Fichier'],
|
||||
// Les deux mesures du placement retenu (§ 5.4, § 8.9), et l'explication
|
||||
// que l'infobulle de leur intitulé donne : ce que compte le nombre, d'où
|
||||
// il vient, et quand il vaut « — ».
|
||||
['accueil.colonne.collisionsCumulees', 'Collisions cumulées (paire et tour)'],
|
||||
// La mesure du placement retenu (§ 5.4, § 8.9), et l'explication que
|
||||
// l'infobulle de son intitulé donne : ce que compte le nombre, d'où il
|
||||
// vient, et quand il vaut « — ».
|
||||
['accueil.colonne.diffRedondance', "Diff somme d'appartenance"],
|
||||
[
|
||||
'accueil.explication.collisionsCumulees',
|
||||
'Celles du placement retenu : chaque fois que deux personnes de la même appartenance partagent une table, ' +
|
||||
'compté une fois par paire et par tour ; deux collègues ensemble aux quatre tours font 4. Le même nombre que la ' +
|
||||
"colonne « Collisions cumulées » du tableau des propositions. Plus il est bas, mieux c'est. " +
|
||||
"« \u{2014} » quand aucun placement n'est retenu, ou que le retenu ne décrit plus la configuration.",
|
||||
],
|
||||
[
|
||||
'accueil.explication.diffRedondance',
|
||||
'|cible \u{2212} Σ r(p)| : la différence, sans signe, entre la cible de la soirée et la somme Σ r(p) du placement ' +
|
||||
|
|
@ -96,13 +88,13 @@ export const TABLE = new Map([
|
|||
],
|
||||
|
||||
// La meilleure proposition du classement de chaque événement (§ 5.4,
|
||||
// § 5.7) : son titre, l'identifiant et les collisions s'écrivent sous les
|
||||
// libellés du bloc de la génération ; l'explication du titre est propre à
|
||||
// la liste, qui l'écrit « — » quand rien n'est classé.
|
||||
// § 5.7) : son titre et son identifiant s'écrivent sous les libellés du
|
||||
// bloc de la génération ; l'explication du titre est propre à la liste,
|
||||
// qui l'écrit « — » quand rien n'est classé.
|
||||
[
|
||||
'accueil.explication.meilleure',
|
||||
"La proposition au rang 1 du classement de l'événement, celle que la section de la génération annonce en tête : " +
|
||||
'son identifiant et ses collisions cumulées. Le classement range les propositions comparables, retenue comprise ; ' +
|
||||
'son identifiant. Le classement range les propositions comparables, retenue comprise ; ' +
|
||||
'le placement retenu se lit à part, sous ses propres intitulés. ' +
|
||||
"« \u{2014} » quand aucune proposition n'est classée : aucune proposition, ou aucune qui décrive encore la " +
|
||||
'configuration.',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
// Les libellés de l'événement ouvert : son en-tête et son résumé chiffré,
|
||||
// ses états, la bande et la place fixe de son cadre de mode, les phases de
|
||||
// ses états, la bande de mode et la place fixe de sa barre, les phases de
|
||||
// sa navigation, la reprise d'un verrou, ses avertissements, le support
|
||||
// rompu (§ 8.3 à § 8.9, § 9, § 14.6). Chaque clé commence par
|
||||
// « evenement. ». Une donnée qui désigne — un numéro, une séance, un
|
||||
|
|
@ -53,7 +53,7 @@ export const TABLE = new Map([
|
|||
['evenement.phase.placer', 'Placer'],
|
||||
['evenement.phase.produire', 'Produire'],
|
||||
|
||||
// --- Le cadre de mode et sa place fixe (§ 8.3, § 8.4, § 8.5) -------------
|
||||
// --- La barre de l'événement et sa place fixe (§ 8.3, § 8.4, § 8.5) ------
|
||||
['evenement.cadre.commandes', 'Commandes de l\u{2019}événement'],
|
||||
['evenement.commande.defaire', 'Défaire'],
|
||||
['evenement.commande.refaire', 'Refaire'],
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ const TEINTES_DE_PASTILLE = [
|
|||
|
||||
/** Les signes, même forme que MARQUES, seuil 3 : l'icône ou le point d'une
|
||||
* pastille sur son fond teinté et sur les surfaces de contenu ; l'icône
|
||||
* d'un message sur son fond ; l'icône de la bande de mode, de la teinte du
|
||||
* cadre de mode, sur le fond de la bande ; le fond de l'action principale,
|
||||
* d'un message sur son fond ; l'icône de la bande de mode, de la teinte de
|
||||
* son mode, sur le fond de la bande ; le fond de l'action principale,
|
||||
* qui délimite le bouton, sur les surfaces. */
|
||||
export const SIGNES = Object.freeze([
|
||||
...TEINTES_DE_PASTILLE.map(([nom, teinte]) => marque(nom, `${teinte}-marque`, [`${teinte}-fond`, ...SURFACES])),
|
||||
|
|
@ -134,10 +134,6 @@ export const MARQUES = Object.freeze([
|
|||
// La sélection marque aussi une colonne du tableau des propositions, sur la
|
||||
// carte, son fond sourd et la ligne du retenu.
|
||||
marque('selection', 'selection', ['page-fond', 'carte-fond', 'carte-fond-sourd', 'etat-retenu-fond', 'sol', 'table-fond', ...REMPLISSAGES_CHAISE]),
|
||||
// Le cadre de mode (§ 8.4) entoure le contenu de l'événement, sur la page.
|
||||
marque('cadreLecture', 'cadre-lecture', ['page-fond']),
|
||||
marque('cadreEcriture', 'cadre-ecriture', ['page-fond']),
|
||||
marque('cadreBloque', 'cadre-bloque', ['page-fond']),
|
||||
marque('focus', 'focus', ['page-fond', 'bandeau-fond', 'carte-fond', 'carte-fond-sourd', 'etat-retenu-fond', 'action-teinte', 'sol', 'table-fond', ...REMPLISSAGES_CHAISE]),
|
||||
// Le mobilier sur le sol : le trait de la table, celui de la chaise, qui
|
||||
// touche aussi le plateau.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const THEMES = ['clair', 'sombre'];
|
|||
const NOMS_MARQUES = [
|
||||
'cadenasFerme', 'cadenasOuvert', 'anneau', 'hachure', 'remplissagePlein',
|
||||
'contourConflit', 'trameAppartenance', 'trameVoisins', 'trameTable', 'pastille', 'poignee', 'gel', 'miseEnEvidence',
|
||||
'selection', 'cadreLecture', 'cadreEcriture', 'cadreBloque', 'focus',
|
||||
'selection', 'focus',
|
||||
'contourTable', 'contourChaise', 'echelle', 'fantome', 'chargement', 'filigrane',
|
||||
];
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ const FONDS_REQUIS = {
|
|||
const parNom = (registre, nom) => registre.find((e) => e.nom === nom);
|
||||
|
||||
describe('registre des marques', () => {
|
||||
test('les vingt-quatre marques du contrat, noms uniques, seuil 3, au moins un fond chacune', () => {
|
||||
test('les vingt et une marques du contrat, noms uniques, seuil 3, au moins un fond chacune', () => {
|
||||
assert.equal(MARQUES.length, NOMS_MARQUES.length);
|
||||
assert.deepEqual(MARQUES.map((m) => m.nom), NOMS_MARQUES);
|
||||
assert.equal(new Set(MARQUES.map((m) => m.nom)).size, MARQUES.length);
|
||||
|
|
@ -204,7 +204,8 @@ describe('les signes des pastilles, des messages, de la bande de mode et de l’
|
|||
assert.equal(signe.jeton, `--couleur-${teinte}-marque`, nom);
|
||||
assert.deepEqual(signe.fonds, [`--couleur-${teinte}-fond`, '--couleur-carte-fond', '--couleur-carte-fond-sourd', '--couleur-page-fond'], nom);
|
||||
}
|
||||
// La bande de mode reprend la teinte du cadre de mode, sur son propre fond.
|
||||
// L'icône de la bande de mode porte la teinte de son mode, sur le fond de
|
||||
// la bande.
|
||||
for (const mode of ['Ecriture', 'Lecture', 'Bloque']) {
|
||||
const signe = parNom(SIGNES, `bande${mode}`);
|
||||
assert.deepEqual([signe.jeton, ...signe.fonds], [`--couleur-cadre-${mode.toLowerCase()}`, `--couleur-mode-${mode.toLowerCase()}-fond`]);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
// Ce que le bandeau dit de l'événement ouvert, en clés de la table de la
|
||||
// coquille : le libellé de mode (§ 8.4, § 8.5), calculé ici une fois pour le
|
||||
// bandeau et pour le cadre de mode, et l'annonce de la version de format du
|
||||
// bandeau, la bande de mode et la barre de l'événement, et l'annonce de la version de format du
|
||||
// fichier (§ 18.6). Les deux se lisent dans l'état observable de la séance.
|
||||
|
||||
// La clé de chaque annonce de format de la séance.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||||
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||
|
||||
// La clé du libellé de mode (§ 8.4, § 8.5), que lisent le bandeau et le
|
||||
// cadre de mode : aucune sans événement ; le plan bloqué avant le mode, qu'il
|
||||
// force en lecture ; sinon le mode de la séance. Et la clé de l'annonce de
|
||||
// La clé du libellé de mode (§ 8.4, § 8.5), que lisent le bandeau, la bande
|
||||
// de mode et la barre de l'événement : aucune sans événement ; le plan
|
||||
// bloqué avant le mode, qu'il force en lecture ; sinon le mode de la séance. Et la clé de l'annonce de
|
||||
// format du bandeau (§ 18.6), une par annonce de la séance.
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, test } from '../../test/lanceur.js';
|
||||
|
|
|
|||
|
|
@ -215,6 +215,24 @@ const base = () => etat().evenement.base;
|
|||
const plat = (texte) => texte.replace(/\s+/g, ' ').trim();
|
||||
const texteDe = (element) => plat(element.textContent);
|
||||
|
||||
// Le mode de l'événement ouvert, sans cadre : la surface de travail est en
|
||||
// ce mode et ne porte aucun trait ; la bande de mode, visible, et le libellé
|
||||
// de la barre, à côté de la place fixe, le nomment, comme le bandeau.
|
||||
function exigerModeSansCadre(mode) {
|
||||
const surface = dans('[data-mode]');
|
||||
expect(surface.dataset.mode).toBe(mode);
|
||||
expect(surface.hasAttribute('data-marque')).toBe(false);
|
||||
const style = getComputedStyle(surface);
|
||||
for (const cote of ['Top', 'Right', 'Bottom', 'Left']) expect(Number.parseFloat(style[`border${cote}Width`]), cote).toBe(0);
|
||||
const bande = dans('[data-ecran="evenement"] > [role="status"]');
|
||||
const libelle = dans('[data-mode-libelle="cadre"]');
|
||||
expect(visible(bande)).toBe(true);
|
||||
expect(visible(libelle)).toBe(true);
|
||||
expect(texteDe(libelle).length).toBeGreaterThan(0);
|
||||
expect(texteDe(bande)).toContain(texteDe(libelle));
|
||||
expect(texteDe(dans('[data-mode-libelle="bandeau"]'))).toBe(texteDe(libelle));
|
||||
}
|
||||
|
||||
// Le texte de l'état et du journal de l'événement ouvert, et sa révision.
|
||||
function fichiers() {
|
||||
return {
|
||||
|
|
@ -832,10 +850,10 @@ describe('le parcours par gestes, depuis un répertoire neuf', { timeout: DUREE
|
|||
expect(charge().participants).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('01b une modification refusée en lecture, à côté de son geste, avec « Modifier » pour remède ; le cadre de lecture', async () => {
|
||||
test('01b une modification refusée en lecture, à côté de son geste, avec « Modifier » pour remède ; la lecture, sans cadre', async () => {
|
||||
await ouvrirSection('tables');
|
||||
const avant = fichiers();
|
||||
expect(dans('[data-mode]').dataset.marque).toBe('cadreLecture');
|
||||
exigerModeSansCadre('lecture');
|
||||
await cliquer('[data-section="tables"] [data-commande="creerTableRonde"]');
|
||||
await refusPres(() => dans('[data-commande="creerTableRonde"]').parentElement, 'MODE_LECTURE', 'modifier', 'créer une table');
|
||||
expect(document.querySelectorAll(FENETRE)).toHaveLength(0);
|
||||
|
|
@ -846,16 +864,11 @@ describe('le parcours par gestes, depuis un répertoire neuf', { timeout: DUREE
|
|||
});
|
||||
});
|
||||
|
||||
test('01b « Modifier » : l’écriture et son cadre ; la même modification passe', async () => {
|
||||
test('01b « Modifier » : l’écriture, sans cadre ; la même modification passe', async () => {
|
||||
await cliquer('[data-place-fixe] [data-commande="modifier"]');
|
||||
await attendre('l’écriture', () => etat().mode === 'ecriture' && dans('[data-place-fixe] [data-commande="lecture"]'));
|
||||
const cadre = dans('[data-mode]');
|
||||
expect(cadre.dataset.marque).toBe('cadreEcriture');
|
||||
expect(visible(cadre)).toBe(true);
|
||||
const libelle = dans('[data-mode-libelle="cadre"]');
|
||||
expect(visible(libelle)).toBe(true);
|
||||
expect(texteDe(libelle).length).toBeGreaterThan(0);
|
||||
expect(texteDe(dans('[data-mode-libelle="bandeau"]'))).toBe(texteDe(libelle));
|
||||
expect(visible(dans('[data-mode]'))).toBe(true);
|
||||
exigerModeSansCadre('ecriture');
|
||||
|
||||
const r = revision();
|
||||
await cliquer('[data-section="tables"] [data-commande="creerTableRonde"]');
|
||||
|
|
@ -1407,11 +1420,10 @@ describe('le parcours par gestes, depuis un répertoire neuf', { timeout: DUREE
|
|||
});
|
||||
|
||||
// Glisser une personne, une table, tirer une poignée, poser une réservation.
|
||||
test('20 la lecture : quatre gestes du plan, empreinte inchangée ; le cadre, son libellé et le bandeau', async () => {
|
||||
test('20 la lecture : quatre gestes du plan, empreinte inchangée ; la bande, le libellé de mode et le bandeau, sans cadre', async () => {
|
||||
await cliquer('[data-place-fixe] [data-commande="lecture"]');
|
||||
await attendre('la lecture', () => etat().mode === 'lecture');
|
||||
expect(dans('[data-mode]').dataset.marque).toBe('cadreLecture');
|
||||
expect(visible(dans('[data-mode-libelle="cadre"]'))).toBe(true);
|
||||
exigerModeSansCadre('lecture');
|
||||
expect(dans('[data-bandeau]').textContent).toContain(MARQUE_PROVENANCE);
|
||||
const empreinte = serialiserCharge(charge());
|
||||
const avant = fichiers();
|
||||
|
|
@ -2095,7 +2107,7 @@ describe('lecture et bloqué : tout geste modifiant refusé, rien d’écrit', {
|
|||
await cliquer('[data-etat="bloque"] [data-commande="etat"]');
|
||||
await uneEntree(r, 'bloquer');
|
||||
expect(charge().evenement.etat).toBe('bloque');
|
||||
expect(dans('[data-mode]').dataset.marque).toBe('cadreBloque');
|
||||
exigerModeSansCadre('bloque');
|
||||
const empreinte = serialiserCharge(charge());
|
||||
const avant = fichiers();
|
||||
const rang = m.trace.length;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
|
||||
|
||||
<script>
|
||||
// Le bandeau du plan (§ 7.5), au-dessus du <svg data-plan> et dans le
|
||||
// cadre de mode : vide tant qu'une table au moins est saine ; quand toutes
|
||||
// Le bandeau du plan (§ 7.5), au-dessus du <svg data-plan> et dans la
|
||||
// surface de travail de l'événement : vide tant qu'une table au moins est saine ; quand toutes
|
||||
// sont en conflit, la phrase, puis ce que le diagnostic prouve — chaque
|
||||
// entrée {cle, details} du modèle (modele.js), traduite. L'appartenance
|
||||
// d'un plancher de groupe est une donnée : elle occupe son propre nœud,
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
// pas rognée par son défilement.
|
||||
//
|
||||
// En plein écran, le titre et les panneaux s'effacent : le plan prend la
|
||||
// surface que le cadre de mode lui laisse sous le bandeau (§ 18.6), et le
|
||||
// cadre l'entoure toujours (§ 8.4).
|
||||
// surface de travail que la bande de mode et la barre de l'événement lui
|
||||
// laissent sous le bandeau (§ 8.4, § 18.6).
|
||||
import { getContext } from 'svelte';
|
||||
import { diagnostiquerCharge } from '../../application/diagnostic.js';
|
||||
import { etatPlaces } from '../../application/places.js';
|
||||
|
|
@ -75,8 +75,8 @@
|
|||
</section>
|
||||
|
||||
<style>
|
||||
/* La section prend la hauteur de la surface du cadre de mode : le plan
|
||||
s'y étend, et c'est la surface du cadre qui défile, jamais la page. */
|
||||
/* La section prend la hauteur de la surface de travail de l'événement :
|
||||
le plan s'y étend, et c'est cette surface qui défile, jamais la page. */
|
||||
.section-plan {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -833,7 +833,7 @@ describe('le plein écran (§ 8.4, § 18.6)', () => {
|
|||
expect(boite(surface(m))).toEqual(avant);
|
||||
});
|
||||
|
||||
test('le cadre de mode garde sa boîte après un zoom', async () => {
|
||||
test('la barre, la tête et la surface de l’événement gardent leur boîte après un zoom', async () => {
|
||||
const m = await petite();
|
||||
await deuxImages();
|
||||
const avant = boite(dans(m, '[data-mode]'));
|
||||
|
|
|
|||
|
|
@ -460,13 +460,17 @@ function releverEtats(m) {
|
|||
return etats;
|
||||
}
|
||||
|
||||
// Le cadre de mode : sa signature, son libellé compris.
|
||||
function signatureCadre(racine) {
|
||||
const cadre = racine.querySelector('[data-mode]');
|
||||
if (cadre === null) return null;
|
||||
const s = getComputedStyle(cadre);
|
||||
const libelle = racine.querySelector('[data-mode-libelle="cadre"]')?.textContent.trim() ?? '';
|
||||
return `bords ${s.borderTopStyle} ${s.borderTopWidth} | ${libelle}`;
|
||||
// Le mode de l'événement : sig, la signature de la bande de mode et du
|
||||
// libellé de mode de la barre ; trait, la somme des épaisseurs des bords de
|
||||
// la surface de travail, qui n'en porte aucun. null hors d'un événement.
|
||||
function signatureMode(racine) {
|
||||
const surface = racine.querySelector('[data-mode]');
|
||||
const bande = racine.querySelector('[data-ecran="evenement"] > [role="status"]');
|
||||
const libelle = racine.querySelector('[data-mode-libelle="cadre"]');
|
||||
if (surface === null || bande === null || libelle === null) return null;
|
||||
const s = getComputedStyle(surface);
|
||||
const trait = ['Top', 'Right', 'Bottom', 'Left'].reduce((somme, cote) => somme + Number.parseFloat(s[`border${cote}Width`]), 0);
|
||||
return { sig: signature([bande, libelle]), trait };
|
||||
}
|
||||
|
||||
/** Fautes d'une paire d'états : chaque élément du premier dont la signature
|
||||
|
|
@ -494,7 +498,7 @@ function dansLaFenetre(element) {
|
|||
|
||||
/** Fautes du bandeau d'un écran (§ 8.4, § 18.6) : unique, visible, portant
|
||||
* la version et la provenance ; le libellé de mode absent sans événement,
|
||||
* présent et égal à celui du cadre sinon. */
|
||||
* présent et égal à celui de la barre de l'événement sinon. */
|
||||
export function fautesBandeau(racine, mode, { version = VERSION.affichee, provenance = provenanceVisible(PROVENANCE, tTemoin) } = {}) {
|
||||
const bandeaux = racine.querySelectorAll('[data-bandeau]');
|
||||
if (bandeaux.length !== 1) return [`${bandeaux.length} bandeaux`];
|
||||
|
|
@ -513,12 +517,12 @@ export function fautesBandeau(racine, mode, { version = VERSION.affichee, proven
|
|||
if (libelle !== null) fautes.push(`libellé de mode « ${libelle.textContent.trim()} » sans événement ouvert`);
|
||||
return fautes;
|
||||
}
|
||||
const cadre = racine.querySelector('[data-mode-libelle="cadre"]');
|
||||
const barre = racine.querySelector('[data-mode-libelle="cadre"]');
|
||||
if (libelle === null) fautes.push(`libellé de mode absent du bandeau (${mode})`);
|
||||
else if (voir(libelle) !== true) fautes.push(`libellé de mode du bandeau non visible : ${voir(libelle)}`);
|
||||
if (cadre === null) fautes.push(`libellé de mode absent du cadre (${mode})`);
|
||||
if (libelle !== null && cadre !== null && libelle.textContent.trim() !== cadre.textContent.trim()) {
|
||||
fautes.push(`bandeau « ${libelle.textContent.trim()} », cadre « ${cadre.textContent.trim()} »`);
|
||||
if (barre === null) fautes.push(`libellé de mode absent de la barre (${mode})`);
|
||||
if (libelle !== null && barre !== null && libelle.textContent.trim() !== barre.textContent.trim()) {
|
||||
fautes.push(`bandeau « ${libelle.textContent.trim()} », barre « ${barre.textContent.trim()} »`);
|
||||
}
|
||||
return fautes;
|
||||
}
|
||||
|
|
@ -603,7 +607,7 @@ beforeAll(async () => {
|
|||
releve.refus = releverRefus(racine).map((r) => ({ ...r, restes: horsMarques(r.texte).filter((x) => !estDonnee(x, connues)) }));
|
||||
releve.etats = releverEtats(m);
|
||||
releve.listes = releverListes(racine);
|
||||
releve.cadre = signatureCadre(racine);
|
||||
releve.signatureMode = signatureMode(racine);
|
||||
releve.clair = releverTheme(racine);
|
||||
await cliquer(m, '[data-bandeau] [data-commande="theme"][data-theme-choix="sombre"]');
|
||||
await attendre('le thème sombre', () => document.documentElement.getAttribute('data-theme') === 'sombre');
|
||||
|
|
@ -984,7 +988,7 @@ describe('chaque écran de l’itération (§ 14.5, § 14.6, § 19.11)', () => {
|
|||
aucuneFaute([...new Set(fautes)], 'atténuations');
|
||||
});
|
||||
|
||||
test('jamais la seule couleur : table saine et en conflit, les trois sortes de conflit, place libre, réservée et à titre non pourvu, portée tour et tous, cadre en lecture, en écriture et bloqué', () => {
|
||||
test('jamais la seule couleur : table saine et en conflit, les trois sortes de conflit, place libre, réservée et à titre non pourvu, portée tour et tous, mode en lecture, en écriture et bloqué, sans trait autour de la surface', () => {
|
||||
const plan = atteints().find((r) => r.nom === 'evenement.plan.ecriture');
|
||||
expect(plan, 'le plan en écriture relevé').toBeDefined();
|
||||
const e = plan.etats;
|
||||
|
|
@ -1000,13 +1004,14 @@ describe('chaque écran de l’itération (§ 14.5, § 14.6, § 19.11)', () => {
|
|||
expect(trois, 'le plan des trois sortes de conflit relevé').toBeDefined();
|
||||
const [a, b, c] = CONFLITS.map(({ marque }) => trois.etats.trames[marque]);
|
||||
fautes.push(...fautesPaire('trame appartenance / voisins', a, b), ...fautesPaire('trame appartenance / table', a, c), ...fautesPaire('trame voisins / table', b, c));
|
||||
const cadres = ['lecture', 'ecriture', 'bloque'].map((mode) => {
|
||||
const modes = ['lecture', 'ecriture', 'bloque'].map((mode) => {
|
||||
const r = atteints().find((x) => x.nom === `evenement.plan.${mode}`);
|
||||
return { ou: `cadre ${mode}`, sig: r?.cadre ?? null };
|
||||
return { ou: `mode ${mode}`, sig: r?.signatureMode?.sig ?? null, trait: r?.signatureMode?.trait ?? null };
|
||||
});
|
||||
for (const c of cadres) if (c.sig === null) fautes.push(`${c.ou} : absent`);
|
||||
for (let i = 0; i < cadres.length; i += 1) {
|
||||
for (let j = i + 1; j < cadres.length; j += 1) fautes.push(...fautesPaire(`${cadres[i].ou} / ${cadres[j].ou}`, [cadres[i]], [cadres[j]]));
|
||||
for (const c of modes) if (c.sig === null) fautes.push(`${c.ou} : absent`);
|
||||
for (const c of modes) if (c.trait !== null && c.trait !== 0) fautes.push(`${c.ou} : un trait de ${c.trait} px autour de la surface`);
|
||||
for (let i = 0; i < modes.length; i += 1) {
|
||||
for (let j = i + 1; j < modes.length; j += 1) fautes.push(...fautesPaire(`${modes[i].ou} / ${modes[j].ou}`, [modes[i]], [modes[j]]));
|
||||
}
|
||||
aucuneFaute(fautes, 'jamais la seule couleur');
|
||||
});
|
||||
|
|
@ -1024,7 +1029,7 @@ describe('chaque écran de l’itération (§ 14.5, § 14.6, § 19.11)', () => {
|
|||
aucuneFaute(atteints().flatMap((r) => r.emoji.fautes.map((f) => `${r.nom} — ${f}`)), 'emoji');
|
||||
});
|
||||
|
||||
test('le bandeau sur tout écran : visible, version et provenance ; libellé de mode absent de la liste, égal à celui du cadre sur un événement', () => {
|
||||
test('le bandeau sur tout écran : visible, version et provenance ; libellé de mode absent de la liste, égal à celui de la barre sur un événement', () => {
|
||||
expect(atteints().some((r) => r.mode === null) && atteints().some((r) => r.mode !== null)).toBe(true);
|
||||
aucuneFaute(atteints().flatMap((r) => r.bandeau.map((f) => `${r.nom} — ${f}`)), 'bandeau');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -107,13 +107,13 @@ const ENTREES = [
|
|||
['data-liste-travail', 'plan : une liste de travail', ['pressentis', 'titres']],
|
||||
['data-marque', 'une marque porteuse d’information, par son nom au registre', null],
|
||||
['data-meilleure', 'la meilleure proposition : accueil ou génération', ['']],
|
||||
['data-meilleure-collisions', 'les collisions de la meilleure proposition', ['']],
|
||||
['data-meilleure-collisions', 'génération : les collisions de la meilleure proposition', ['']],
|
||||
['data-meilleure-id', 'l’identifiant de la meilleure proposition', ['']],
|
||||
['data-meilleure-libelle-id', 'génération : le libellé de l’identifiant de la meilleure', ['']],
|
||||
['data-meilleure-vide', 'génération : la meilleure absente', ['']],
|
||||
['data-mention', 'aperçu : une mention d’une page du plan imprimé — disposition relative, listes hors d’échelle', ['listes', 'relative']],
|
||||
['data-menu', 'plan : le menu d’une personne', null],
|
||||
['data-mode', 'événement : le cadre de mode, par sa forme', null],
|
||||
['data-mode', 'événement : la barre, la tête et la surface de l’événement, par son mode', null],
|
||||
['data-mode-libelle', 'le libellé de mode', ['bandeau', 'cadre']],
|
||||
['data-motif', 'import : le motif d’une ligne refusée', ['']],
|
||||
['data-multiligne', 'import : une ligne sur plusieurs lignes de texte', null],
|
||||
|
|
@ -130,7 +130,7 @@ const ENTREES = [
|
|||
['data-pastille', 'plan : la pastille d’une table', null],
|
||||
['data-phrase', 'une phrase : diagnostic ou défaut des tables', ['']],
|
||||
['data-pied', 'aperçu : le pied d’une feuille imprimée', ['']],
|
||||
['data-place-fixe', 'événement : la place fixe du cadre de mode', ['']],
|
||||
['data-place-fixe', 'événement : la place fixe de la barre de l’événement', ['']],
|
||||
['data-place-vide', 'aperçu : une place vide de la feuille par table', ['']],
|
||||
['data-plan', 'plan : le <svg> du plan', ['']],
|
||||
['data-poignee', 'plan : la poignée d’une table', null],
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ export const ECRANS = Object.freeze([
|
|||
// « chargement ».
|
||||
const m = await contexte.monter({ fichiers: [fichierEtat(chargePrincipale()), fichierEtat(chargeGelee(), 'gelee')] });
|
||||
await attendre('le signe du chargement dans chaque valeur de résumé', () =>
|
||||
[BASE, 'gelee'].every((base) => m.racine.querySelectorAll(`[data-evenement="${base}"] [aria-busy="true"] [data-chargement]`).length === 4),
|
||||
[BASE, 'gelee'].every((base) => m.racine.querySelectorAll(`[data-evenement="${base}"] [aria-busy="true"] [data-chargement]`).length === 2),
|
||||
);
|
||||
return m.racine;
|
||||
},
|
||||
|
|
@ -433,8 +433,8 @@ export const ECRANS = Object.freeze([
|
|||
nom: 'accueil.mesuresRetenu',
|
||||
mode: null,
|
||||
async atteindre(contexte) {
|
||||
// La principale, sa proposition retenue : la ligne montre les deux
|
||||
// mesures du retenu et la meilleure proposition du classement, et le
|
||||
// La principale, sa proposition retenue : la ligne montre la
|
||||
// différence du retenu et la meilleure proposition du classement, et le
|
||||
// pointeur réel sur l'intitulé de la différence — un en-tête de
|
||||
// colonne du tableau, une fois pour la liste — ouvre son infobulle.
|
||||
const principale = chargePrincipale();
|
||||
|
|
@ -442,12 +442,8 @@ export const ECRANS = Object.freeze([
|
|||
const m = await contexte.monter({ fichiers: [fichierEtat(charge)] });
|
||||
const ligne = `[data-evenement="${BASE}"]`;
|
||||
await chargerResumes(m);
|
||||
await attendre('les mesures du retenu et la meilleure proposition', () =>
|
||||
[
|
||||
...['collisionsCumulees', 'diffRedondance'].map((colonne) => `[data-colonne="${colonne}"]`),
|
||||
'[data-meilleure-id]',
|
||||
'[data-meilleure-collisions]',
|
||||
].every((valeur) => /\d/.test(m.racine.querySelector(`${ligne} ${valeur}`)?.textContent ?? '')),
|
||||
await attendre('la différence du retenu et la meilleure proposition', () =>
|
||||
['[data-colonne="diffRedondance"]', '[data-meilleure-id]'].every((valeur) => /\d/.test(m.racine.querySelector(`${ligne} ${valeur}`)?.textContent ?? '')),
|
||||
);
|
||||
const { x, y } = await centre(await element(m, '[data-ecran="accueil"] thead [data-terme="diffRedondance"]'));
|
||||
await pointer('bouger', x, y);
|
||||
|
|
|
|||
Loading…
Reference in a new issue