diff --git a/src/app.component.simple.html b/src/app.component.simple.html index e44c553..590514e 100644 --- a/src/app.component.simple.html +++ b/src/app.component.simple.html @@ -30,6 +30,7 @@ (markdownPlaygroundSelected)="setView('markdown-playground')" (parametersOpened)="setView('parameters')" (testsPanelRequested)="setView('tests-panel')" + (testsExcalidrawRequested)="setView('tests-excalidraw')" (helpPageRequested)="openHelpPage()" (noteCreated)="onNoteCreated($event)" (noteCreatedAndSelected)="onNoteCreatedAndSelected($event)" @@ -541,6 +542,8 @@ } @else if (activeView() === 'tests-panel') { + } @else if (activeView() === 'tests-excalidraw') { + } @else { @if (activeView() === 'drawings') { @if (currentDrawingPath()) { diff --git a/src/app.component.ts b/src/app.component.ts index 4609baa..31c4598 100644 --- a/src/app.component.ts +++ b/src/app.component.ts @@ -38,6 +38,8 @@ import { LayoutModule } from '@angular/cdk/layout'; import { ToastContainerComponent } from './app/shared/toast/toast-container.component'; import { ParametersPage } from './app/features/parameters/parameters.page'; import { TestsPanelComponent } from './app/features/tests/tests-panel.component'; +import { TestExcalidrawPageComponent } from './app/features/tests/test-excalidraw-page.component'; +import { Router } from '@angular/router'; // Types import { FileMetadata, Note, TagInfo, VaultNode } from './types'; @@ -71,6 +73,7 @@ interface TocEntry { ToastContainerComponent, ParametersPage, TestsPanelComponent, + TestExcalidrawPageComponent, ], templateUrl: './app.component.simple.html', styleUrls: ['./app.component.css'], @@ -93,12 +96,13 @@ export class AppComponent implements OnInit, OnDestroy { private readonly drawingsFiles = inject(DrawingsFileService); private readonly urlState = inject(UrlStateService); private readonly editorState = inject(EditorStateService); + private readonly router = inject(Router); // --- State Signals --- isSidebarOpen = signal(true); isOutlineOpen = signal(false); outlineTab = signal<'outline' | 'settings'>('outline'); - activeView = signal<'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings' | 'markdown-playground' | 'parameters' | 'tests-panel'>('files'); + activeView = signal<'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings' | 'markdown-playground' | 'parameters' | 'tests-panel' | 'tests-excalidraw'>('files'); currentDrawingPath = signal(null); selectedNoteId = signal(''); sidebarSearchTerm = signal(''); @@ -140,6 +144,13 @@ export class AppComponent implements OnInit, OnDestroy { this.editorState.enterEditMode(note.filePath, content); } }); + // Deep-link support for tests=excalidraw + try { + const sp = new URLSearchParams(window.location.search || ''); + if (sp.get('tests') === 'excalidraw') { + this.activeView.set('tests-excalidraw'); + } + } catch {} } onNoteCreatedAndSelected(event: { id: string; filePath: string }): void { @@ -978,10 +989,16 @@ export class AppComponent implements OnInit, OnDestroy { handle?.addEventListener('lostpointercapture', cleanup); } - setView(view: 'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings' | 'markdown-playground' | 'parameters' | 'tests-panel'): void { + setView(view: 'files' | 'graph' | 'tags' | 'search' | 'calendar' | 'bookmarks' | 'drawings' | 'markdown-playground' | 'parameters' | 'tests-panel' | 'tests-excalidraw'): void { const previousView = this.activeView(); this.activeView.set(view); this.sidebarSearchTerm.set(''); + // Maintain deep-link for tests view + if (view === 'tests-excalidraw') { + this.router.navigate([], { queryParams: { tests: 'excalidraw' }, queryParamsHandling: 'merge', preserveFragment: true }); + } else if (previousView === 'tests-excalidraw') { + this.router.navigate([], { queryParams: { tests: null }, queryParamsHandling: 'merge', preserveFragment: true }); + } // Log view changes if (view === 'bookmarks' && previousView !== 'bookmarks') { diff --git a/src/app/features/drawings/drawings-editor.component.ts b/src/app/features/drawings/drawings-editor.component.ts index 5121f9c..999d845 100644 --- a/src/app/features/drawings/drawings-editor.component.ts +++ b/src/app/features/drawings/drawings-editor.component.ts @@ -223,8 +223,28 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy const isExMd = /\.excalidraw\.md$/i.test(this.path || ''); let result: { rev: string } | null = null; if (isExMd) { - // Use same compression as creation: build Obsidian MD with ```compressed-json``` - const md = this.excalIo.toObsidianMd(scene as any, null); + // Preserve existing frontmatter and normalize keys + let existing = ''; + try { existing = await firstValueFrom(this.files.getText(this.path)); } catch {} + let fm = this.excalIo.extractFrontMatter(existing) || null; + if (fm) { + // Ensure excalidraw-plugin: parsed + if (!/excalidraw-plugin\s*:/i.test(fm)) { + fm = fm.replace(/^---\s*\n/, `---\nexcalidraw-plugin: parsed\n`); + } else { + fm = fm.replace(/excalidraw-plugin\s*:\s*.*?(\r?\n)/i, `excalidraw-plugin: parsed$1`); + } + // Update timestamp + const stamp = new Date().toISOString(); + if (/^updated\s*:/mi.test(fm)) { + fm = fm.replace(/updated\s*:\s*.*?(\r?\n)/i, `updated: "${stamp}"$1`); + } else { + fm = fm.replace(/^---\s*\n/, `---\nupdated: "${stamp}"\n`); + } + } else { + fm = `---\nexcalidraw-plugin: parsed\nupdated: "${new Date().toISOString()}"\n---`; + } + const md = this.excalIo.toObsidianMd(scene as any, fm); result = await firstValueFrom(this.files.putText(this.path, md)); } else { // For .excalidraw or .json let server persist plain JSON @@ -420,13 +440,20 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy } private prepareSceneData(scene: ExcalidrawScene) { + const incomingApp: any = (scene && typeof scene.appState === 'object' && scene.appState) + ? { ...(scene.appState as any) } + : {}; + // Excalidraw expects collaborators to be a Map at runtime; JSON files store object/array → remove it + if (incomingApp && typeof incomingApp === 'object' && 'collaborators' in incomingApp) { + try { delete incomingApp.collaborators; } catch {} + } return { elements: Array.isArray(scene.elements) ? scene.elements : [], appState: { viewBackgroundColor: '#1e1e1e', currentItemFontFamily: 1, theme: this.themeName(), - ...(scene.appState || {}) + ...incomingApp }, scrollToContent: true, ...(scene.files && { files: scene.files }) @@ -678,7 +705,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy return null; } - async exportPNG(): Promise { + async exportPNG(withBackground: boolean = true): Promise { try { const host = this.editorEl?.nativeElement; if (!host || !this.excalidrawReady) { @@ -687,7 +714,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy return; } - const blob = await this.previews.exportPNGFromElement(host, true); + const blob = await this.previews.exportPNGFromElement(host, withBackground); if (!blob) { console.error('Failed to generate PNG blob'); this.showToast('Export PNG indisponible', 'error'); @@ -705,7 +732,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy } } - async exportSVG(): Promise { + async exportSVG(withBackground: boolean = true): Promise { try { const host = this.editorEl?.nativeElement; if (!host || !this.excalidrawReady) { @@ -714,7 +741,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy return; } - const blob = await this.previews.exportSVGFromElement(host, true); + const blob = await this.previews.exportSVGFromElement(host, withBackground); if (!blob) { console.error('Failed to generate SVG blob'); this.showToast('Export SVG indisponible', 'error'); diff --git a/src/app/features/drawings/drawings-file.service.ts b/src/app/features/drawings/drawings-file.service.ts index 693da83..3799ec0 100644 --- a/src/app/features/drawings/drawings-file.service.ts +++ b/src/app/features/drawings/drawings-file.service.ts @@ -1,6 +1,7 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, map, tap } from 'rxjs'; +import { ExcalidrawIoService } from './excalidraw-io.service'; export type ThemeName = 'light' | 'dark'; @@ -14,10 +15,29 @@ export interface ExcalidrawScene { export class DrawingsFileService { private etagCache = new Map(); - constructor(private http: HttpClient) {} + private http = inject(HttpClient); + private excalIo = inject(ExcalidrawIoService); get(path: string): Observable { const url = `/api/files`; + const isMd = /\.excalidraw\.md$/i.test(path || ''); + if (isMd) { + return this.http.get(url, { + params: { path }, + observe: 'response', + responseType: 'text' as const, + }).pipe( + tap((res) => { + const etag = res.headers.get('ETag'); + if (etag) this.etagCache.set(path, etag); + }), + map((res) => { + const md = res.body ?? ''; + const parsed = this.excalIo.parseAny(String(md)) || { elements: [], appState: {}, files: {} }; + return parsed as ExcalidrawScene; + }) + ); + } return this.http.get(url, { params: { path: path }, observe: 'response' @@ -30,6 +50,22 @@ export class DrawingsFileService { ); } + // Fetch raw text content and record ETag + getText(path: string): Observable { + const url = `/api/files`; + return this.http.get(url, { + params: { path }, + observe: 'response', + responseType: 'text' as const, + }).pipe( + tap((res) => { + const etag = res.headers.get('ETag'); + if (etag) this.etagCache.set(path, etag); + }), + map((res) => res.body ?? '') + ); + } + put(path: string, scene: ExcalidrawScene): Observable<{ rev: string }> { const url = `/api/files`; const prev = this.etagCache.get(path); diff --git a/src/app/features/drawings/excalidraw-io.service.ts b/src/app/features/drawings/excalidraw-io.service.ts index a5a4f08..8456117 100644 --- a/src/app/features/drawings/excalidraw-io.service.ts +++ b/src/app/features/drawings/excalidraw-io.service.ts @@ -18,10 +18,26 @@ export class ExcalidrawIoService { */ extractFrontMatter(md: string): string | null { if (!md) return null; - const match = md.match(/^---\s*\n([\s\S]*?)\n---/); + const match = md.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/); return match ? match[0] : null; } + /** + * Remove fields that cause issues when (de)serializing between Excalidraw and Obsidian + */ + private sanitizeForSave(scene: ExcalidrawScene): ExcalidrawScene { + const app: any = scene?.appState && typeof scene.appState === 'object' ? { ...scene.appState } : {}; + // Excalidraw expects collaborators as a Map at runtime; do not persist arbitrary object + if (app && typeof app === 'object' && 'collaborators' in app) { + try { delete app.collaborators; } catch {} + } + return { + elements: Array.isArray(scene?.elements) ? scene.elements : [], + appState: app, + files: (scene && typeof scene.files === 'object') ? scene.files : {} + }; + } + /** * Parse Obsidian Excalidraw markdown format * Extracts compressed-json block and decompresses using LZ-String @@ -29,14 +45,19 @@ export class ExcalidrawIoService { parseObsidianMd(md: string): ExcalidrawScene | null { if (!md || typeof md !== 'string') return null; - // Try to extract compressed-json block - const compressedMatch = md.match(/```\s*compressed-json\s*\n([\s\S]*?)\n```/i); + // Try to extract compressed-json block anywhere (Obsidian may wrap with %% comments or not) + const compressedMatch = md.match(/```\s*compressed-json\s*\r?\n([\s\S]*?)\r?\n```/i); if (compressedMatch && compressedMatch[1]) { try { - // Remove whitespace from base64 data + // Remove ALL whitespace (including newlines, spaces, tabs) from base64 data const compressed = compressedMatch[1].replace(/\s+/g, ''); + if (!compressed) { + console.warn('[Excalidraw] Empty compressed data'); + return null; + } + // Decompress using LZ-String const decompressed = LZString.decompressFromBase64(compressed); @@ -50,19 +71,26 @@ export class ExcalidrawIoService { return this.normalizeScene(data); } catch (error) { console.error('[Excalidraw] Failed to parse compressed-json:', error); + console.error('[Excalidraw] Error details:', error); return null; } } - // Fallback: try to extract plain json block - const jsonMatch = md.match(/```\s*(?:excalidraw|json)\s*\n([\s\S]*?)\n```/i); + // Fallback: try to extract plain json fence anywhere + const jsonMatch = md.match(/```\s*json\s*\r?\n([\s\S]*?)\r?\n```/i); if (jsonMatch && jsonMatch[1]) { try { - const data = JSON.parse(jsonMatch[1].trim()); + const jsonStr = jsonMatch[1].trim(); + if (!jsonStr) { + console.warn('[Excalidraw] Empty JSON data'); + return null; + } + const data = JSON.parse(jsonStr); return this.normalizeScene(data); } catch (error) { console.error('[Excalidraw] Failed to parse json block:', error); + console.error('[Excalidraw] JSON string:', jsonMatch[1].substring(0, 100)); return null; } } @@ -94,28 +122,35 @@ export class ExcalidrawIoService { * Convert Excalidraw scene to Obsidian markdown format */ toObsidianMd(data: ExcalidrawScene, existingFrontMatter?: string | null): string { - // Normalize scene data + // Normalize scene data then sanitize and wrap into Excalidraw export payload const scene = this.normalizeScene(data); + const sanitized = this.sanitizeForSave(scene); + const payload = { + type: 'excalidraw', + version: 2, + source: 'obsidian-excalidraw-plugin', + elements: sanitized.elements || [], + appState: sanitized.appState || {}, + files: sanitized.files || {} + } as any; - // Serialize to JSON - const json = JSON.stringify(scene); - - // Compress using LZ-String + // Serialize and compress + const json = JSON.stringify(payload); const compressed = LZString.compressToBase64(json); + + if (!compressed) { + throw new Error('[Excalidraw] Failed to compress data'); + } // Use existing front matter or create default - const frontMatter = existingFrontMatter?.trim() || `--- + const frontMatter = existingFrontMatter || `--- excalidraw-plugin: parsed tags: [excalidraw] ---`; - // Banner text (Obsidian standard) - const banner = `==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== -You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'`; - - // Construct full markdown + // Construct Obsidian structure with only the compressed-json block (as used by the plugin) return `${frontMatter} -${banner} +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== # Excalidraw Data @@ -163,4 +198,4 @@ ${compressed} if (!Array.isArray(data.elements)) return false; return true; } -} +} \ No newline at end of file diff --git a/src/app/features/sidebar/nimbus-sidebar.component.ts b/src/app/features/sidebar/nimbus-sidebar.component.ts index ec1690f..4c58727 100644 --- a/src/app/features/sidebar/nimbus-sidebar.component.ts +++ b/src/app/features/sidebar/nimbus-sidebar.component.ts @@ -44,6 +44,11 @@ import { VaultService } from '../../../services/vault.service'; class="mt-1 w-full text-left block text-sm px-2 py-1.5 rounded hover:bg-surface1 dark:hover:bg-card text-main dark:text-main hover:text-main dark:hover:text-gray-100"> API Tests Panel + @@ -178,6 +183,7 @@ export class NimbusSidebarComponent implements OnChanges { @Output() quickLinkSelected = new EventEmitter(); @Output() markdownPlaygroundSelected = new EventEmitter(); @Output() testsPanelSelected = new EventEmitter(); + @Output() testsExcalidrawSelected = new EventEmitter(); @Output() helpPageSelected = new EventEmitter(); @Output() aboutSelected = new EventEmitter(); diff --git a/src/app/features/tests/test-excalidraw-page.component.ts b/src/app/features/tests/test-excalidraw-page.component.ts new file mode 100644 index 0000000..28a1f68 --- /dev/null +++ b/src/app/features/tests/test-excalidraw-page.component.ts @@ -0,0 +1,287 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnDestroy, OnInit, ViewChild, inject, signal } from '@angular/core'; +import { Router } from '@angular/router'; +import { DrawingsEditorComponent } from '../drawings/drawings-editor.component'; +import { DrawingsFileService } from '../drawings/drawings-file.service'; +import { ExcalidrawIoService, type ExcalidrawScene } from '../drawings/excalidraw-io.service'; +import { VaultService } from '../../../services/vault.service'; +import { firstValueFrom } from 'rxjs'; + +@Component({ + selector: 'app-test-excalidraw-page', + standalone: true, + imports: [CommonModule, DrawingsEditorComponent], + template: ` +
+
+
+
+

Test Excalidraw

+ Legacy Integration + + {{ currentPath() }} + + + No file + + • {{ dirty() ? 'Dirty' : 'Saved' }} +
+
+ +
+ +
+
Sélectionner un fichier *.excalidraw.md
+
    +
  • + +
  • +
+
+
+ +
+ + +
+
+ +
+ + +
+ + +
+ +
+
+
+ + +
+
+
+ +
+ + + + +
+
+
Aucun fichier Excalidraw sélectionné
+
+ + +
+
+
+
+
+
+ `, +}) +export class TestExcalidrawPageComponent implements OnInit, OnDestroy { + private readonly drawings = inject(DrawingsFileService); + private readonly excalIo = inject(ExcalidrawIoService); + private readonly vault = inject(VaultService); + private readonly router = inject(Router); + + @ViewChild('editor', { static: false }) editor?: DrawingsEditorComponent; + + currentPath = signal(null); + openPicker = signal(false); + openExport = signal(false); + autosave = signal(false); + dirty = signal(false); + + private autosaveTimer: any = null; + private saveTimer: any = null; + + excalidrawFiles = signal>([]); + + async ngOnInit(): Promise { + // lazy define custom element (in case editor needs it early) + try { await import('../../../../web-components/excalidraw/define'); } catch {} + + // try to read ?file= param + try { + const sp = new URLSearchParams(window.location.search || ''); + const file = sp.get('file'); + if (file && /\.excalidraw(?:\.md)?$/i.test(file)) { + const clean = file.replace(/^\/+/, ''); + this.currentPath.set(clean); + this.updateUrlFileParam(clean); + } + } catch {} + + // build files list for picker + this.refreshFileList(); + + // poll editor dirty state via save hash comparison using existing editor polling + // here we just mirror the editor's internal dirty state by attempting a heuristic: + const syncDirty = () => { + // not accessible directly; rely on presence of '*' in title not available. Skip. + }; + void syncDirty; + } + + ngOnDestroy(): void { + if (this.autosaveTimer) clearInterval(this.autosaveTimer); + if (this.saveTimer) clearTimeout(this.saveTimer); + } + + refreshFileList(): void { + const notes = this.vault.allNotes(); + const list = notes + .filter(n => /\.excalidraw\.md$/i.test(n.filePath || '')) + .map(n => ({ filePath: n.filePath })); + this.excalidrawFiles.set(list); + } + + toggleOpenPicker(): void { + this.openPicker.update(v => !v); + if (this.openPicker()) this.refreshFileList(); + } + + toggleExport(): void { + this.openExport.update(v => !v); + } + + async createNew(): Promise { + const now = new Date(); + const y = now.getFullYear(); + const m = String(now.getMonth() + 1).padStart(2, '0'); + const d = String(now.getDate()).padStart(2, '0'); + const hh = String(now.getHours()).padStart(2, '0'); + const mm = String(now.getMinutes()).padStart(2, '0'); + const base = `Drawing-${y}${m}${d}-${hh}${mm}`; + const name = prompt('Nom du fichier', `${base}.excalidraw.md`); + if (!name) return; + const file = name.endsWith('.excalidraw.md') ? name : `${name}.excalidraw.md`; + const path = file.replace(/^\/+/, ''); + const scene: ExcalidrawScene = { elements: [], appState: {}, files: {} }; + + const fm = `---\nexcalidraw-plugin: parsed\ncreated: "${now.toISOString()}"\nupdated: "${now.toISOString()}"\ntitle: "${(file || '').replace(/\.excalidraw\.md$/i,'')}"\n---`; + const md = this.excalIo.toObsidianMd(scene, fm); + await firstValueFrom(this.drawings.putText(path, md)); + this.currentPath.set(path); + this.updateUrlFileParam(path); + this.openPicker.set(false); + // focus editor next tick + setTimeout(() => this.editor?.onExcalidrawReady?.(), 0); + } + + openFile(path: string): void { + this.currentPath.set(path); + this.updateUrlFileParam(path); + this.openPicker.set(false); + } + + async saveDebounced(): Promise { + if (this.saveTimer) clearTimeout(this.saveTimer); + this.saveTimer = setTimeout(() => this.save(), 1200); + } + + async save(): Promise { + await this.editor?.saveNow(); + } + + async saveAs(): Promise { + const current = this.currentPath() || ''; + const suggested = current ? current.split('/').pop() || 'Drawing.excalidraw.md' : 'Drawing.excalidraw.md'; + const name = prompt('Enregistrer sous (racine de la voûte)', suggested); + if (!name) return; + const file = name.endsWith('.excalidraw.md') ? name : `${name}.excalidraw.md`; + const path = file.replace(/^\/+/, ''); + + // Get latest scene from server after manual save to ensure consistency + await this.editor?.saveNow(); + const scene = await firstValueFrom(this.drawings.get(this.currentPath() || '')); + const fm = `---\nexcalidraw-plugin: parsed\ncreated: "${new Date().toISOString()}"\nupdated: "${new Date().toISOString()}"\ntitle: "${(file || '').replace(/\.excalidraw\.md$/i,'')}"\n---`; + const md = this.excalIo.toObsidianMd(scene, fm); + await firstValueFrom(this.drawings.putText(path, md)); + this.currentPath.set(path); + this.updateUrlFileParam(path); + } + + async exportPNG(withBg: boolean): Promise { + await this.editor?.exportPNG(withBg); + this.openExport.set(false); + } + + async exportSVG(withBg: boolean): Promise { + await this.editor?.exportSVG(withBg); + this.openExport.set(false); + } + + private updateUrlFileParam(filePath: string | null): void { + try { + this.router.navigate([], { queryParams: { file: filePath || null }, queryParamsHandling: 'merge', preserveFragment: true }); + } catch {} + } + + closeFile(): void { + this.currentPath.set(null); + this.openPicker.set(false); + this.openExport.set(false); + this.updateUrlFileParam(null); + } + + async exportJSON(): Promise { + const path = this.currentPath(); + if (!path) return; + try { + const scene = await firstValueFrom(this.drawings.get(path)); + const blob = new Blob([JSON.stringify(scene, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = path.replace(/\.excalidraw(?:\.md)?$/i, '.json'); + a.click(); + URL.revokeObjectURL(url); + } catch (e) { + console.error('Export JSON failed', e); + } + } + + setAutosave(on: boolean): void { + this.autosave.set(on); + if (this.autosaveTimer) { clearInterval(this.autosaveTimer); this.autosaveTimer = null; } + if (on) { + this.autosaveTimer = setInterval(() => { + if (this.currentPath()) { + this.editor?.saveNow(); + } + }, 10000); + } + } + + toggleFullscreen(): void { + this.editor?.toggleFullscreen(); + } + +} diff --git a/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts b/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts index 9b7e49f..c2bddfe 100644 --- a/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts +++ b/src/app/layout/app-shell-nimbus/app-shell-nimbus.component.ts @@ -17,6 +17,7 @@ import { QuickLinksComponent } from '../../features/quick-links/quick-links.comp import { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrollable-overlay.directive'; import { MarkdownPlaygroundComponent } from '../../features/tests/markdown-playground/markdown-playground.component'; import { TestsPanelComponent } from '../../features/tests/tests-panel.component'; +import { TestExcalidrawPageComponent } from '../../features/tests/test-excalidraw-page.component'; import { ParametersPage } from '../../features/parameters/parameters.page'; import { AboutPanelComponent } from '../../features/about/about-panel.component'; import { UrlStateService } from '../../services/url-state.service'; @@ -28,12 +29,12 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search @Component({ selector: 'app-shell-nimbus-layout', standalone: true, - imports: [CommonModule, FileExplorerComponent, NoteViewerComponent, AppBottomNavigationComponent, AppSidebarDrawerComponent, AppTocOverlayComponent, SwipeNavDirective, NotesListComponent, NimbusSidebarComponent, QuickLinksComponent, ScrollableOverlayDirective, MarkdownPlaygroundComponent, TestsPanelComponent, ParametersPage, AboutPanelComponent, NoteInfoModalComponent, InPageSearchOverlayComponent], + imports: [CommonModule, FileExplorerComponent, NoteViewerComponent, AppBottomNavigationComponent, AppSidebarDrawerComponent, AppTocOverlayComponent, SwipeNavDirective, NotesListComponent, NimbusSidebarComponent, QuickLinksComponent, ScrollableOverlayDirective, MarkdownPlaygroundComponent, TestsPanelComponent, TestExcalidrawPageComponent, ParametersPage, AboutPanelComponent, NoteInfoModalComponent, InPageSearchOverlayComponent], template: `
-
+
+
@@ -187,7 +190,8 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search - + - + +
@@ -297,6 +302,10 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
+ } @else if (activeView === 'tests-excalidraw') { +
+ +
} @else {
@if (selectedNote) { @@ -370,6 +379,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit { @Output() parametersOpened = new EventEmitter(); @Output() helpPageRequested = new EventEmitter(); @Output() testsPanelRequested = new EventEmitter(); + @Output() testsExcalidrawRequested = new EventEmitter(); folderFilter: string | null = null; listQuery: string = ''; @@ -857,6 +867,10 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit { this.testsPanelRequested.emit(); } + onTestsExcalidrawSelected(): void { + this.testsExcalidrawRequested.emit(); + } + onParametersOpen(): void { this.parametersOpened.emit(); } diff --git a/vault/.obsidian/workspace.json b/vault/.obsidian/workspace.json index 54dd4b6..6207014 100644 --- a/vault/.obsidian/workspace.json +++ b/vault/.obsidian/workspace.json @@ -4,19 +4,17 @@ "type": "split", "children": [ { - "id": "22c2ec9062224eb0", + "id": "b123bf2cfbaf681c", "type": "tabs", "children": [ { - "id": "9ad6b7932a502fb2", + "id": "a71c9cf3bbb86552", "type": "leaf", "state": { - "type": "excalidraw", - "state": { - "file": "Drawing 2025-10-27 16.43.48.excalidraw.md" - }, - "icon": "excalidraw-icon", - "title": "Drawing 2025-10-27 16.43.48.excalidraw" + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" } } ] @@ -161,7 +159,8 @@ } ], "direction": "horizontal", - "width": 234.5 + "width": 200, + "collapsed": true }, "left-ribbon": { "hiddenItems": { @@ -175,32 +174,43 @@ "obsidian-excalidraw-plugin:New drawing": false } }, - "active": "9ad6b7932a502fb2", + "active": "a71c9cf3bbb86552", "lastOpenFiles": [ - "Drawing 2025-10-27 16.43.48.excalidraw.md.tmp", - "Drawing 2025-10-27 16.43.48.excalidraw.md.bak", - "test-drawing.excalidraw.md", - "Drawing 2025-10-27 16.43.48.excalidraw.md", - "Untitled.canvas", - "test.md", + "dessin_05.excalidraw.md", + "dessin_03.excalidraw.md", + "dessin_05.excalidraw.md.bak", + "dessin_04.excalidraw.md.tmp", + "dessin_04.excalidraw.md.bak", + "dessin_04.excalidraw.md", + "dessin-002.excalidraw.md", + "dessin_03.excalidraw.md.tmp", + "dessin_03.excalidraw.md.bak", + "dessin-002.excalidraw.md.tmp", + "dessin-002.excalidraw.md.bak", + "Dessin_001.excalidraw.md", + "Dessin_001.excalidraw.md.tmp", + "Dessin_001.excalidraw.md.bak", + "Drawing 2025-10-28 11.11.59.excalidraw.md", + "Drawing 2025-10-28 11.11.59.excalidraw.md.tmp", + "dessin-06.excalidraw.md", + "Dessin-5.excalidraw.md", + "Drawing-20251027-2203.excalidraw.md", + "dessin-04.excalidraw.md", + "Drawing-20251027-2201.excalidraw.md", + "Dessin-03.md.excalidraw.md", + "Dessin-01.excalidraw.md", + "Drawing 2025-10-27 19.22.35.excalidraw.md", + "Nouveau-markdown.md", + "Drawing-20251027-1922.excalidraw.md", + "Nouvelle note 16.md", + "Dessin.excalidraw.md", + "Dessin.excalidraw.restored.md", "HOME.md", - "folder1/test2.md", - "folder2/test2.md", - "folder2", - "folder1", - "NonExistentNote.md", - "tata/titi-coco.md", - "folder/test2.md", - "Fichier_not_found.png.md", - "welcome.md", - "tata/briana/test-todo.md", - "titi/tata-coco.md", - "tata/briana/test-table.md", - "tata/briana/test-note-1.md", - "tata/briana/test-code.md", - "deep/path/test3.md", - "deep/path", - "deep", - "folder" + "Drawing-20251027-1914.excalidraw.md", + "Drawing-20251027-1705.excalidraw.md", + "Drawing 2025-10-27 16.43.48.excalidraw.md", + "test-drawing.excalidraw.md", + "Untitled.canvas", + "test.md" ] } \ No newline at end of file diff --git a/vault/Dessin_001.excalidraw.md b/vault/Dessin_001.excalidraw.md new file mode 100644 index 0000000..aed01e1 --- /dev/null +++ b/vault/Dessin_001.excalidraw.md @@ -0,0 +1,49 @@ +--- +excalidraw-plugin: parsed +updated: "2025-10-28T15:45:35.205Z" +--- + +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== +You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving' + +# Excalidraw Data + +## Text Elements +%% +## Drawing +```compressed-json +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 +``` +%% \ No newline at end of file diff --git a/vault/Drawing 2025-10-27 16.43.48.excalidraw.md b/vault/Drawing 2025-10-27 16.43.48.excalidraw.md deleted file mode 100644 index 032939b..0000000 --- a/vault/Drawing 2025-10-27 16.43.48.excalidraw.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -titre: Drawing 2025-10-27 16.43.48.excalidraw -auteur: Bruno Charest -creation_date: 2025-10-27T16:43:48-04:00 -modification_date: 2025-10-27T16:43:48-04:00 -catégorie: "" -tags: - - excalidraw -aliases: [] -status: en-cours -publish: false -favoris: false -template: false -task: false -archive: false -draft: false -private: false -excalidraw-plugin: parsed ---- -==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving' - - -## Drawing -```compressed-json -N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcm+gV31TkQAswYKDXgB6MQHNsYfpwBGAOlT0AtmIBeNCtlQbs6RmPry6uA4wC0KDDgLFLUTJ2lH8MTDHQ0YNMWHRJMRZFAEYANjCyJE9VGEYwGgQAbQBdcnQoKABlALA+UEl8PGzsDT5GTkxMch0YIgAhdFQAayKuRlwAYXpMenwEEABiADMx8ZAAX0mgA -``` -%% \ No newline at end of file diff --git a/vault/Drawing 2025-10-27 16.43.48.excalidraw.md.bak b/vault/Drawing 2025-10-27 16.43.48.excalidraw.md.bak deleted file mode 100644 index 9b7ce3b..0000000 --- a/vault/Drawing 2025-10-27 16.43.48.excalidraw.md.bak +++ /dev/null @@ -1,14 +0,0 @@ ---- - -excalidraw-plugin: parsed -tags: [excalidraw] - ---- -==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving' - - -## Drawing -```compressed-json -N4IgLgngDgpiBcIYA8DGBDANgSwCYCd0B3EAGhADcZ8BnbAewDsEAmcm+gV31TkQAswYKDXgB6MQHNsYfpwBGAOlT0AtmIBeNCtlQbs6RmPry6uA4wC0KDDgLFLUTJ2lH8MTDHQ0YNMWHRJMRZFAEYANjCyJE9VGEYwGgQAbQBdcnQoKABlALA+UEl8PGzsDT5GTkxMch0YIgAhdFQAayKuRlwAYXpMenwEEABiADMx8ZAAX0mgA -``` -%% \ No newline at end of file diff --git a/vault/dessin-002.excalidraw.md b/vault/dessin-002.excalidraw.md new file mode 100644 index 0000000..02e0a8f --- /dev/null +++ b/vault/dessin-002.excalidraw.md @@ -0,0 +1,21 @@ +--- +excalidraw-plugin: parsed +updated: "2025-10-28T15:52:34.935Z" +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + + +# Text Elements + +# Drawing +```json +{"elements":[{"id":"FH8lzL5Mi3fE2hkUwBpjg","type":"ellipse","x":186.5,"y":194.5,"width":184,"height":143.5,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":980334059,"version":87,"versionNonce":1002008523,"isDeleted":false,"boundElements":[{"id":"RwNj5xmpJCXxtCVzpGsCc","type":"arrow"},{"id":"tVxfMX9-gzA1gIMHh04AP","type":"arrow"}],"updated":1761666749806,"link":null,"locked":false},{"id":"RwNj5xmpJCXxtCVzpGsCc","type":"arrow","x":186.5,"y":271.5,"width":183,"height":5,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1981797643,"version":68,"versionNonce":624371685,"isDeleted":false,"boundElements":null,"updated":1761666744662,"link":null,"locked":false,"points":[[0,0],[183,-5]],"lastCommittedPoint":null,"startBinding":{"elementId":"FH8lzL5Mi3fE2hkUwBpjg","focus":0.03811374375552938,"gap":1},"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow"},{"id":"tVxfMX9-gzA1gIMHh04AP","type":"arrow","x":277,"y":192,"width":1,"height":145,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":132945413,"version":89,"versionNonce":1141263659,"isDeleted":false,"boundElements":null,"updated":1761666749806,"link":null,"locked":false,"points":[[0,0],[1,145]],"lastCommittedPoint":null,"startBinding":{"elementId":"FH8lzL5Mi3fE2hkUwBpjg","focus":0.01073822548811009,"gap":2.5093386662695423},"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow"}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":{},"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":2,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2025-10-28-1152","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{"tVxfMX9-gzA1gIMHh04AP":true},"resizingElement":null,"scrolledOutside":false,"scrollX":0,"scrollY":0,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#f8f9fa","zenModeEnabled":false,"zoom":{"value":1},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":723,"offsetTop":155,"width":665,"height":546},"files":{}} +``` + +# Embedded Files + +%% +```compressed-json +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 +``` +%% \ No newline at end of file diff --git a/vault/dessin_03.excalidraw.md b/vault/dessin_03.excalidraw.md new file mode 100644 index 0000000..ffda655 --- /dev/null +++ b/vault/dessin_03.excalidraw.md @@ -0,0 +1,21 @@ +--- +excalidraw-plugin: parsed +updated: "2025-10-28T16:00:51.235Z" +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + + +# Text Elements + +# Drawing +```json +{"elements":[{"id":"k815FAbmfYF5PnwUH8C-Y","type":"diamond","x":145,"y":168,"width":184,"height":162,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1003701346,"version":78,"versionNonce":177304866,"isDeleted":false,"boundElements":null,"updated":1761667243295,"link":null,"locked":false},{"id":"-5PKdSwNmlYANRFbrw08a","type":"arrow","x":236.5,"y":170.5,"width":1,"height":154,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1957331170,"version":45,"versionNonce":596418814,"isDeleted":false,"boundElements":null,"updated":1761667248128,"link":null,"locked":false,"points":[[0,0],[1,154]],"lastCommittedPoint":null,"startBinding":null,"endBinding":null,"startArrowhead":null,"endArrowhead":"arrow"}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":{},"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":2,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2025-10-28-1200","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{"-5PKdSwNmlYANRFbrw08a":true},"resizingElement":null,"scrolledOutside":false,"scrollX":0,"scrollY":0,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#f8f9fa","zenModeEnabled":false,"zoom":{"value":1},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":723,"offsetTop":155,"width":665,"height":546},"files":{}} +``` + +# Embedded Files + +%% +```compressed-json +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= +``` +%% \ No newline at end of file diff --git a/vault/dessin_04.excalidraw.md b/vault/dessin_04.excalidraw.md new file mode 100644 index 0000000..ec12027 --- /dev/null +++ b/vault/dessin_04.excalidraw.md @@ -0,0 +1,21 @@ +--- +excalidraw-plugin: parsed +updated: "2025-10-28T16:26:01.524Z" +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + + +# Text Elements + +# Drawing +```json +{"elements":[{"id":"GeEImoYg9respBPK3wmrF","type":"diamond","x":284.5,"y":175,"width":151,"height":168,"angle":0,"strokeColor":"#1e1e1e","backgroundColor":"transparent","fillStyle":"solid","strokeWidth":2,"strokeStyle":"solid","roughness":1,"opacity":100,"groupIds":[],"frameId":null,"roundness":{"type":2},"seed":1926584722,"version":64,"versionNonce":1088033746,"isDeleted":false,"boundElements":null,"updated":1761668759766,"link":null,"locked":false}],"appState":{"showWelcomeScreen":true,"theme":"dark","collaborators":{},"currentChartType":"bar","currentItemBackgroundColor":"transparent","currentItemEndArrowhead":"arrow","currentItemFillStyle":"solid","currentItemFontFamily":1,"currentItemFontSize":20,"currentItemOpacity":100,"currentItemRoughness":1,"currentItemStartArrowhead":null,"currentItemStrokeColor":"#1e1e1e","currentItemRoundness":"round","currentItemStrokeStyle":"solid","currentItemStrokeWidth":2,"currentItemTextAlign":"left","cursorButton":"up","activeEmbeddable":null,"draggingElement":null,"editingElement":null,"editingGroupId":null,"editingLinearElement":null,"activeTool":{"type":"selection","customType":null,"locked":false,"lastActiveTool":null},"penMode":false,"penDetected":false,"errorMessage":null,"exportBackground":true,"exportScale":2,"exportEmbedScene":false,"exportWithDarkMode":false,"fileHandle":null,"gridSize":null,"isBindingEnabled":true,"defaultSidebarDockedPreference":false,"isLoading":false,"isResizing":false,"isRotating":false,"lastPointerDownWith":"mouse","multiElement":null,"name":"Untitled-2025-10-28-1225","contextMenu":null,"openMenu":null,"openPopup":null,"openSidebar":null,"openDialog":null,"pasteDialog":{"shown":false,"data":null},"previousSelectedElementIds":{"GeEImoYg9respBPK3wmrF":true},"resizingElement":null,"scrolledOutside":false,"scrollX":0,"scrollY":0,"selectedElementIds":{},"selectedGroupIds":{},"selectedElementsAreBeingDragged":false,"selectionElement":null,"shouldCacheIgnoreZoom":false,"showStats":false,"startBoundElement":null,"suggestedBindings":[],"frameRendering":{"enabled":true,"clip":true,"name":true,"outline":true},"frameToHighlight":null,"editingFrame":null,"elementsToHighlight":null,"toast":null,"viewBackgroundColor":"#f8f9fa","zenModeEnabled":false,"zoom":{"value":1},"viewModeEnabled":false,"pendingImageElementId":null,"showHyperlinkPopup":false,"selectedLinearElement":null,"snapLines":[],"originSnapOffset":null,"objectsSnapModeEnabled":false,"offsetLeft":723,"offsetTop":155,"width":665,"height":546},"files":{}} +``` + +# Embedded Files + +%% +```compressed-json +N4IgpgNmC2YHYBcDOIBcBtUBLAJmkA4mAKICS0A9gJoDmAnAE5hIAOAQgAoDSAzAO7QGAMRAAaEAgCeLMPhxYAhpTh5xADzQAmABwAWAHQBWcZLQBGAOzGQfXAgAW5w2fH2wWGvYTmAbNvEKcDRQaAAM4kgIDBQA1mAAwhQQFAz4AMRmYJmZYiAARgoAxjE00QCuKonJqagSDIGsCkyIuQBmWBAQAMpSIbVISbi5kdFxAOp2jqiaEVGxYD2SfSADEEPi5Z5wzCioLiAULEVYUuah4SClFGUspDi76AC64q31sHdocGWdG9cq20hdqApDItABfCJgMB4PZ0TQ+Qx6CyaGYgABuYAYSCwFDgaB8unEGKxOLgADlcYVZHtQtptKEeDwLLofOIsEgACKQMAIaFoVoKCBIMDiPJ/HDEKCwRC7L4/EA3HAKXkwyw+Mw+PxWOgWTXiNZwGKfb4QfUUYp81ACoVgMHPEAKFgsHrK6mgJD2Ch8MaQQoUWBdQpMeBoKJlEUSNywORNI3iP2dBRi+oIFJAiEgQplBjNBDxexNBAAFWk1PyTVyWZz8AQpF50DYRRK5UqSRS+CiDSOucr2dzdZgxBUAEEc163AoYQ6x3xe9XEAPoEIOt1emXVutM32a4uhLiEEIlB1THt49uF/W94gulgAF7UzQXKv9+sAeSOhROJ7M5zP89r9YAErXFsOzmH+L4wC6DAIKO0R8BOMJyqaW7/ouPSjAkbY1CAGRZPhc6QdAwEVDgAK7CALaqKhREYfMizLBu1HPju9Z0eMkxaBBrEwEWYBqLBaw0HitRQK03h/gMDBsGUCCpiJCosLkRQIFgGLENAeTQkqeR9Mh4g4PUNA0FgQSSjANbGvK0InKZNDmdK3ioPp4DyKpQQEOUtxISa4g2e5NAADKmWATQOZZzm+Q6hSqRiRYUEkaDAqW+DClAMWkr2kT+iWoKRfKyQWjC1rCvqCiRMOGVxQlEBWRAGYyHAACyFA4NSJURo1XK8jFlodX5Y4ME1OwKDQ1IufxLApAgjbFFcpGhgw4Z+WoU0wYGgoPita0IBpWk4IG8DtYKpXgKt00TA4HKxi1bX8idEbtFAAASgQ4HpUWlLgN73nVbJIGwpnyGZcBJlAMJhhGbUCt8CA3m1BQMBy5pxDgHBMK0mLwFS902v9gUUJOdm46d7KAcwd7E1aD3/cBCDKlT/UgBA5UIBwFCmbySNenAl1TCAlBlMKuTQLDWDhS0+UoaD0a1AAqogJzgwAtI+miGMrP6q9omsooYlb7vxCDDV8f0HI1JtlGbhzwOzLA3NbjXw2AiOO/AHKKMkNBm0ckRgB7goUN7qDup6fAiUzSr03VDVMGiOJC103K9RKUo7vcSWECQ5DUPQTCsJwvACMIi3hhm+eU2ZaeSy5SBBkk4OvrJ2J3dTeMrPXnQABphBEncQFQvcrMnKoS7WGchxmaVgCnnnXN56aQulo/V8go5gGw7hBByRljcVNPD8vpJj2bHrXBAODxEUbikMJKRgAAWgl0AkxGZ98C6yCv7MhZsOKJ9SwiGUYyzAVSAxUHZB49pXhKDAOTFQmIqagHgGDS0kN4xrCUqgdBIAZbUhwdcBABp8FLVtC8N4YB4rPQ8PYISXgzb+TskIChDDV5ICoTQuhTkXKplZmbeOYA+CzWbOKKo7ZahpFaNoVodABS5HvM1VqJBQa6T6gfW8z9M5okFOGcwGYBF8Fuso1B+926NWBjQcgo0SCrw+IAlYYdnqlgYAaGIdsHZt1OtPFOwVthhVXqfUGLBfHMDQE8cQKQPCmS6EE18rRWjCm4VFCgeQABWM9kAxMdEYocJjv4HHiYkwKYBxJoGRDwCJhSeTxSwWYQw1hbA4AcPiBErh3CeCcoYFkGYnqhMnmCIAA=== +``` +%% \ No newline at end of file diff --git a/vault/dessin_05.excalidraw.md b/vault/dessin_05.excalidraw.md new file mode 100644 index 0000000..c18e14e --- /dev/null +++ b/vault/dessin_05.excalidraw.md @@ -0,0 +1,15 @@ +--- +excalidraw-plugin: parsed +updated: "2025-10-28T16:46:06.902Z" +--- +==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== + +# Excalidraw Data + +## Text Elements +%% +## Drawing +```compressed-json +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 +``` +%% \ No newline at end of file