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:
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MAP_FORMAT_VERSION, type MindMap } from '@/types/map';
|
||||
import { createEmptyMap } from '@/services/mapFactory';
|
||||
import { parseMapDocument, serializeMap } from '@/services/mapSerialization';
|
||||
import { normalizeMapDocument, serializeMap } from '@/services/mapSerialization';
|
||||
|
||||
/** Carte v1 telle qu'elle etait exportee avant la phase 1.1. */
|
||||
const LEGACY_V1 = {
|
||||
@@ -29,7 +29,7 @@ const LEGACY_V1 = {
|
||||
|
||||
describe('compatibilite du format', () => {
|
||||
it('importe une carte v1 sans propriete de mise en page', () => {
|
||||
const map = parseMapDocument(LEGACY_V1);
|
||||
const map = normalizeMapDocument(LEGACY_V1);
|
||||
|
||||
expect(map.version).toBe(MAP_FORMAT_VERSION);
|
||||
expect(map.nodes).toHaveLength(2);
|
||||
@@ -39,7 +39,7 @@ describe('compatibilite du format', () => {
|
||||
});
|
||||
|
||||
it('migre la largeur v1 vers layout.width', () => {
|
||||
const map = parseMapDocument(LEGACY_V1);
|
||||
const map = normalizeMapDocument(LEGACY_V1);
|
||||
const root = map.nodes[0];
|
||||
|
||||
expect(root?.layout?.widthMode).toBe('fixed');
|
||||
@@ -52,7 +52,7 @@ describe('compatibilite du format', () => {
|
||||
});
|
||||
|
||||
it('refuse une version plus recente que celle supportee', () => {
|
||||
expect(() => parseMapDocument({ ...LEGACY_V1, version: MAP_FORMAT_VERSION + 1 })).toThrow(
|
||||
expect(() => normalizeMapDocument({ ...LEGACY_V1, version: MAP_FORMAT_VERSION + 1 })).toThrow(
|
||||
/plus recente/i,
|
||||
);
|
||||
});
|
||||
@@ -107,7 +107,7 @@ describe('aller-retour des nouvelles proprietes', () => {
|
||||
|
||||
it('conserve toute la mise en page apres export puis import', () => {
|
||||
const original = richMap();
|
||||
const restored = parseMapDocument(JSON.parse(serializeMap(original)));
|
||||
const restored = normalizeMapDocument(JSON.parse(serializeMap(original)));
|
||||
const node = restored.nodes[0];
|
||||
const source = original.nodes[0];
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('aller-retour des nouvelles proprietes', () => {
|
||||
});
|
||||
|
||||
it('borne les valeurs aberrantes a l import', () => {
|
||||
const map = parseMapDocument({
|
||||
const map = normalizeMapDocument({
|
||||
version: 2,
|
||||
id: 'x',
|
||||
nodes: [
|
||||
@@ -149,7 +149,7 @@ describe('aller-retour des nouvelles proprietes', () => {
|
||||
});
|
||||
|
||||
it('rejette les valeurs de mise en page invalides', () => {
|
||||
const map = parseMapDocument({
|
||||
const map = normalizeMapDocument({
|
||||
version: 2,
|
||||
id: 'x',
|
||||
nodes: [
|
||||
|
||||
137
frontend/src/services/mapNormalization.test.ts
Normal file
137
frontend/src/services/mapNormalization.test.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { MAP_FORMAT_VERSION, type MindMap } from '@/types/map';
|
||||
import { db } from '@/db/database';
|
||||
import { createMap, getMap, importMap } from '@/services/mapRepository';
|
||||
import {
|
||||
MapFormatError,
|
||||
normalizeMapDocument,
|
||||
serializeMap,
|
||||
} from '@/services/mapSerialization';
|
||||
|
||||
/** Carte v1 telle qu'elle pouvait etre enregistree en IndexedDB avant la v2. */
|
||||
function legacyStoredMap(id: string): Record<string, unknown> {
|
||||
return {
|
||||
version: 1,
|
||||
id,
|
||||
name: 'Homelab v1',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
updatedAt: '2026-01-02T00:00:00.000Z',
|
||||
nodes: [
|
||||
{
|
||||
id: 'root',
|
||||
type: 'root',
|
||||
text: 'Infrastructure',
|
||||
position: { x: 0, y: 0 },
|
||||
parentId: null,
|
||||
icon: '🖥️',
|
||||
style: { backgroundColor: '#eef2ff', width: 240, textAlign: 'center' },
|
||||
},
|
||||
{
|
||||
id: 'n1',
|
||||
type: 'standard',
|
||||
text: 'Reseau',
|
||||
position: { x: 260, y: 0 },
|
||||
parentId: 'root',
|
||||
},
|
||||
],
|
||||
edges: [{ id: 'e-root-n1', source: 'root', target: 'n1' }],
|
||||
viewport: { x: 12, y: 34, zoom: 1.5 },
|
||||
settings: { theme: 'ocean', layout: 'tree-vertical', branchStyle: 'curved', showGrid: false },
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.maps.clear();
|
||||
await db.shares.clear();
|
||||
await db.meta.clear();
|
||||
});
|
||||
|
||||
describe('pipeline de normalisation', () => {
|
||||
it('normalise une carte v1 venue d IndexedDB comme un fichier v1', async () => {
|
||||
await db.maps.put(legacyStoredMap('stored-1') as unknown as MindMap);
|
||||
|
||||
const fromStorage = await getMap('stored-1');
|
||||
const fromFile = normalizeMapDocument(legacyStoredMap('stored-1'), { source: 'file' });
|
||||
|
||||
expect(fromStorage).toBeDefined();
|
||||
expect(fromStorage?.nodes).toEqual(fromFile.nodes);
|
||||
expect(fromStorage?.settings).toEqual(fromFile.settings);
|
||||
expect(fromStorage?.version).toBe(MAP_FORMAT_VERSION);
|
||||
});
|
||||
|
||||
it('migre la largeur v1 d une carte stockee sans la reecrire en base', async () => {
|
||||
await db.maps.put(legacyStoredMap('stored-2') as unknown as MindMap);
|
||||
|
||||
const opened = await getMap('stored-2');
|
||||
expect(opened?.nodes[0]?.layout?.width).toBe(240);
|
||||
expect(opened?.nodes[0]?.layout?.widthMode).toBe('fixed');
|
||||
expect(opened?.nodes[0]?.style?.width).toBeUndefined();
|
||||
|
||||
// La base contient toujours le document d'origine : rien n'a ete reecrit.
|
||||
const raw = (await db.maps.get('stored-2')) as unknown as Record<string, unknown>;
|
||||
expect(raw.version).toBe(1);
|
||||
expect((raw.nodes as Array<Record<string, unknown>>)[0]?.style).toMatchObject({ width: 240 });
|
||||
});
|
||||
|
||||
it('conserve le contenu et les reglages d une carte v1', async () => {
|
||||
await db.maps.put(legacyStoredMap('stored-3') as unknown as MindMap);
|
||||
const opened = await getMap('stored-3');
|
||||
|
||||
expect(opened?.name).toBe('Homelab v1');
|
||||
expect(opened?.createdAt).toBe('2026-01-01T00:00:00.000Z');
|
||||
expect(opened?.updatedAt).toBe('2026-01-02T00:00:00.000Z');
|
||||
expect(opened?.nodes[0]?.icon).toBe('🖥️');
|
||||
expect(opened?.nodes[0]?.style?.textAlign).toBe('center');
|
||||
expect(opened?.settings.theme).toBe('ocean');
|
||||
expect(opened?.settings.layout).toBe('tree-vertical');
|
||||
expect(opened?.viewport).toEqual({ x: 12, y: 34, zoom: 1.5 });
|
||||
});
|
||||
|
||||
it('ne modifie jamais le document fourni', () => {
|
||||
const original = legacyStoredMap('immutable');
|
||||
const copy = JSON.parse(JSON.stringify(original));
|
||||
|
||||
normalizeMapDocument(original, { source: 'storage' });
|
||||
|
||||
expect(original).toEqual(copy);
|
||||
});
|
||||
|
||||
it('laisse une carte v2 inchangee', () => {
|
||||
const v2 = normalizeMapDocument(legacyStoredMap('x'), { source: 'file' });
|
||||
const again = normalizeMapDocument(JSON.parse(serializeMap(v2)), { source: 'file' });
|
||||
|
||||
expect(again.nodes).toEqual(v2.nodes);
|
||||
expect(again.settings).toEqual(v2.settings);
|
||||
expect(again.version).toBe(MAP_FORMAT_VERSION);
|
||||
});
|
||||
|
||||
it('accepte une carte locale videe de ses noeuds', async () => {
|
||||
const map = await createMap('Carte videe');
|
||||
await db.maps.put({ ...map, nodes: [], edges: [] });
|
||||
|
||||
const opened = await getMap(map.id);
|
||||
expect(opened?.nodes).toHaveLength(0);
|
||||
expect(opened?.name).toBe('Carte videe');
|
||||
});
|
||||
|
||||
it('refuse un fichier sans noeud exploitable', () => {
|
||||
expect(() => normalizeMapDocument({ version: 1, id: 'x', nodes: [] })).toThrow(MapFormatError);
|
||||
});
|
||||
|
||||
it('adapte le message d erreur a la provenance', () => {
|
||||
expect(() => normalizeMapDocument('pas un objet', { source: 'file' })).toThrow(/fichier/i);
|
||||
expect(() => normalizeMapDocument('pas un objet', { source: 'storage' })).toThrow(
|
||||
/enregistree/i,
|
||||
);
|
||||
expect(() => normalizeMapDocument('pas un objet', { source: 'share' })).toThrow(/partagee/i);
|
||||
});
|
||||
|
||||
it('fait passer un import par le meme pipeline', async () => {
|
||||
const normalised = normalizeMapDocument(legacyStoredMap('file-1'), { source: 'file' });
|
||||
const stored = await importMap(normalised);
|
||||
const reopened = await getMap(stored.id);
|
||||
|
||||
expect(reopened?.nodes).toEqual(normalised.nodes);
|
||||
expect(reopened?.version).toBe(MAP_FORMAT_VERSION);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { type MindMap, type MapSummary } from '@/types/map';
|
||||
import { cloneMap, createEmptyMap, createSampleMap } from '@/services/mapFactory';
|
||||
import { createId } from '@/utils/id';
|
||||
import { deleteShareRecord, getShareRecord, listShareRecords } from '@/services/shareRepository';
|
||||
import { normalizeMapDocument } from '@/services/mapSerialization';
|
||||
|
||||
/** Toutes les cartes locales, de la plus recemment modifiee a la plus ancienne. */
|
||||
export async function listMaps(): Promise<MapSummary[]> {
|
||||
@@ -21,8 +22,17 @@ export async function listMaps(): Promise<MapSummary[]> {
|
||||
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit une carte locale et la fait passer par le pipeline de normalisation.
|
||||
*
|
||||
* Les cartes enregistrees avant une evolution de format sont ainsi ramenees au
|
||||
* modele courant a l'ouverture, sans etre reecrites en base : la migration
|
||||
* n'est persistee que si l'utilisateur modifie la carte.
|
||||
*/
|
||||
export async function getMap(id: string): Promise<MindMap | undefined> {
|
||||
return db.maps.get(id);
|
||||
const stored = await db.maps.get(id);
|
||||
if (!stored) return undefined;
|
||||
return normalizeMapDocument(stored, { source: 'storage', allowEmpty: true });
|
||||
}
|
||||
|
||||
export async function saveMap(map: MindMap): Promise<void> {
|
||||
|
||||
@@ -3,14 +3,14 @@ import { MAP_FORMAT_VERSION } from '@/types/map';
|
||||
import { createSampleMap } from '@/services/mapFactory';
|
||||
import {
|
||||
MapFormatError,
|
||||
parseMapDocument,
|
||||
normalizeMapDocument,
|
||||
serializeMap,
|
||||
} from '@/services/mapSerialization';
|
||||
|
||||
describe('serialisation des cartes', () => {
|
||||
it('effectue un aller-retour sans perte', () => {
|
||||
const original = createSampleMap();
|
||||
const restored = parseMapDocument(JSON.parse(serializeMap(original)));
|
||||
const restored = normalizeMapDocument(JSON.parse(serializeMap(original)));
|
||||
|
||||
expect(restored.name).toBe(original.name);
|
||||
expect(restored.nodes).toHaveLength(original.nodes.length);
|
||||
@@ -20,25 +20,25 @@ describe('serialisation des cartes', () => {
|
||||
});
|
||||
|
||||
it('rejette un contenu qui n est pas un objet', () => {
|
||||
expect(() => parseMapDocument('pas une carte')).toThrow(MapFormatError);
|
||||
expect(() => parseMapDocument(null)).toThrow(MapFormatError);
|
||||
expect(() => parseMapDocument([1, 2, 3])).toThrow(MapFormatError);
|
||||
expect(() => normalizeMapDocument('pas une carte')).toThrow(MapFormatError);
|
||||
expect(() => normalizeMapDocument(null)).toThrow(MapFormatError);
|
||||
expect(() => normalizeMapDocument([1, 2, 3])).toThrow(MapFormatError);
|
||||
});
|
||||
|
||||
it('rejette une carte sans version', () => {
|
||||
const map = createSampleMap() as unknown as Record<string, unknown>;
|
||||
delete map.version;
|
||||
expect(() => parseMapDocument(map)).toThrow(/version/i);
|
||||
expect(() => normalizeMapDocument(map)).toThrow(/version/i);
|
||||
});
|
||||
|
||||
it('rejette une version de format plus recente', () => {
|
||||
const map = { ...createSampleMap(), version: MAP_FORMAT_VERSION + 5 };
|
||||
expect(() => parseMapDocument(map)).toThrow(/plus recente/i);
|
||||
expect(() => normalizeMapDocument(map)).toThrow(/plus recente/i);
|
||||
});
|
||||
|
||||
it('rejette une carte sans noeud exploitable', () => {
|
||||
expect(() => parseMapDocument({ version: 1, id: 'x', nodes: [] })).toThrow(MapFormatError);
|
||||
expect(() => parseMapDocument({ version: 1, id: 'x', nodes: [{ text: 'sans id' }] })).toThrow(
|
||||
expect(() => normalizeMapDocument({ version: 1, id: 'x', nodes: [] })).toThrow(MapFormatError);
|
||||
expect(() => normalizeMapDocument({ version: 1, id: 'x', nodes: [{ text: 'sans id' }] })).toThrow(
|
||||
MapFormatError,
|
||||
);
|
||||
});
|
||||
@@ -49,13 +49,13 @@ describe('serialisation des cartes', () => {
|
||||
text: `Noeud ${index}`,
|
||||
parentId: index === 0 ? null : 'n0',
|
||||
}));
|
||||
expect(() => parseMapDocument({ version: 1, id: 'x', nodes }, { maxNodes: 5 })).toThrow(
|
||||
expect(() => normalizeMapDocument({ version: 1, id: 'x', nodes }, { maxNodes: 5 })).toThrow(
|
||||
/limite/i,
|
||||
);
|
||||
});
|
||||
|
||||
it('neutralise les URL dangereuses', () => {
|
||||
const parsed = parseMapDocument({
|
||||
const parsed = normalizeMapDocument({
|
||||
version: 1,
|
||||
id: 'x',
|
||||
name: 'Test',
|
||||
@@ -72,7 +72,7 @@ describe('serialisation des cartes', () => {
|
||||
});
|
||||
|
||||
it('conserve le texte tel quel sans interpreter le HTML', () => {
|
||||
const parsed = parseMapDocument({
|
||||
const parsed = normalizeMapDocument({
|
||||
version: 1,
|
||||
id: 'x',
|
||||
nodes: [{ id: 'a', type: 'root', text: '<img src=x onerror=alert(1)>' }],
|
||||
@@ -81,7 +81,7 @@ describe('serialisation des cartes', () => {
|
||||
});
|
||||
|
||||
it('reconstruit la hierarchie a partir des liens', () => {
|
||||
const parsed = parseMapDocument({
|
||||
const parsed = normalizeMapDocument({
|
||||
version: 1,
|
||||
id: 'x',
|
||||
nodes: [
|
||||
@@ -97,7 +97,7 @@ describe('serialisation des cartes', () => {
|
||||
});
|
||||
|
||||
it('ignore les liens pointant vers des noeuds inexistants', () => {
|
||||
const parsed = parseMapDocument({
|
||||
const parsed = normalizeMapDocument({
|
||||
version: 1,
|
||||
id: 'x',
|
||||
nodes: [{ id: 'a', type: 'root', text: 'Racine' }],
|
||||
@@ -107,7 +107,7 @@ describe('serialisation des cartes', () => {
|
||||
});
|
||||
|
||||
it('ignore les styles invalides mais garde les styles valides', () => {
|
||||
const parsed = parseMapDocument({
|
||||
const parsed = normalizeMapDocument({
|
||||
version: 1,
|
||||
id: 'x',
|
||||
nodes: [
|
||||
|
||||
@@ -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' });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type MindMap } from '@/types/map';
|
||||
import { parseMapDocument } from '@/services/mapSerialization';
|
||||
import { normalizeMapDocument } from '@/services/mapSerialization';
|
||||
|
||||
/** Erreur reseau/serveur deja traduite en message utilisateur. */
|
||||
export class ShareApiError extends Error {
|
||||
@@ -146,7 +146,7 @@ export async function fetchSharedMap(shareToken: string): Promise<SharedMapRespo
|
||||
return {
|
||||
shareToken: raw.share_token,
|
||||
title: raw.title,
|
||||
map: parseMapDocument(raw.map),
|
||||
map: normalizeMapDocument(raw.map, { source: 'share' }),
|
||||
createdAt: raw.created_at,
|
||||
updatedAt: raw.updated_at,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user