La barre d'un projet envisagé est pâle et hachurée, aux bords fondus. Elle se glisse et se redimensionne comme une phase, mais accrochée au mois : le geste existe, il ne permet pas de poser une date qu'on n'a pas. Le panneau ajoute deux champs de mois et trois crans. Une barre « Afficher » filtre par état, disjonctive là où celle des tags est conjonctive — d'où des boutons carrés et non des pastilles. Les écartés en sont décochés au départ. Engager démarre au premier jour de l'horizon, et clore un projet demande confirmation quand il reste des phases à écraser. Au passage : releasePointerCapture peut lever quand le pointeur a disparu, ce qui sautait l'application du geste. Le nettoyage en est maintenant isolé. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
446 lines
18 KiB
HTML
446 lines
18 KiB
HTML
<!doctype html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Frise multi-projets</title>
|
||
<link rel="stylesheet" href="css/style.css" />
|
||
<link
|
||
rel="icon"
|
||
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' rx='3' fill='%233b82f6'/><rect x='3' y='4' width='9' height='2' rx='1' fill='white'/><rect x='5' y='7' width='8' height='2' rx='1' fill='white'/><rect x='3' y='10' width='6' height='2' rx='1' fill='white'/></svg>"
|
||
/>
|
||
</head>
|
||
<body>
|
||
<header class="outils">
|
||
<h1>Frise multi-projets</h1>
|
||
|
||
<!--
|
||
Deux regards sur les mêmes données : la frise, qui montre les durées et
|
||
les chevauchements, et la liste par mois, qui montre l'ordre du
|
||
calendrier tous projets mêlés. Le filtre par tags, lui, vaut pour les
|
||
deux — d'où sa place inchangée juste en dessous.
|
||
-->
|
||
<!--
|
||
Deux boutons à deux états plutôt que le motif ARIA « tablist » : celui-ci
|
||
promet une navigation aux flèches, or les flèches servent déjà à décaler
|
||
la phase sélectionnée. Mieux vaut ne rien annoncer qu'annoncer faux.
|
||
-->
|
||
<div class="onglets" id="onglets" role="group" aria-label="Vue">
|
||
<button type="button" data-vue="frise" id="onglet-frise">Frise</button>
|
||
<button type="button" data-vue="mois" id="onglet-mois">Par mois</button>
|
||
</div>
|
||
|
||
<div class="outils__groupe">
|
||
<button type="button" id="aller-aujourdhui">Aujourd'hui</button>
|
||
<button type="button" id="nouveau-projet" class="bouton--principal">
|
||
Nouveau projet
|
||
</button>
|
||
<!--
|
||
Des raccourcis d'une lettre ne se devinent pas. Ce bouton est le seul
|
||
endroit de l'écran qui dit qu'ils existent : sans lui, la moitié de ce
|
||
que l'outil sait faire au clavier resterait dans le README.
|
||
-->
|
||
<button
|
||
type="button"
|
||
id="ouvrir-aide"
|
||
class="outils__aide"
|
||
title="Raccourcis clavier (?)"
|
||
aria-label="Raccourcis clavier"
|
||
>
|
||
?
|
||
</button>
|
||
</div>
|
||
|
||
<p class="outils__etat" id="etat-sauvegarde" role="status" aria-live="polite"></p>
|
||
</header>
|
||
|
||
<!--
|
||
Filtre par état, masqué tant que tous les projets sont dans le même : lui
|
||
aussi n'apparaît que le jour où il sert.
|
||
|
||
Il ne se comporte pas comme celui des tags, et c'est pourquoi il ne lui
|
||
ressemble pas. Un projet porte plusieurs tags mais un seul état : cocher un
|
||
tag de plus resserre la sélection, cocher un état de plus l'élargit. D'où
|
||
« Afficher » ici et « Filtrer » en dessous, et deux formes distinctes —
|
||
sans quoi la même case voudrait dire deux choses opposées selon la ligne
|
||
où elle se trouve.
|
||
-->
|
||
<div class="etats" id="etats" hidden role="group" aria-label="Afficher par état">
|
||
<span class="filtres__titre">Afficher</span>
|
||
<div class="etats__liste" id="etats-liste"></div>
|
||
</div>
|
||
|
||
<!--
|
||
Barre de filtres, masquée tant qu'aucun projet ne porte de tag : elle
|
||
n'apparaît que le jour où elle sert à quelque chose.
|
||
-->
|
||
<div class="filtres" id="filtres" hidden role="group" aria-label="Filtrer par tag">
|
||
<span class="filtres__titre">Filtrer</span>
|
||
<div class="tags tags--filtres" id="filtres-liste"></div>
|
||
<button type="button" id="effacer-filtres" hidden>Tout afficher</button>
|
||
</div>
|
||
|
||
<!--
|
||
Un seul conteneur défilant, découpé en grille 2×2 : l'en-tête de dates est
|
||
collé en haut (`sticky top`), la colonne des libellés collée à gauche
|
||
(`sticky left`), et le coin aux deux. Le navigateur gère ainsi le
|
||
défilement des deux axes sans aucune synchronisation en JavaScript.
|
||
-->
|
||
<main class="frise" id="frise">
|
||
<div class="grille">
|
||
<div class="grille__coin">
|
||
<!--
|
||
Poignée de largeur de la colonne. Placée dans le coin, seul endroit
|
||
de la colonne qui reste visible quel que soit le défilement.
|
||
-->
|
||
<div
|
||
class="poignee-colonne"
|
||
id="poignee-colonne"
|
||
role="separator"
|
||
aria-orientation="vertical"
|
||
aria-label="Largeur de la colonne des projets"
|
||
tabindex="0"
|
||
title="Glisser pour élargir la colonne — double-clic pour la réinitialiser"
|
||
></div>
|
||
</div>
|
||
<div class="grille__entete" id="entete"></div>
|
||
<div class="grille__libelles" id="libelles"></div>
|
||
<div class="grille__couloirs" id="couloirs"></div>
|
||
</div>
|
||
</main>
|
||
|
||
<!--
|
||
Vue par mois. Elle remplace la frise plutôt que de la flanquer : les deux
|
||
veulent toute la largeur, l'une pour dérouler le temps, l'autre pour poser
|
||
côte à côte la date, le nom, le projet et le statut.
|
||
-->
|
||
<section class="mois" id="vue-mois" hidden aria-label="Tâches et jalons par mois"></section>
|
||
|
||
<p class="vide" id="message-vide" hidden>
|
||
Aucun projet pour l'instant.<br />
|
||
<button type="button" id="premier-projet" class="bouton--principal">
|
||
Créer un premier projet
|
||
</button>
|
||
</p>
|
||
|
||
<!--
|
||
Deux tamis peuvent vider la vue — les tags cochés, les états décochés — et
|
||
celui qui les a resserrés ne sait pas toujours lequel a fini par tout
|
||
emporter. Le bouton relâche donc les deux d'un coup.
|
||
-->
|
||
<p class="vide" id="message-filtre" hidden>
|
||
Aucun projet ne passe les filtres en cours.<br />
|
||
<button type="button" id="effacer-filtres-vide">Tout afficher</button>
|
||
</p>
|
||
|
||
<!--
|
||
Panneau de détail, ouvert au clic sur une phase. Volontairement **non
|
||
modal** : la frise reste vivante derrière lui, on passe d'une phase à
|
||
l'autre en cliquant sa barre. `tabindex="-1"` permet d'y poser le focus à
|
||
l'ouverture sans le placer dans un champ de saisie, ce qui laisse les
|
||
flèches piloter la phase sélectionnée.
|
||
-->
|
||
<aside class="panneau" id="panneau" hidden tabindex="-1" aria-label="Détail de la phase">
|
||
<!--
|
||
Poignée de largeur, sur le bord gauche. Même geste que celle de la
|
||
colonne des libellés, et même largeur pour les deux panneaux : ce qu'on
|
||
élargit, c'est le volet, pas l'un ou l'autre de ses contenus.
|
||
-->
|
||
<div
|
||
class="poignee-panneau"
|
||
data-poignee-panneau
|
||
role="separator"
|
||
aria-orientation="vertical"
|
||
aria-label="Largeur du panneau"
|
||
tabindex="0"
|
||
title="Glisser pour élargir le panneau — double-clic pour le réinitialiser"
|
||
></div>
|
||
<div class="panneau__entete">
|
||
<h2 id="panneau-titre">Phase</h2>
|
||
<button type="button" id="fermer-panneau" aria-label="Fermer">×</button>
|
||
</div>
|
||
<form class="panneau__corps" id="formulaire-phase">
|
||
<label>
|
||
Nom
|
||
<input type="text" name="name" required autocomplete="off" />
|
||
</label>
|
||
|
||
<div class="champ-double">
|
||
<label>
|
||
Début
|
||
<input type="date" name="start" required />
|
||
</label>
|
||
<label id="champ-fin">
|
||
Fin
|
||
<input type="date" name="end" required />
|
||
</label>
|
||
</div>
|
||
|
||
<label>
|
||
Statut
|
||
<select name="status">
|
||
<option value="todo">À venir</option>
|
||
<option value="doing">En cours</option>
|
||
<option value="done">Terminé</option>
|
||
<option value="blocked">Bloqué</option>
|
||
</select>
|
||
</label>
|
||
|
||
<label class="case">
|
||
<input type="checkbox" name="milestone" />
|
||
Jalon (une seule date, affichée en losange)
|
||
</label>
|
||
|
||
<!--
|
||
Les notes sont bâties par `notes.js` : aperçu markdown, barre d'outils
|
||
et zone de saisie, le tout identique dans les deux panneaux. Un seul
|
||
point d'ancrage ici plutôt que deux fois la même douzaine de balises.
|
||
-->
|
||
<section id="notes-phase"></section>
|
||
|
||
<p class="panneau__erreur" id="erreur-phase" role="alert" hidden></p>
|
||
|
||
<div class="panneau__actions">
|
||
<button type="button" id="supprimer-phase" class="bouton--danger">Supprimer</button>
|
||
</div>
|
||
</form>
|
||
</aside>
|
||
|
||
<!-- Menu contextuel, positionné à l'ouverture près de son point d'ancrage. -->
|
||
<div class="menu" id="menu" hidden role="menu"></div>
|
||
|
||
<!--
|
||
Paramètres d'un projet. Même panneau, mêmes gestes et même édition en
|
||
direct que le détail d'une phase : renommer un projet et renommer une
|
||
phase sont la même opération à un niveau près, elles n'ont pas à se
|
||
présenter différemment.
|
||
-->
|
||
<aside
|
||
class="panneau"
|
||
id="panneau-projet"
|
||
hidden
|
||
tabindex="-1"
|
||
aria-label="Paramètres du projet"
|
||
>
|
||
<div
|
||
class="poignee-panneau"
|
||
data-poignee-panneau
|
||
role="separator"
|
||
aria-orientation="vertical"
|
||
aria-label="Largeur du panneau"
|
||
tabindex="0"
|
||
title="Glisser pour élargir le panneau — double-clic pour le réinitialiser"
|
||
></div>
|
||
<div class="panneau__entete">
|
||
<h2 id="panneau-projet-titre">Projet</h2>
|
||
<button type="button" id="fermer-panneau-projet" aria-label="Fermer">×</button>
|
||
</div>
|
||
<form class="panneau__corps" id="formulaire-projet">
|
||
<!--
|
||
Où en est le projet dans son cycle de vie. En lecture seule : l'état
|
||
se déduit des données, il ne se choisit pas dans une liste. Les deux
|
||
qui s'actent — terminer, écarter — sont dans le menu ⋯ du projet, avec
|
||
les autres décisions qu'on ne prend qu'une fois.
|
||
-->
|
||
<p class="panneau__etat" id="panneau-projet-etat"></p>
|
||
|
||
<label>
|
||
Nom
|
||
<input type="text" name="name" required autocomplete="off" />
|
||
</label>
|
||
|
||
<!--
|
||
Horizon : l'ancrage temporel d'un projet envisagé, au mois. Le bloc
|
||
disparaît dès que le projet a une phase — ce sont elles qui commandent
|
||
alors son emprise (docs/decisions.md, section 27).
|
||
-->
|
||
<fieldset class="horizon" id="panneau-projet-horizon" hidden>
|
||
<legend>Horizon</legend>
|
||
<div class="horizon__intervalle">
|
||
<label>
|
||
De
|
||
<input type="month" name="horizonStart" autocomplete="off" />
|
||
</label>
|
||
<label>
|
||
à
|
||
<input type="month" name="horizonEnd" autocomplete="off" />
|
||
</label>
|
||
</div>
|
||
<!--
|
||
Un horizon se repousse par crans et jamais au jour : c'est ce qui
|
||
garde l'imprécision visible. Les mêmes crans dans les deux sens.
|
||
-->
|
||
<div class="horizon__crans" id="panneau-projet-crans">
|
||
<button type="button" data-cran="-3" title="Avancer de trois mois">−3 mois</button>
|
||
<button type="button" data-cran="3" title="Repousser de trois mois">+3 mois</button>
|
||
<button type="button" data-cran="12" title="Repousser d'un an">+1 an</button>
|
||
</div>
|
||
<p class="horizon__aide" id="panneau-projet-horizon-aide"></p>
|
||
</fieldset>
|
||
|
||
<fieldset class="couleurs">
|
||
<legend>Couleur du couloir</legend>
|
||
<div id="panneau-projet-couleurs"></div>
|
||
</fieldset>
|
||
|
||
<label>
|
||
Tags, séparés par des virgules
|
||
<input type="text" name="tags" autocomplete="off" placeholder="client, refonte" />
|
||
</label>
|
||
|
||
<!-- Masqué tant qu'aucun tag n'existe ailleurs dans le planning. -->
|
||
<fieldset class="couleurs" id="panneau-projet-suggestions-bloc" hidden>
|
||
<legend>Tags déjà utilisés</legend>
|
||
<div class="tags tags--suggestions" id="panneau-projet-suggestions"></div>
|
||
</fieldset>
|
||
|
||
<!--
|
||
Notes du projet : ce que la frise ne sait pas dire. Le contexte d'un
|
||
engagement, un interlocuteur, une décision et sa date. Elles occupent
|
||
le bas du volet, là où celles d'une phase occupent le bas du sien.
|
||
-->
|
||
<section id="notes-projet"></section>
|
||
|
||
<p class="panneau__erreur" id="erreur-projet" role="alert" hidden></p>
|
||
|
||
<!--
|
||
Les deux actes du cycle de vie. Ils sont aussi dans le menu ⋯, avec les
|
||
autres décisions qu'on ne prend qu'une fois — mais c'est ici qu'on lit
|
||
l'état du projet, donc ici qu'on cherche à en changer.
|
||
-->
|
||
<div class="panneau__actions panneau__actions--cycle">
|
||
<button type="button" id="terminer-projet"></button>
|
||
<button type="button" id="ecarter-projet"></button>
|
||
</div>
|
||
|
||
<div class="panneau__actions">
|
||
<button type="button" id="supprimer-projet" class="bouton--danger">
|
||
Supprimer le projet
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</aside>
|
||
|
||
<!-- Confirmation d'une action irréversible. -->
|
||
<dialog class="dialogue" id="dialogue-confirmation">
|
||
<form method="dialog">
|
||
<h2 id="dialogue-confirmation-titre">Confirmer</h2>
|
||
<p id="dialogue-confirmation-message"></p>
|
||
<menu class="dialogue__actions">
|
||
<button type="submit" value="annuler">Annuler</button>
|
||
<button type="submit" value="ok" class="bouton--principal" id="dialogue-confirmation-ok">
|
||
Confirmer
|
||
</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<!--
|
||
Aide des raccourcis, ouverte par `?`.
|
||
|
||
Un `<dialog>` modal, contrairement aux deux panneaux : celui-ci ne sert
|
||
qu'à lire, et le clavier doit y être *rendu* — laisser `j` déplacer le
|
||
curseur derrière la table qui explique `j` serait absurde. La modalité
|
||
apporte aussi le voile, le piège à focus et la fermeture par Échap.
|
||
|
||
La table est écrite à la main plutôt que bâtie depuis la table des
|
||
séquences de clavier.js : celle-ci associe une touche à un nom d'action, ce
|
||
qui ne dit ni ce que l'action fait, ni dans quel ordre la présenter, ni
|
||
quelles touches vont par paires. Une génération automatique produirait une
|
||
liste exacte et illisible.
|
||
-->
|
||
<dialog class="dialogue dialogue--aide" id="dialogue-aide" aria-labelledby="aide-titre">
|
||
<form method="dialog">
|
||
<h2 id="aide-titre">Raccourcis clavier</h2>
|
||
|
||
<div class="aide__colonnes">
|
||
<section>
|
||
<h3>Déplacer le curseur</h3>
|
||
<dl class="aide">
|
||
<dt><kbd>j</kbd> <kbd>k</kbd></dt>
|
||
<dd>Ligne suivante, précédente</dd>
|
||
<dt><kbd>h</kbd> <kbd>l</kbd></dt>
|
||
<dd>Phase précédente, suivante du même projet</dd>
|
||
<dt><kbd>{</kbd> <kbd>}</kbd></dt>
|
||
<dd>Projet précédent, suivant</dd>
|
||
<dt><kbd>gg</kbd> <kbd>G</kbd></dt>
|
||
<dd>Première, dernière ligne</dd>
|
||
</dl>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Modifier la phase du curseur</h3>
|
||
<dl class="aide">
|
||
<dt><kbd>←</kbd> <kbd>→</kbd></dt>
|
||
<dd>Décale d'un jour</dd>
|
||
<dt><kbd>Maj</kbd>+<kbd>←</kbd> <kbd>→</kbd></dt>
|
||
<dd>Allonge, raccourcit d'un jour</dd>
|
||
<dt><kbd>Espace</kbd></dt>
|
||
<dd>Statut suivant : à venir, en cours, terminé, bloqué</dd>
|
||
<dt><kbd>Entrée</kbd> <kbd>e</kbd></dt>
|
||
<dd>Ouvre le volet et saisit le nom</dd>
|
||
<dt><kbd>dd</kbd></dt>
|
||
<dd>Supprime la phase, ou le projet depuis sa ligne</dd>
|
||
</dl>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Ajouter</h3>
|
||
<dl class="aide">
|
||
<dt><kbd>A</kbd></dt>
|
||
<dd>Une phase à la fin du projet</dd>
|
||
<dt><kbd>a</kbd></dt>
|
||
<dd>Une phase au début du projet</dd>
|
||
<dt><kbd>o</kbd></dt>
|
||
<dd>Une phase juste après celle du curseur</dd>
|
||
<dt><kbd>O</kbd></dt>
|
||
<dd>Une phase juste avant elle</dd>
|
||
<dt><kbd>P</kbd></dt>
|
||
<dd>Un projet</dd>
|
||
</dl>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Plier, cadrer, naviguer</h3>
|
||
<dl class="aide">
|
||
<dt><kbd>za</kbd></dt>
|
||
<dd>Plie ou déplie le projet</dd>
|
||
<dt><kbd>zR</kbd> <kbd>zM</kbd></dt>
|
||
<dd>Déplie tout, plie tout</dd>
|
||
<dt><kbd>zz</kbd></dt>
|
||
<dd>Recentre la frise sur le curseur</dd>
|
||
<dt><kbd>w</kbd> <kbd>b</kbd></dt>
|
||
<dd>Un mois plus tard, plus tôt</dd>
|
||
<dt><kbd>W</kbd> <kbd>B</kbd></dt>
|
||
<dd>Un an plus tard, plus tôt</dd>
|
||
<dt><kbd>ga</kbd></dt>
|
||
<dd>Revient à aujourd'hui</dd>
|
||
<dt><kbd>gm</kbd></dt>
|
||
<dd>Bascule frise et vue par mois</dd>
|
||
<dt><kbd>Maj</kbd>+molette</dt>
|
||
<dd>Fait défiler la frise horizontalement</dd>
|
||
</dl>
|
||
</section>
|
||
</div>
|
||
|
||
<p class="aide__note">
|
||
Toutes ces touches se taisent pendant une saisie : dans un champ, une
|
||
note ou un dialogue, elles écrivent la lettre qu'elles portent.
|
||
<kbd>Échap</kbd> referme le volet ouvert sans déplacer le curseur.
|
||
</p>
|
||
|
||
<menu class="dialogue__actions">
|
||
<button type="submit" value="ok" class="bouton--principal">Fermer</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<!-- Bandeau d'erreur bloquante : fichier corrompu, serveur injoignable. -->
|
||
<div class="alerte" id="alerte" hidden role="alert">
|
||
<p id="alerte-message"></p>
|
||
</div>
|
||
|
||
<script type="module" src="js/app.js"></script>
|
||
</body>
|
||
</html>
|