Phase 1.1.1 : consolidation et coherence UX
Trois corrections ciblees, sans changement d'architecture ni d'interface. 1. Le style ne transporte plus l'icone L'icone d'un noeud est du contenu, comme son texte ou sa note. Copier le style de "Reseau" et le coller sur "Stockage" et "Securite" applique desormais les couleurs, la forme et la mise en page en laissant chaque noeud avec sa propre icone. Seule la presentation de l'icone (position, taille, espacement) reste une propriete de style. 2. Le cercle conserve un rapport 1:1 `keepAspectRatio` sur le redimensionneur, et contrainte cote store pour les trois autres chemins : redimensionnement direct, saisie d'une largeur ou d'une hauteur dans le panneau, et conversion d'un noeud rectangulaire en cercle. Un cercle ne peut plus devenir un ovale. 3. Point d'entree unique des documents `normalizeMapDocument()` remplace `parseMapDocument()` et devient le seul chemin par lequel une carte entre dans l'editeur, qu'elle vienne d'un fichier .mmap, d'IndexedDB ou d'un partage. Le pipeline est toujours validation -> migration -> normalisation. Les cartes locales passaient jusqu'ici a cote de la migration : elles y passent maintenant a l'ouverture, sans etre reecrites en base tant que l'utilisateur ne les modifie pas. Une carte locale videe de ses noeuds reste ouvrable, et le message d'erreur s'adapte a la provenance. Egalement - Zoom minimum abaisse de 0.1 a 0.02 : "Adapter a l'ecran" butait sur ce plancher et ne montrait pas toute une carte de 181 noeuds. - README : pipeline de normalisation, comportement du style et du cercle, limitations mesurees. Tests : 103 vitest (85 -> 103) et 31 pytest, ruff, eslint et tsc au vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -345,14 +345,48 @@ function migrate(raw: Record<string, unknown>, version: number): Record<string,
|
||||
return document;
|
||||
}
|
||||
|
||||
/** Provenance d'un document : ne sert qu'a formuler les messages d'erreur. */
|
||||
export type MapDocumentSource = 'file' | 'storage' | 'share';
|
||||
|
||||
export interface NormalizeOptions {
|
||||
maxNodes?: number;
|
||||
source?: MapDocumentSource;
|
||||
/**
|
||||
* Autorise un document sans noeud. Vrai pour les cartes deja enregistrees :
|
||||
* l'utilisateur a le droit d'avoir vide sa carte, et la rejeter reviendrait a
|
||||
* la lui rendre inaccessible.
|
||||
*/
|
||||
allowEmpty?: boolean;
|
||||
}
|
||||
|
||||
const INVALID_DOCUMENT_MESSAGE: Record<MapDocumentSource, string> = {
|
||||
file: "Le fichier selectionne n'est pas une carte valide.",
|
||||
storage: "Cette carte enregistree est illisible et n'a pas pu etre ouverte.",
|
||||
share: "Cette carte partagee est illisible.",
|
||||
};
|
||||
|
||||
/**
|
||||
* Valide et normalise un document carte provenant d'un fichier ou du serveur.
|
||||
* Cette fonction ne fait qu'analyser des donnees : rien n'est jamais evalue,
|
||||
* ni injecte en HTML.
|
||||
* Point d'entree unique des documents carte.
|
||||
*
|
||||
* Toute carte qui entre dans l'editeur passe par ici, quelle que soit sa
|
||||
* provenance : fichier .mmap, IndexedDB ou partage serveur. Le pipeline est
|
||||
* toujours le meme :
|
||||
*
|
||||
* source -> validation -> migration de version -> normalisation -> modele courant
|
||||
*
|
||||
* La normalisation est non destructive : le document d'origine n'est jamais
|
||||
* modifie, et rien n'est reecrit en base. Une carte v1 restee en IndexedDB
|
||||
* continue donc de s'ouvrir exactement comme avant, et n'est reecrite que si
|
||||
* l'utilisateur la modifie.
|
||||
*
|
||||
* Cette fonction ne fait qu'analyser des donnees : rien n'est jamais evalue ni
|
||||
* injecte en HTML.
|
||||
*/
|
||||
export function parseMapDocument(raw: unknown, options: { maxNodes?: number } = {}): MindMap {
|
||||
export function normalizeMapDocument(raw: unknown, options: NormalizeOptions = {}): MindMap {
|
||||
const source = options.source ?? 'file';
|
||||
|
||||
if (!isRecord(raw)) {
|
||||
throw new MapFormatError("Le fichier selectionne n'est pas une carte valide.");
|
||||
throw new MapFormatError(INVALID_DOCUMENT_MESSAGE[source]);
|
||||
}
|
||||
|
||||
const version = raw.version;
|
||||
@@ -372,7 +406,7 @@ export function parseMapDocument(raw: unknown, options: { maxNodes?: number } =
|
||||
const document = migrate(raw, version);
|
||||
|
||||
if (!Array.isArray(document.nodes)) {
|
||||
throw new MapFormatError("Le fichier selectionne n'est pas une carte valide (noeuds absents).");
|
||||
throw new MapFormatError(`${INVALID_DOCUMENT_MESSAGE[source]} (noeuds absents)`);
|
||||
}
|
||||
|
||||
const maxNodes = options.maxNodes ?? APP_CONFIG.maxNodes;
|
||||
@@ -395,7 +429,7 @@ export function parseMapDocument(raw: unknown, options: { maxNodes?: number } =
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
if (nodes.length === 0) {
|
||||
if (nodes.length === 0 && !options.allowEmpty) {
|
||||
throw new MapFormatError('Cette carte ne contient aucun noeud exploitable.');
|
||||
}
|
||||
|
||||
@@ -416,7 +450,7 @@ export function parseMapDocument(raw: unknown, options: { maxNodes?: number } =
|
||||
}));
|
||||
|
||||
// Au moins une racine explicite pour que l'editeur reste coherent.
|
||||
if (!linked.some((node) => node.type === 'root') && linked[0]) {
|
||||
if (linked.length > 0 && !linked.some((node) => node.type === 'root') && linked[0]) {
|
||||
linked[0] = { ...linked[0], type: 'root', parentId: null };
|
||||
}
|
||||
|
||||
@@ -434,7 +468,7 @@ export function parseMapDocument(raw: unknown, options: { maxNodes?: number } =
|
||||
viewport: {
|
||||
x: asFiniteNumber(rawViewport.x, 0),
|
||||
y: asFiniteNumber(rawViewport.y, 0),
|
||||
zoom: Math.min(4, Math.max(0.1, asFiniteNumber(rawViewport.zoom, 1))),
|
||||
zoom: Math.min(4, Math.max(0.02, asFiniteNumber(rawViewport.zoom, 1))),
|
||||
},
|
||||
settings: parseSettings(document.settings),
|
||||
};
|
||||
@@ -456,5 +490,5 @@ export async function readMapFile(
|
||||
} catch {
|
||||
throw new MapFormatError("Le fichier selectionne n'est pas une carte valide (JSON illisible).");
|
||||
}
|
||||
return parseMapDocument(raw);
|
||||
return normalizeMapDocument(raw, { source: 'file' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user