Phase 2 : mind map bilaterale, relations, templates et Markdown

Format .mmap v4 : `node.side` (cote d'une branche de niveau 1) et
`MindMap.relations` (relations secondaires). Migration v3 -> v4 sans perte, les
cartes v1/v2/v3 restent ouvrables telles quelles.

Une relation est une connexion purement visuelle : elle ne touche jamais
parentId, order, les enfants, le repli ni l'auto-layout. `withEdges()` continue
de deriver les liens de parentId et se contente de retirer les relations dont
une extremite a disparu.

Mind map bilaterale : repartition gloutonne et deterministe des branches selon
la taille des sous-arbres, heritage du cote par sous-arbre, deux passes dagre
recalees sur la racine. Choisir une disposition ne deplace plus la carte : seul
Reorganiser applique.

Ajoute aussi une galerie de huit modeles locaux, l'import Markdown (fichier ou
texte, nouvelle carte ou sous-arbre) reutilisant l'analyseur d'outline existant,
et l'export Markdown de la hierarchie.

Corrige un bug de rendu : React Flow ne remesure pas les poignees quand elles
changent de cote, ce qui faisait disparaitre les liens concernes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 08:12:00 +02:00
parent 2afbf637ce
commit e3c530348e
33 changed files with 4151 additions and 104 deletions

View File

@@ -28,6 +28,7 @@ import {
} from '@/features/editor/nodeStyleResolver';
import { BUILTIN_ICONS, BUILTIN_ICON_IDS, EMOJI_ICONS } from '@/features/editor/nodeIcons';
import { PanelSection } from '@/features/editor/panel/PanelSection';
import { RelationProperties } from '@/features/editor/RelationProperties';
import {
ColorField,
NumberField,
@@ -90,6 +91,7 @@ const ICON_SIZES = [16, 20, 24, 32];
export function PropertiesPanel() {
const map = useEditorStore((state) => state.map);
const selectedIds = useEditorStore((state) => state.selectedNodeIds);
const selectedRelationId = useEditorStore((state) => state.selectedRelationId);
const updateNode = useEditorStore((state) => state.updateNode);
const updateNodeText = useEditorStore((state) => state.updateNodeText);
@@ -135,6 +137,8 @@ export function PropertiesPanel() {
const palette = paletteForDepth(theme, referenceDepth);
const reference = single ? resolveNodeAppearance(single, referenceDepth, theme) : undefined;
const relation = map?.relations.find((candidate) => candidate.id === selectedRelationId);
const header = (
<header className="flex items-center justify-between gap-2 border-b border-zinc-100 px-4 py-3 dark:border-zinc-800">
<h2 className="text-sm font-semibold text-zinc-900 dark:text-zinc-50">
@@ -153,6 +157,20 @@ export function PropertiesPanel() {
</header>
);
// Une relation selectionnee prend la place du panneau de noeud : les deux
// selections s'excluent, le panneau ne montre donc jamais les deux a la fois.
if (nodes.length === 0 && relation) {
return (
<aside
className="no-print flex h-full w-72 shrink-0 flex-col overflow-y-auto border-l border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900"
aria-label="Panneau de proprietes"
>
{header}
<RelationProperties relationId={relation.id} />
</aside>
);
}
if (nodes.length === 0) {
return (
<aside
@@ -162,7 +180,8 @@ export function PropertiesPanel() {
{header}
<p className="px-4 py-6 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400">
Selectionnez un noeud pour modifier son contenu, sa typographie, son apparence et sa mise
en page. Selectionnez-en plusieurs pour appliquer un reglage a tout le lot.
en page. Selectionnez-en plusieurs pour appliquer un reglage a tout le lot. Cliquez une
relation pour en modifier le libelle et le trace.
</p>
</aside>
);