10 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	Améliorations du Système de Sauvegarde Excalidraw
Modifications Appliquées
1. Hash Stable et Déterministe
Fichier: src/app/features/drawings/drawings-editor.component.ts
Problème résolu: Hash instable causant des faux positifs/négatifs pour le dirty state.
Solution:
- Tri stable des éléments par idpour éviter les changements de hash dus à l'ordre
- Tri stable des clés de filespour éviter les changements de hash dus à l'ordre des propriétés
- Suppression des propriétés volatiles (version,versionNonce,updated)
private hashScene(scene: ExcalidrawScene | null): string {
  try {
    if (!scene) return '';
    
    // Normalize elements: remove volatile properties
    const normEls = Array.isArray(scene.elements) ? scene.elements.map((el: any) => {
      const { version, versionNonce, updated, ...rest } = el || {};
      return rest;
    }) : [];
    // Stable sort of elements by id
    const sortedEls = normEls.slice().sort((a: any, b: any) => 
      (a?.id || '').localeCompare(b?.id || '')
    );
    
    // Stable sort of files keys
    const filesObj = scene.files && typeof scene.files === 'object' ? scene.files : {};
    const sortedFilesKeys = Object.keys(filesObj).sort();
    const sortedFiles: Record<string, any> = {};
    for (const k of sortedFilesKeys) sortedFiles[k] = filesObj[k];
    const stable = { elements: sortedEls, files: sortedFiles };
    return btoa(unescape(encodeURIComponent(JSON.stringify(stable))));
  } catch (error) {
    console.error('Error hashing scene:', error);
    return '';
  }
}
2. Prévention des Sauvegardes Concurrentes
Fichier: src/app/features/drawings/drawings-editor.component.ts
Problème résolu: Plusieurs PUT peuvent être lancés simultanément, causant des conflits.
Solution: Ajout d'un filtre pour ignorer les autosaves si une sauvegarde est déjà en cours.
this.saveSub = sceneChange$.pipe(
  distinctUntilChanged((prev, curr) => prev.hash === curr.hash),
  debounceTime(2000),
  filter(({ hash }) => this.lastSavedHash !== hash),
  filter(() => !this.isSaving()),  // ⬅️ NOUVEAU: Empêche les sauvegardes concurrentes
  tap(({ hash }) => {
    console.log('💾 Autosaving... hash:', hash.substring(0, 10));
    this.isSaving.set(true);
    this.error.set(null);
  }),
  switchMap(({ scene, hash }) => this.files.put(this.path, scene).pipe(...))
).subscribe();
3. Suppression du Mode readOnly Pendant la Sauvegarde
Fichier: src/app/features/drawings/drawings-editor.component.html
Problème résolu: Le passage en readOnly pendant la sauvegarde peut perturber les événements onChange et bloquer des micro-changements.
Solution: Suppression de [readOnly]="isSaving()", conservation uniquement de l'indicateur visuel d'opacité.
<excalidraw-editor
  #editorEl
  [initialData]="scene() || { elements: [], appState: { viewBackgroundColor: '#1e1e1e' } }"
  [theme]="themeName()"
  [lang]="'fr'"
  (ready)="console.log('READY', $event); onExcalidrawReady()"
  style="display:block; height:100%; width:100%"
></excalidraw-editor>
4. Logs de Diagnostic Améliorés
Fichiers:
- web-components/excalidraw/ExcalidrawElement.tsx
- web-components/excalidraw/define.ts
- src/app/features/drawings/drawings-editor.component.html
Ajouts:
- Log visible console.logau lieu deconsole.debugpourscene-change
- Log du readyevent dans le web component
- Log du readyevent dans le template Angular
- Tous les événements avec bubbles: true, composed: true
// ExcalidrawElement.tsx
const onChange = (elements: any[], appState: Partial<AppState>, files: any) => {
  if (!host) return;
  const detail: SceneChangeDetail = { elements, appState, files, source: 'user' };
  console.log('[excalidraw-editor] 📝 SCENE-CHANGE dispatched', { 
    elCount: Array.isArray(elements) ? elements.length : 'n/a' 
  });
  host.dispatchEvent(new CustomEvent('scene-change', { detail, bubbles: true, composed: true }));
};
// define.ts
const onReady = () => {
  console.log('[excalidraw-editor] 🎨 READY event dispatched', { 
    apiAvailable: !!(this as any).__excalidrawAPI 
  });
  this.dispatchEvent(new CustomEvent('ready', { 
    detail: { apiAvailable: !!(this as any).__excalidrawAPI },
    bubbles: true,
    composed: true
  }));
};
Check-list de Test
1. Vérifier les Événements de Base
Ouvrir la console DevTools et observer:
// Test manuel dans la console
document.querySelector('excalidraw-editor')
  ?.addEventListener('scene-change', e => console.log('✅ SCENE-CHANGE reçu', e?.detail));
Attendu:
- ✅ [excalidraw-editor] 🎨 READY event dispatchedau chargement
- ✅ READY { detail: { apiAvailable: true } }dans le template Angular
- ✅ 🎨 Excalidraw Ready - Binding listenersdans le composant
- ✅ 🔗 Binding Excalidraw host listenersdans le composant
- ✅ [excalidraw-editor] 📝 SCENE-CHANGE dispatchedà chaque modification
- ✅ ✏️ Dirty flagged (event)dans Angular après chaque modification
2. Tester le Dirty State
- Ouvrir un fichier .excalidraw.md
- Vérifier: indicateur ⚫ Gris "Sauvegardé"
- Ajouter un rectangle
- Vérifier: indicateur 🔴 Rouge "Non sauvegardé" immédiatement
- Attendre 2 secondes
- Vérifier console: 💾 Autosaving...
- Vérifier console: ✅ Autosave successful
- Vérifier: indicateur ⚫ Gris "Sauvegardé"
3. Tester la Sauvegarde Manuelle
- Modifier le dessin
- Appuyer Ctrl+S(ou cliquer sur l'indicateur)
- Vérifier console: 💾 Manual save triggered
- Vérifier console: 📤 Sending save request...
- Vérifier console: ✅ Manual save successful
- Vérifier toast: "Sauvegarde réussie"
- Vérifier Network: PUT /api/files?path=...
- Vérifier: indicateur ⚫ Gris "Sauvegardé"
4. Tester la Stabilité du Hash
- Ouvrir un fichier
- Ajouter un élément → attendre autosave
- Déplacer légèrement l'élément → attendre autosave
- Vérifier console: les hash doivent être différents
- Ne rien toucher pendant 5 secondes
- Vérifier: pas de nouveaux autosaves (hash stable)
5. Tester les Sauvegardes Concurrentes
- Modifier rapidement plusieurs éléments
- Immédiatement appuyer Ctrl+Splusieurs fois
- Vérifier console: un seul 💾 Autosaving...à la fois
- Vérifier Network: pas de requêtes PUT simultanées
6. Tester les Conflits (ETag)
- Ouvrir un fichier
- Modifier externellement le fichier (autre éditeur)
- Modifier dans Excalidraw
- Attendre l'autosave
- Vérifier: bannière de conflit apparaît
- Tester "Recharger depuis le disque" ou "Écraser"
Comportement Attendu
Séquence de Chargement
1. GET /api/files?path=test.excalidraw.md
2. [excalidraw-editor] 🎨 READY event dispatched
3. READY { detail: { apiAvailable: true } }
4. 🎨 Excalidraw Ready - Binding listeners
5. 🔗 Binding Excalidraw host listeners
6. ✓ Dirty check and Autosave subscriptions active
7. Indicateur: ⚫ Gris "Sauvegardé"
Séquence de Modification
1. Utilisateur dessine un rectangle
2. [excalidraw-editor] 📝 SCENE-CHANGE dispatched { elCount: 1 }
3. ✏️ Dirty flagged (event) { lastSaved: 'abc123...', current: 'def456...' }
4. Indicateur: 🔴 Rouge "Non sauvegardé"
5. (attente 2s)
6. 💾 Autosaving... hash: def456...
7. PUT /api/files?path=... (avec If-Match: "...")
8. ✅ Autosave successful { newHash: 'def456...' }
9. Indicateur: ⚫ Gris "Sauvegardé"
Séquence de Sauvegarde Manuelle
1. Utilisateur appuie Ctrl+S
2. 💾 Manual save triggered
3. 📤 Sending save request...
4. 🧩 Snapshot { elements: 3, hasFiles: true }
5. PUT /api/files?path=...
6. 📥 Save response { rev: '...' }
7. 🔁 Verify after save { ok: true, ... }
8. ✅ Manual save successful
9. Toast: "Sauvegarde réussie"
10. Indicateur: ⚫ Gris "Sauvegardé"
Troubleshooting
Pas de logs [excalidraw-editor]
→ Le web component ne se charge pas. Vérifier ngOnInit() et l'import du custom element.
READY n'apparaît jamais
→ L'API Excalidraw ne s'initialise pas. Vérifier la console pour des erreurs React.
SCENE-CHANGE n'apparaît jamais
→ Le onChange n'est pas appelé. Vérifier que le composant React reçoit bien __host.
✏️ Dirty flagged n'apparaît jamais
→ Le binding n'a pas eu lieu. Vérifier que onExcalidrawReady() est appelé.
Hash change en permanence (autosave en boucle)
→ Le hash n'est pas stable. Vérifier que la nouvelle version de hashScene() est bien appliquée.
Indicateur reste rouge après autosave
→ Vérifier que this.dirty.set(false) est bien appelé dans le tap() après succès.
Conflits 409 en permanence
→ Vérifier que le serveur renvoie bien un ETag dans les réponses GET et PUT.
Fichiers Modifiés
- 
✅ src/app/features/drawings/drawings-editor.component.ts- hashScene(): tri stable des éléments et files
- bindEditorHostListeners(): ajout du filtre- !isSaving()
 
- 
✅ src/app/features/drawings/drawings-editor.component.html- Suppression de [readOnly]="isSaving()"
- Ajout de log du readyevent
 
- Suppression de 
- 
✅ web-components/excalidraw/ExcalidrawElement.tsx- Log visible console.logpourscene-change
 
- Log visible 
- 
✅ web-components/excalidraw/define.ts- Log visible console.logpourready
- Ajout de bubbles: true, composed: trueaureadyevent
 
- Log visible 
Prochaines Étapes
Une fois les tests validés:
- Retirer les logs de debug (optionnel, utiles pour le monitoring)
- Tester avec des fichiers volumineux (100+ éléments)
- Tester sur mobile (touch events)
- Ajouter des tests E2E pour la sauvegarde automatique
- Documenter le comportement ETag côté backend
Résumé
✅ Hash stable et déterministe (tri des éléments et files)
✅ Prévention des sauvegardes concurrentes
✅ Suppression du readOnly pendant la sauvegarde
✅ Logs de diagnostic améliorés
✅ Événements avec bubbles et composed
✅ Check-list de test complète