feat: add test excalidraw page with deep linking support
- Added new test excalidraw page component and route with deep linking via ?tests=excalidraw - Enhanced drawings editor to preserve frontmatter and normalize metadata when saving .excalidraw.md files - Improved excalidraw file handling with better error handling and sanitization - Updated sidebar and navigation to include test excalidraw option - Fixed collaborators map serialization issue in excalidraw scene data
This commit is contained in:
parent
e944f6645a
commit
5647b42d8a
@ -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 @@
|
||||
<app-parameters></app-parameters>
|
||||
} @else if (activeView() === 'tests-panel') {
|
||||
<app-tests-panel></app-tests-panel>
|
||||
} @else if (activeView() === 'tests-excalidraw') {
|
||||
<app-test-excalidraw-page></app-test-excalidraw-page>
|
||||
} @else {
|
||||
@if (activeView() === 'drawings') {
|
||||
@if (currentDrawingPath()) {
|
||||
|
||||
@ -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<boolean>(true);
|
||||
isOutlineOpen = signal<boolean>(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<string | null>(null);
|
||||
selectedNoteId = signal<string>('');
|
||||
sidebarSearchTerm = signal<string>('');
|
||||
@ -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') {
|
||||
|
||||
@ -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<void> {
|
||||
async exportPNG(withBackground: boolean = true): Promise<void> {
|
||||
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<void> {
|
||||
async exportSVG(withBackground: boolean = true): Promise<void> {
|
||||
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');
|
||||
|
||||
@ -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<string, string>();
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
private http = inject(HttpClient);
|
||||
private excalIo = inject(ExcalidrawIoService);
|
||||
|
||||
get(path: string): Observable<ExcalidrawScene> {
|
||||
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<ExcalidrawScene>(url, {
|
||||
params: { path: path },
|
||||
observe: 'response'
|
||||
@ -30,6 +50,22 @@ export class DrawingsFileService {
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch raw text content and record ETag
|
||||
getText(path: string): Observable<string> {
|
||||
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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
</button>
|
||||
<button
|
||||
(click)="testsExcalidrawSelected.emit()"
|
||||
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">
|
||||
Test Excalidraw
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -178,6 +183,7 @@ export class NimbusSidebarComponent implements OnChanges {
|
||||
@Output() quickLinkSelected = new EventEmitter<string>();
|
||||
@Output() markdownPlaygroundSelected = new EventEmitter<void>();
|
||||
@Output() testsPanelSelected = new EventEmitter<void>();
|
||||
@Output() testsExcalidrawSelected = new EventEmitter<void>();
|
||||
@Output() helpPageSelected = new EventEmitter<void>();
|
||||
@Output() aboutSelected = new EventEmitter<void>();
|
||||
|
||||
|
||||
287
src/app/features/tests/test-excalidraw-page.component.ts
Normal file
287
src/app/features/tests/test-excalidraw-page.component.ts
Normal file
@ -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: `
|
||||
<div class="h-full flex flex-col gap-3">
|
||||
<div class="rounded-xl border border-border bg-card shadow-subtle">
|
||||
<div class="flex items-center justify-between px-3 py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2 class="text-sm font-semibold">Test Excalidraw</h2>
|
||||
<span class="text-[10px] font-semibold uppercase tracking-widest rounded-full bg-surface1 px-2 py-0.5 text-muted">Legacy Integration</span>
|
||||
<ng-container *ngIf="currentPath(); else nofile">
|
||||
<span class="ml-2 text-xs text-muted select-all truncate max-w-[32rem]" [title]="currentPath()">{{ currentPath() }}</span>
|
||||
</ng-container>
|
||||
<ng-template #nofile>
|
||||
<span class="ml-2 text-xs text-muted">No file</span>
|
||||
</ng-template>
|
||||
<span class="ml-2 text-xs" [class.text-success]="!dirty()" [class.text-warning]="dirty()">• {{ dirty() ? 'Dirty' : 'Saved' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="createNew()" title="Nouveau">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
||||
<span class="ml-1 hidden sm:inline">Nouveau</span>
|
||||
</button>
|
||||
<div class="relative">
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleOpenPicker()" title="Ouvrir">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5l2 2h9a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="ml-1 hidden sm:inline">Ouvrir</span>
|
||||
</button>
|
||||
<div *ngIf="openPicker()" class="absolute right-0 mt-1 w-80 max-h-80 overflow-y-auto rounded-lg border border-border bg-card shadow-xl z-10">
|
||||
<div class="p-2 text-xs text-muted">Sélectionner un fichier *.excalidraw.md</div>
|
||||
<ul>
|
||||
<li *ngFor="let f of excalidrawFiles()">
|
||||
<button type="button" class="w-full text-left px-3 py-1.5 text-sm hover:bg-surface1" (click)="openFile(f.filePath)">{{ f.filePath }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="closeFile()" [disabled]="!currentPath()" title="Fermer le fichier">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
<span class="ml-1 hidden sm:inline">Fermer</span>
|
||||
</button>
|
||||
<div class="mx-1 h-5 w-px bg-border"></div>
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="saveDebounced()" [disabled]="!currentPath()" title="Enregistrer (Ctrl/Cmd+S)">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h11l5 5v9a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||
<span class="ml-1 hidden sm:inline">Enregistrer</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="saveAs()" title="Enregistrer sous">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M19 12H5"/></svg>
|
||||
<span class="ml-1 hidden sm:inline">Enregistrer sous</span>
|
||||
</button>
|
||||
<div class="mx-1 h-5 w-px bg-border"></div>
|
||||
<div class="relative">
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleExport()" title="Exporter">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7 11l5 5 5-5"/><path d="M5 19h14"/></svg>
|
||||
<span class="ml-1 hidden sm:inline">Exporter</span>
|
||||
</button>
|
||||
<div *ngIf="openExport()" class="absolute right-0 mt-1 w-52 rounded-lg border border-border bg-card shadow-xl z-10">
|
||||
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportPNG(true)">PNG (avec fond)</button>
|
||||
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportPNG(false)">PNG (sans fond)</button>
|
||||
<div class="h-px bg-border mx-2"></div>
|
||||
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportSVG(true)">SVG (embed)</button>
|
||||
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportSVG(false)">SVG</button>
|
||||
<div class="h-px bg-border mx-2"></div>
|
||||
<button class="w-full text-left px-3 py-2 text-sm hover:bg-surface1" (click)="exportJSON()">JSON (télécharger)</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-1 h-5 w-px bg-border"></div>
|
||||
<label class="inline-flex items-center gap-2 text-xs text-muted select-none">
|
||||
<input type="checkbox" [checked]="autosave()" (change)="setAutosave($any($event.target).checked)" /> Autosave 10s
|
||||
</label>
|
||||
<button type="button" class="btn btn-ghost btn-sm" (click)="toggleFullscreen()" title="Plein écran">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M16 3h3a2 2 0 0 1 2 2v3"/><path d="M8 21H5a2 2 0 0 1-2-2v-3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-h-0">
|
||||
<ng-container *ngIf="currentPath(); else noSelection">
|
||||
<app-drawings-editor #editor [path]="currentPath()!"></app-drawings-editor>
|
||||
</ng-container>
|
||||
<ng-template #noSelection>
|
||||
<div class="h-full flex items-center justify-center rounded-xl border border-border bg-card text-muted">
|
||||
<div class="text-center space-y-2">
|
||||
<div class="text-sm">Aucun fichier Excalidraw sélectionné</div>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button class="btn btn-sm" (click)="createNew()">Nouveau</button>
|
||||
<button class="btn btn-sm btn-ghost" (click)="toggleOpenPicker()">Ouvrir…</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
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<string | null>(null);
|
||||
openPicker = signal(false);
|
||||
openExport = signal(false);
|
||||
autosave = signal(false);
|
||||
dirty = signal(false);
|
||||
|
||||
private autosaveTimer: any = null;
|
||||
private saveTimer: any = null;
|
||||
|
||||
excalidrawFiles = signal<Array<{ filePath: string }>>([]);
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
if (this.saveTimer) clearTimeout(this.saveTimer);
|
||||
this.saveTimer = setTimeout(() => this.save(), 1200);
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
await this.editor?.saveNow();
|
||||
}
|
||||
|
||||
async saveAs(): Promise<void> {
|
||||
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<void> {
|
||||
await this.editor?.exportPNG(withBg);
|
||||
this.openExport.set(false);
|
||||
}
|
||||
|
||||
async exportSVG(withBg: boolean): Promise<void> {
|
||||
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<void> {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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: `
|
||||
<div class="relative h-screen flex flex-col bg-card dark:bg-main text-main dark:text-gray-100" [style.--sidebar-width.px]="isSidebarOpen ? leftSidebarWidth : 64">
|
||||
|
||||
<!-- Fullscreen overlay for note -->
|
||||
<div *ngIf="noteFullScreen && selectedNote && activeView !== 'markdown-playground'" class="absolute inset-0 z-50 flex flex-col bg-card dark:bg-main">
|
||||
<div *ngIf="noteFullScreen && selectedNote && activeView !== 'markdown-playground' && activeView !== 'tests-excalidraw'" class="absolute inset-0 z-50 flex flex-col bg-card dark:bg-main">
|
||||
<div class="note-content-area flex-1 overflow-y-auto px-4 py-4 lg:px-12" appScrollableOverlay>
|
||||
<app-note-viewer
|
||||
[note]="selectedNote || null"
|
||||
@ -74,6 +75,7 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
||||
(quickLinkSelected)="onQuickLink($event)"
|
||||
(markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()"
|
||||
(testsPanelSelected)="onTestsPanelSelected()"
|
||||
(testsExcalidrawSelected)="onTestsExcalidrawSelected()"
|
||||
(helpPageSelected)="onHelpPageSelected()"
|
||||
(aboutSelected)="onAboutSelected()"
|
||||
(noteCreated)="onNoteCreated($event)"
|
||||
@ -146,6 +148,7 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
||||
<div *ngSwitchCase="'tests'" class="p-3">
|
||||
<button type="button" class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card" (click)="onTestsPanelSelected(); $event.stopPropagation(); hoveredFlyout = null">🔬 <span>API Tests Panel</span></button>
|
||||
<button type="button" class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card" (click)="onMarkdownPlaygroundSelected(); $event.stopPropagation(); hoveredFlyout = null">📝 <span>Markdown Playground</span></button>
|
||||
<button type="button" class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card" (click)="onTestsExcalidrawSelected(); $event.stopPropagation(); hoveredFlyout = null">🎨 <span>Test Excalidraw</span></button>
|
||||
</div>
|
||||
<div *ngSwitchCase="'playground'" class="p-3">
|
||||
<button type="button" class="w-full text-left flex items-center gap-2 px-3 py-2 text-sm rounded-lg hover:bg-surface1 dark:hover:bg-card" (click)="onMarkdownPlaygroundSelected(); $event.stopPropagation(); hoveredFlyout = null">🧪 <span>Markdown Playground</span></button>
|
||||
@ -187,7 +190,8 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
||||
<app-markdown-playground *ngIf="activeView === 'markdown-playground'"></app-markdown-playground>
|
||||
<app-parameters *ngIf="activeView === 'parameters'"></app-parameters>
|
||||
<app-tests-panel *ngIf="activeView === 'tests-panel'"></app-tests-panel>
|
||||
<app-note-viewer *ngIf="activeView !== 'markdown-playground' && activeView !== 'parameters' && activeView !== 'tests-panel'"
|
||||
<app-test-excalidraw-page *ngIf="activeView === 'tests-excalidraw'"></app-test-excalidraw-page>
|
||||
<app-note-viewer *ngIf="activeView !== 'markdown-playground' && activeView !== 'parameters' && activeView !== 'tests-panel' && activeView !== 'tests-excalidraw'"
|
||||
[note]="selectedNote || null"
|
||||
[noteHtmlContent]="renderedNoteContent"
|
||||
[allNotes]="vault.allNotes()"
|
||||
@ -255,7 +259,8 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
||||
<app-markdown-playground *ngIf="activeView === 'markdown-playground'"></app-markdown-playground>
|
||||
<app-parameters *ngIf="activeView === 'parameters'"></app-parameters>
|
||||
<app-tests-panel *ngIf="activeView === 'tests-panel'"></app-tests-panel>
|
||||
<app-note-viewer *ngIf="activeView !== 'markdown-playground' && activeView !== 'parameters' && activeView !== 'tests-panel'" [note]="selectedNote || null" [noteHtmlContent]="renderedNoteContent" [allNotes]="vault.allNotes()" (noteLinkClicked)="noteSelected.emit($event)" (tagClicked)="onTagSelected($event)" (wikiLinkActivated)="wikiLinkActivated.emit($event)" [fullScreenActive]="noteFullScreen" (fullScreenRequested)="toggleNoteFullScreen()" (legacyRequested)="ui.toggleUIMode()" (parametersRequested)="onParametersOpen()" (showToc)="mobileNav.toggleToc()" (directoryClicked)="onFolderSelected($event)" [tocOpen]="mobileNav.tocOpen()"></app-note-viewer>
|
||||
<app-test-excalidraw-page *ngIf="activeView === 'tests-excalidraw'"></app-test-excalidraw-page>
|
||||
<app-note-viewer *ngIf="activeView !== 'markdown-playground' && activeView !== 'parameters' && activeView !== 'tests-panel' && activeView !== 'tests-excalidraw'" [note]="selectedNote || null" [noteHtmlContent]="renderedNoteContent" [allNotes]="vault.allNotes()" (noteLinkClicked)="noteSelected.emit($event)" (tagClicked)="onTagSelected($event)" (wikiLinkActivated)="wikiLinkActivated.emit($event)" [fullScreenActive]="noteFullScreen" (fullScreenRequested)="toggleNoteFullScreen()" (legacyRequested)="ui.toggleUIMode()" (parametersRequested)="onParametersOpen()" (showToc)="mobileNav.toggleToc()" (directoryClicked)="onFolderSelected($event)" [tocOpen]="mobileNav.tocOpen()"></app-note-viewer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -297,6 +302,10 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
||||
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
||||
<app-tests-panel></app-tests-panel>
|
||||
</div>
|
||||
} @else if (activeView === 'tests-excalidraw') {
|
||||
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
||||
<app-test-excalidraw-page></app-test-excalidraw-page>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
||||
@if (selectedNote) {
|
||||
@ -370,6 +379,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
||||
@Output() parametersOpened = new EventEmitter<void>();
|
||||
@Output() helpPageRequested = new EventEmitter<void>();
|
||||
@Output() testsPanelRequested = new EventEmitter<void>();
|
||||
@Output() testsExcalidrawRequested = new EventEmitter<void>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
78
vault/.obsidian/workspace.json
vendored
78
vault/.obsidian/workspace.json
vendored
@ -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"
|
||||
]
|
||||
}
|
||||
49
vault/Dessin_001.excalidraw.md
Normal file
49
vault/Dessin_001.excalidraw.md
Normal file
@ -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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
|
||||
```
|
||||
%%
|
||||
@ -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
|
||||
```
|
||||
%%
|
||||
@ -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
|
||||
```
|
||||
%%
|
||||
21
vault/dessin-002.excalidraw.md
Normal file
21
vault/dessin-002.excalidraw.md
Normal file
@ -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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
|
||||
```
|
||||
%%
|
||||
21
vault/dessin_03.excalidraw.md
Normal file
21
vault/dessin_03.excalidraw.md
Normal file
@ -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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=
|
||||
```
|
||||
%%
|
||||
21
vault/dessin_04.excalidraw.md
Normal file
21
vault/dessin_04.excalidraw.md
Normal file
@ -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===
|
||||
```
|
||||
%%
|
||||
15
vault/dessin_05.excalidraw.md
Normal file
15
vault/dessin_05.excalidraw.md
Normal file
@ -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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
|
||||
```
|
||||
%%
|
||||
Loading…
x
Reference in New Issue
Block a user