Piloter la frise au clavier, à la vim
Les flèches décalaient déjà une phase, mais rien ne permettait de la choisir :
`selection` n'était posée qu'au clic, si bien qu'il fallait la souris pour
commencer. Elle devient `curseur`, désigne une ligne — celle d'un projet en est
une, d'où l'on plie, renomme et ajoute — et survit à Échap, sans quoi elle ne
pourrait servir de point de départ au déplacement suivant. `dessiner` le
normalise, la ligne du projet servant de refuge quand la sienne disparaît.
Deux couches qui ne se recouvrent pas : les flèches modifient, les lettres
déplacent et commandent. Aucun raccourci documenté ne change de sens. Les
touches se taisent dès que le clavier est à quelqu'un d'autre — une saisie, un
dialogue, le menu, un glisser —, faute de quoi `a` dans un nom créerait une
phase. AltGr est toléré : sur un clavier français `{` et `}` n'existent qu'avec
lui, et Windows le présente comme Ctrl+Alt.
Les phases étant triées par date, `o` et `O` insèrent dans le temps et non dans
un rang. `P` reste hors de cette famille : un projet naît sans position.
Le curseur se peint dans la colonne des libellés, seul endroit qui porte une
ligne pour chaque position atteignable — projet plié, projet masqué, ligne de
projet. Et `?` ouvre la table des raccourcis, qui ne se devinent pas.
Décision 26.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,9 +93,9 @@ export function creerEchelle(fenetre) {
|
||||
*
|
||||
* @param {object} refs éléments DOM ciblés (libelles, entete, couloirs)
|
||||
* @param {object} planning l'état courant
|
||||
* @param {object} options { fenetre, selection, tagsActifs }
|
||||
* @param {object} options { fenetre, curseur, tagsActifs }
|
||||
*/
|
||||
export function rendre(refs, planning, { fenetre, selection, tagsActifs = [] }) {
|
||||
export function rendre(refs, planning, { fenetre, curseur, tagsActifs = [] }) {
|
||||
const echelle = creerEchelle(fenetre);
|
||||
const retenus = planning.projects.filter((projet) => projetFiltre(projet, tagsActifs));
|
||||
const visibles = retenus.filter((projet) => !projet.hidden);
|
||||
@@ -104,10 +104,10 @@ export function rendre(refs, planning, { fenetre, selection, tagsActifs = [] })
|
||||
refs.couloirs.style.width = `${echelle.largeur}px`;
|
||||
|
||||
refs.entete.replaceChildren(...construireEntete(echelle));
|
||||
refs.libelles.replaceChildren(...construireLibelles(retenus, tagsActifs));
|
||||
refs.libelles.replaceChildren(...construireLibelles(retenus, tagsActifs, curseur));
|
||||
refs.couloirs.replaceChildren(
|
||||
...construireTraits(echelle, hauteurTotale(visibles)),
|
||||
...construireCouloirs(visibles, echelle, selection)
|
||||
...construireCouloirs(visibles, echelle, curseur)
|
||||
);
|
||||
|
||||
return echelle;
|
||||
@@ -269,21 +269,28 @@ function moisSuivant(annee, mois) {
|
||||
// Colonne des libellés
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function construireLibelles(projets, tagsActifs) {
|
||||
/**
|
||||
* La colonne est aussi la règle graduée du curseur clavier : c'est le seul
|
||||
* endroit qui porte une ligne pour *chaque* position atteignable, y compris
|
||||
* celles qui n'ont pas de barre en face — un projet plié, un projet masqué, ou
|
||||
* simplement la ligne d'un projet. `app.js` compte donc ses enfants pour savoir
|
||||
* où amener le défilement, et l'ordre produit ici est celui qu'il énumère.
|
||||
*/
|
||||
function construireLibelles(projets, tagsActifs, curseur) {
|
||||
const elements = [];
|
||||
|
||||
for (const projet of projets) {
|
||||
if (projet.hidden) {
|
||||
elements.push(libelleProjet(projet, tagsActifs));
|
||||
continue;
|
||||
}
|
||||
const surLeProjet = curseur && curseur.projet === projet.id && !curseur.phase;
|
||||
elements.push(libelleProjet(projet, tagsActifs, surLeProjet));
|
||||
|
||||
elements.push(libelleProjet(projet, tagsActifs));
|
||||
if (projet.collapsed) continue;
|
||||
// Un projet masqué n'a plus que sa ligne : ses phases ne se dessinent nulle
|
||||
// part, et le curseur ne les traverse pas davantage qu'il ne les affiche.
|
||||
if (projet.hidden || projet.collapsed) continue;
|
||||
|
||||
for (const phase of projet.phases) {
|
||||
const sousCurseur = curseur && curseur.projet === projet.id && curseur.phase === phase.id;
|
||||
const ligne = document.createElement('div');
|
||||
ligne.className = 'libelle-phase';
|
||||
ligne.className = `libelle-phase${sousCurseur ? ' libelle-phase--curseur' : ''}`;
|
||||
ligne.textContent = phase.name;
|
||||
ligne.title = `${phase.name} — ${LIBELLES_STATUT[phase.status]}`;
|
||||
ligne.dataset.projet = projet.id;
|
||||
@@ -295,10 +302,14 @@ function construireLibelles(projets, tagsActifs) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
function libelleProjet(projet, tagsActifs = []) {
|
||||
function libelleProjet(projet, tagsActifs = [], sousCurseur = false) {
|
||||
const ligne = document.createElement('div');
|
||||
ligne.className = `libelle-projet${projet.hidden ? ' libelle-projet--masque' : ''}`;
|
||||
ligne.className =
|
||||
'libelle-projet' +
|
||||
(projet.hidden ? ' libelle-projet--masque' : '') +
|
||||
(sousCurseur ? ' libelle-projet--curseur' : '');
|
||||
ligne.style.setProperty('--couleur-projet', projet.color);
|
||||
ligne.dataset.projet = projet.id;
|
||||
|
||||
const plier = document.createElement('button');
|
||||
plier.className = 'plier';
|
||||
@@ -388,12 +399,17 @@ function libelleProjet(projet, tagsActifs = []) {
|
||||
// Couloirs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function construireCouloirs(projets, echelle, selection) {
|
||||
function construireCouloirs(projets, echelle, curseur) {
|
||||
const elements = [];
|
||||
|
||||
for (const projet of projets) {
|
||||
// Le curseur clavier se pose aussi sur la ligne d'un projet, où il commande
|
||||
// le pli, les paramètres et l'ajout d'une phase. Elle n'a pas de barre à
|
||||
// souligner : c'est le couloir entier qui porte la marque.
|
||||
const surLeProjet = curseur && curseur.projet === projet.id && !curseur.phase;
|
||||
|
||||
const couloirProjet = document.createElement('div');
|
||||
couloirProjet.className = 'couloir couloir--projet';
|
||||
couloirProjet.className = `couloir couloir--projet${surLeProjet ? ' couloir--curseur' : ''}`;
|
||||
couloirProjet.style.setProperty('--couleur-projet', projet.color);
|
||||
|
||||
// La barre cumulative n'est plus réservée au mode plié : dépliée, elle
|
||||
@@ -413,7 +429,7 @@ function construireCouloirs(projets, echelle, selection) {
|
||||
const couloir = document.createElement('div');
|
||||
couloir.className = 'couloir';
|
||||
couloir.style.setProperty('--couleur-projet', projet.color);
|
||||
couloir.append(...construirePhase(projet, phase, echelle, selection));
|
||||
couloir.append(...construirePhase(projet, phase, echelle, curseur));
|
||||
elements.push(couloir);
|
||||
}
|
||||
}
|
||||
@@ -421,9 +437,9 @@ function construireCouloirs(projets, echelle, selection) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
function construirePhase(projet, phase, echelle, selection) {
|
||||
function construirePhase(projet, phase, echelle, curseur) {
|
||||
const selectionnee =
|
||||
selection && selection.projet === projet.id && selection.phase === phase.id;
|
||||
curseur && curseur.projet === projet.id && curseur.phase === phase.id;
|
||||
|
||||
const elements = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user