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:
@@ -15,6 +15,8 @@ export interface NodeContextMenuProps {
|
||||
onClose: () => void;
|
||||
onAddChildren: (parentId: string) => void;
|
||||
onMoveTo: (nodeId: string) => void;
|
||||
onAddRelation: (nodeId: string) => void;
|
||||
onImportMarkdown: (nodeId: string) => void;
|
||||
}
|
||||
|
||||
interface MenuEntry {
|
||||
@@ -34,7 +36,14 @@ interface MenuHeading {
|
||||
type MenuLine = MenuEntry | MenuHeading;
|
||||
|
||||
/** Menu contextuel d'un noeud, organise par famille d'action. */
|
||||
export function NodeContextMenu({ state, onClose, onAddChildren, onMoveTo }: NodeContextMenuProps) {
|
||||
export function NodeContextMenu({
|
||||
state,
|
||||
onClose,
|
||||
onAddChildren,
|
||||
onMoveTo,
|
||||
onAddRelation,
|
||||
onImportMarkdown,
|
||||
}: NodeContextMenuProps) {
|
||||
const map = useEditorStore((store) => store.map);
|
||||
const selectedIds = useEditorStore((store) => store.selectedNodeIds);
|
||||
const hasStyleClipboard = useEditorStore((store) => store.styleClipboard !== null);
|
||||
@@ -72,6 +81,14 @@ export function NodeContextMenu({ state, onClose, onAddChildren, onMoveTo }: Nod
|
||||
const hasChildren = map.nodes.some((candidate) => candidate.parentId === node.id);
|
||||
const notify = (message: string) => pushToast(message, 'success');
|
||||
|
||||
// Le cote ne se regle que sur une branche de niveau 1 : ailleurs, il est
|
||||
// herite de la branche, il n'y a donc rien a choisir.
|
||||
const parent = node.parentId
|
||||
? map.nodes.find((candidate) => candidate.id === node.parentId)
|
||||
: undefined;
|
||||
const isBranchRoot = Boolean(parent && !parent.parentId);
|
||||
const bilateral = map.settings.layout === 'mindmap-both';
|
||||
|
||||
return [
|
||||
{ kind: 'heading', label: 'Ajouter' },
|
||||
{ kind: 'item', label: 'Ajouter un enfant', hint: 'Tab', run: () => store.addChild(node.id) },
|
||||
@@ -94,6 +111,18 @@ export function NodeContextMenu({ state, onClose, onAddChildren, onMoveTo }: Nod
|
||||
disabled: !node.parentId,
|
||||
run: () => store.addSiblingAt(node.id, 'after'),
|
||||
},
|
||||
{
|
||||
kind: 'item',
|
||||
label: 'Importer Markdown ici...',
|
||||
run: () => onImportMarkdown(node.id),
|
||||
},
|
||||
|
||||
{ kind: 'heading', label: 'Relation' },
|
||||
{
|
||||
kind: 'item',
|
||||
label: 'Ajouter une relation...',
|
||||
run: () => onAddRelation(node.id),
|
||||
},
|
||||
|
||||
{ kind: 'heading', label: 'Branche' },
|
||||
{
|
||||
@@ -125,6 +154,28 @@ export function NodeContextMenu({ state, onClose, onAddChildren, onMoveTo }: Nod
|
||||
run: () => store.setBranchCollapsed(node.id, !node.collapsed),
|
||||
},
|
||||
|
||||
{ kind: 'heading', label: 'Cote' },
|
||||
{
|
||||
kind: 'item',
|
||||
label: 'Placer la branche a gauche',
|
||||
hint: bilateral ? undefined : 'bilaterale',
|
||||
disabled: !isBranchRoot || node.side === 'left',
|
||||
run: () => {
|
||||
store.setNodeSide(node.id, 'left');
|
||||
notify('Branche placee a gauche. Relancez l organisation pour la deplacer.');
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'item',
|
||||
label: 'Placer la branche a droite',
|
||||
hint: bilateral ? undefined : 'bilaterale',
|
||||
disabled: !isBranchRoot || node.side === 'right',
|
||||
run: () => {
|
||||
store.setNodeSide(node.id, 'right');
|
||||
notify('Branche placee a droite. Relancez l organisation pour la deplacer.');
|
||||
},
|
||||
},
|
||||
|
||||
{ kind: 'heading', label: 'Ordre' },
|
||||
{
|
||||
kind: 'item',
|
||||
@@ -204,6 +255,8 @@ export function NodeContextMenu({ state, onClose, onAddChildren, onMoveTo }: Nod
|
||||
hasNodeClipboard,
|
||||
onAddChildren,
|
||||
onMoveTo,
|
||||
onAddRelation,
|
||||
onImportMarkdown,
|
||||
pushToast,
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user