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')"
|
(markdownPlaygroundSelected)="setView('markdown-playground')"
|
||||||
(parametersOpened)="setView('parameters')"
|
(parametersOpened)="setView('parameters')"
|
||||||
(testsPanelRequested)="setView('tests-panel')"
|
(testsPanelRequested)="setView('tests-panel')"
|
||||||
|
(testsExcalidrawRequested)="setView('tests-excalidraw')"
|
||||||
(helpPageRequested)="openHelpPage()"
|
(helpPageRequested)="openHelpPage()"
|
||||||
(noteCreated)="onNoteCreated($event)"
|
(noteCreated)="onNoteCreated($event)"
|
||||||
(noteCreatedAndSelected)="onNoteCreatedAndSelected($event)"
|
(noteCreatedAndSelected)="onNoteCreatedAndSelected($event)"
|
||||||
@ -541,6 +542,8 @@
|
|||||||
<app-parameters></app-parameters>
|
<app-parameters></app-parameters>
|
||||||
} @else if (activeView() === 'tests-panel') {
|
} @else if (activeView() === 'tests-panel') {
|
||||||
<app-tests-panel></app-tests-panel>
|
<app-tests-panel></app-tests-panel>
|
||||||
|
} @else if (activeView() === 'tests-excalidraw') {
|
||||||
|
<app-test-excalidraw-page></app-test-excalidraw-page>
|
||||||
} @else {
|
} @else {
|
||||||
@if (activeView() === 'drawings') {
|
@if (activeView() === 'drawings') {
|
||||||
@if (currentDrawingPath()) {
|
@if (currentDrawingPath()) {
|
||||||
|
|||||||
@ -38,6 +38,8 @@ import { LayoutModule } from '@angular/cdk/layout';
|
|||||||
import { ToastContainerComponent } from './app/shared/toast/toast-container.component';
|
import { ToastContainerComponent } from './app/shared/toast/toast-container.component';
|
||||||
import { ParametersPage } from './app/features/parameters/parameters.page';
|
import { ParametersPage } from './app/features/parameters/parameters.page';
|
||||||
import { TestsPanelComponent } from './app/features/tests/tests-panel.component';
|
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
|
// Types
|
||||||
import { FileMetadata, Note, TagInfo, VaultNode } from './types';
|
import { FileMetadata, Note, TagInfo, VaultNode } from './types';
|
||||||
@ -71,6 +73,7 @@ interface TocEntry {
|
|||||||
ToastContainerComponent,
|
ToastContainerComponent,
|
||||||
ParametersPage,
|
ParametersPage,
|
||||||
TestsPanelComponent,
|
TestsPanelComponent,
|
||||||
|
TestExcalidrawPageComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './app.component.simple.html',
|
templateUrl: './app.component.simple.html',
|
||||||
styleUrls: ['./app.component.css'],
|
styleUrls: ['./app.component.css'],
|
||||||
@ -93,12 +96,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private readonly drawingsFiles = inject(DrawingsFileService);
|
private readonly drawingsFiles = inject(DrawingsFileService);
|
||||||
private readonly urlState = inject(UrlStateService);
|
private readonly urlState = inject(UrlStateService);
|
||||||
private readonly editorState = inject(EditorStateService);
|
private readonly editorState = inject(EditorStateService);
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
// --- State Signals ---
|
// --- State Signals ---
|
||||||
isSidebarOpen = signal<boolean>(true);
|
isSidebarOpen = signal<boolean>(true);
|
||||||
isOutlineOpen = signal<boolean>(false);
|
isOutlineOpen = signal<boolean>(false);
|
||||||
outlineTab = signal<'outline' | 'settings'>('outline');
|
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);
|
currentDrawingPath = signal<string | null>(null);
|
||||||
selectedNoteId = signal<string>('');
|
selectedNoteId = signal<string>('');
|
||||||
sidebarSearchTerm = signal<string>('');
|
sidebarSearchTerm = signal<string>('');
|
||||||
@ -140,6 +144,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
this.editorState.enterEditMode(note.filePath, content);
|
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 {
|
onNoteCreatedAndSelected(event: { id: string; filePath: string }): void {
|
||||||
@ -978,10 +989,16 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
handle?.addEventListener('lostpointercapture', cleanup);
|
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();
|
const previousView = this.activeView();
|
||||||
this.activeView.set(view);
|
this.activeView.set(view);
|
||||||
this.sidebarSearchTerm.set('');
|
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
|
// Log view changes
|
||||||
if (view === 'bookmarks' && previousView !== 'bookmarks') {
|
if (view === 'bookmarks' && previousView !== 'bookmarks') {
|
||||||
|
|||||||
@ -223,8 +223,28 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
const isExMd = /\.excalidraw\.md$/i.test(this.path || '');
|
const isExMd = /\.excalidraw\.md$/i.test(this.path || '');
|
||||||
let result: { rev: string } | null = null;
|
let result: { rev: string } | null = null;
|
||||||
if (isExMd) {
|
if (isExMd) {
|
||||||
// Use same compression as creation: build Obsidian MD with ```compressed-json```
|
// Preserve existing frontmatter and normalize keys
|
||||||
const md = this.excalIo.toObsidianMd(scene as any, null);
|
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));
|
result = await firstValueFrom(this.files.putText(this.path, md));
|
||||||
} else {
|
} else {
|
||||||
// For .excalidraw or .json let server persist plain JSON
|
// For .excalidraw or .json let server persist plain JSON
|
||||||
@ -420,13 +440,20 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
private prepareSceneData(scene: ExcalidrawScene) {
|
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 {
|
return {
|
||||||
elements: Array.isArray(scene.elements) ? scene.elements : [],
|
elements: Array.isArray(scene.elements) ? scene.elements : [],
|
||||||
appState: {
|
appState: {
|
||||||
viewBackgroundColor: '#1e1e1e',
|
viewBackgroundColor: '#1e1e1e',
|
||||||
currentItemFontFamily: 1,
|
currentItemFontFamily: 1,
|
||||||
theme: this.themeName(),
|
theme: this.themeName(),
|
||||||
...(scene.appState || {})
|
...incomingApp
|
||||||
},
|
},
|
||||||
scrollToContent: true,
|
scrollToContent: true,
|
||||||
...(scene.files && { files: scene.files })
|
...(scene.files && { files: scene.files })
|
||||||
@ -678,7 +705,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportPNG(): Promise<void> {
|
async exportPNG(withBackground: boolean = true): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const host = this.editorEl?.nativeElement;
|
const host = this.editorEl?.nativeElement;
|
||||||
if (!host || !this.excalidrawReady) {
|
if (!host || !this.excalidrawReady) {
|
||||||
@ -687,7 +714,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await this.previews.exportPNGFromElement(host, true);
|
const blob = await this.previews.exportPNGFromElement(host, withBackground);
|
||||||
if (!blob) {
|
if (!blob) {
|
||||||
console.error('Failed to generate PNG blob');
|
console.error('Failed to generate PNG blob');
|
||||||
this.showToast('Export PNG indisponible', 'error');
|
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 {
|
try {
|
||||||
const host = this.editorEl?.nativeElement;
|
const host = this.editorEl?.nativeElement;
|
||||||
if (!host || !this.excalidrawReady) {
|
if (!host || !this.excalidrawReady) {
|
||||||
@ -714,7 +741,7 @@ export class DrawingsEditorComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await this.previews.exportSVGFromElement(host, true);
|
const blob = await this.previews.exportSVGFromElement(host, withBackground);
|
||||||
if (!blob) {
|
if (!blob) {
|
||||||
console.error('Failed to generate SVG blob');
|
console.error('Failed to generate SVG blob');
|
||||||
this.showToast('Export SVG indisponible', 'error');
|
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 { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable, map, tap } from 'rxjs';
|
import { Observable, map, tap } from 'rxjs';
|
||||||
|
import { ExcalidrawIoService } from './excalidraw-io.service';
|
||||||
|
|
||||||
export type ThemeName = 'light' | 'dark';
|
export type ThemeName = 'light' | 'dark';
|
||||||
|
|
||||||
@ -14,10 +15,29 @@ export interface ExcalidrawScene {
|
|||||||
export class DrawingsFileService {
|
export class DrawingsFileService {
|
||||||
private etagCache = new Map<string, string>();
|
private etagCache = new Map<string, string>();
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
private http = inject(HttpClient);
|
||||||
|
private excalIo = inject(ExcalidrawIoService);
|
||||||
|
|
||||||
get(path: string): Observable<ExcalidrawScene> {
|
get(path: string): Observable<ExcalidrawScene> {
|
||||||
const url = `/api/files`;
|
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, {
|
return this.http.get<ExcalidrawScene>(url, {
|
||||||
params: { path: path },
|
params: { path: path },
|
||||||
observe: 'response'
|
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 }> {
|
put(path: string, scene: ExcalidrawScene): Observable<{ rev: string }> {
|
||||||
const url = `/api/files`;
|
const url = `/api/files`;
|
||||||
const prev = this.etagCache.get(path);
|
const prev = this.etagCache.get(path);
|
||||||
|
|||||||
@ -18,10 +18,26 @@ export class ExcalidrawIoService {
|
|||||||
*/
|
*/
|
||||||
extractFrontMatter(md: string): string | null {
|
extractFrontMatter(md: string): string | null {
|
||||||
if (!md) return 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;
|
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
|
* Parse Obsidian Excalidraw markdown format
|
||||||
* Extracts compressed-json block and decompresses using LZ-String
|
* Extracts compressed-json block and decompresses using LZ-String
|
||||||
@ -29,14 +45,19 @@ export class ExcalidrawIoService {
|
|||||||
parseObsidianMd(md: string): ExcalidrawScene | null {
|
parseObsidianMd(md: string): ExcalidrawScene | null {
|
||||||
if (!md || typeof md !== 'string') return null;
|
if (!md || typeof md !== 'string') return null;
|
||||||
|
|
||||||
// Try to extract compressed-json block
|
// Try to extract compressed-json block anywhere (Obsidian may wrap with %% comments or not)
|
||||||
const compressedMatch = md.match(/```\s*compressed-json\s*\n([\s\S]*?)\n```/i);
|
const compressedMatch = md.match(/```\s*compressed-json\s*\r?\n([\s\S]*?)\r?\n```/i);
|
||||||
|
|
||||||
if (compressedMatch && compressedMatch[1]) {
|
if (compressedMatch && compressedMatch[1]) {
|
||||||
try {
|
try {
|
||||||
// Remove whitespace from base64 data
|
// Remove ALL whitespace (including newlines, spaces, tabs) from base64 data
|
||||||
const compressed = compressedMatch[1].replace(/\s+/g, '');
|
const compressed = compressedMatch[1].replace(/\s+/g, '');
|
||||||
|
|
||||||
|
if (!compressed) {
|
||||||
|
console.warn('[Excalidraw] Empty compressed data');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Decompress using LZ-String
|
// Decompress using LZ-String
|
||||||
const decompressed = LZString.decompressFromBase64(compressed);
|
const decompressed = LZString.decompressFromBase64(compressed);
|
||||||
|
|
||||||
@ -50,19 +71,26 @@ export class ExcalidrawIoService {
|
|||||||
return this.normalizeScene(data);
|
return this.normalizeScene(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Excalidraw] Failed to parse compressed-json:', error);
|
console.error('[Excalidraw] Failed to parse compressed-json:', error);
|
||||||
|
console.error('[Excalidraw] Error details:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: try to extract plain json block
|
// Fallback: try to extract plain json fence anywhere
|
||||||
const jsonMatch = md.match(/```\s*(?:excalidraw|json)\s*\n([\s\S]*?)\n```/i);
|
const jsonMatch = md.match(/```\s*json\s*\r?\n([\s\S]*?)\r?\n```/i);
|
||||||
|
|
||||||
if (jsonMatch && jsonMatch[1]) {
|
if (jsonMatch && jsonMatch[1]) {
|
||||||
try {
|
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);
|
return this.normalizeScene(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Excalidraw] Failed to parse json block:', error);
|
console.error('[Excalidraw] Failed to parse json block:', error);
|
||||||
|
console.error('[Excalidraw] JSON string:', jsonMatch[1].substring(0, 100));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,28 +122,35 @@ export class ExcalidrawIoService {
|
|||||||
* Convert Excalidraw scene to Obsidian markdown format
|
* Convert Excalidraw scene to Obsidian markdown format
|
||||||
*/
|
*/
|
||||||
toObsidianMd(data: ExcalidrawScene, existingFrontMatter?: string | null): string {
|
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 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
|
// Serialize and compress
|
||||||
const json = JSON.stringify(scene);
|
const json = JSON.stringify(payload);
|
||||||
|
|
||||||
// Compress using LZ-String
|
|
||||||
const compressed = LZString.compressToBase64(json);
|
const compressed = LZString.compressToBase64(json);
|
||||||
|
|
||||||
|
if (!compressed) {
|
||||||
|
throw new Error('[Excalidraw] Failed to compress data');
|
||||||
|
}
|
||||||
|
|
||||||
// Use existing front matter or create default
|
// Use existing front matter or create default
|
||||||
const frontMatter = existingFrontMatter?.trim() || `---
|
const frontMatter = existingFrontMatter || `---
|
||||||
excalidraw-plugin: parsed
|
excalidraw-plugin: parsed
|
||||||
tags: [excalidraw]
|
tags: [excalidraw]
|
||||||
---`;
|
---`;
|
||||||
|
|
||||||
// Banner text (Obsidian standard)
|
// Construct Obsidian structure with only the compressed-json block (as used by the plugin)
|
||||||
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
|
|
||||||
return `${frontMatter}
|
return `${frontMatter}
|
||||||
${banner}
|
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==
|
||||||
|
|
||||||
# Excalidraw Data
|
# Excalidraw Data
|
||||||
|
|
||||||
@ -163,4 +198,4 @@ ${compressed}
|
|||||||
if (!Array.isArray(data.elements)) return false;
|
if (!Array.isArray(data.elements)) return false;
|
||||||
return true;
|
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">
|
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
|
API Tests Panel
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -178,6 +183,7 @@ export class NimbusSidebarComponent implements OnChanges {
|
|||||||
@Output() quickLinkSelected = new EventEmitter<string>();
|
@Output() quickLinkSelected = new EventEmitter<string>();
|
||||||
@Output() markdownPlaygroundSelected = new EventEmitter<void>();
|
@Output() markdownPlaygroundSelected = new EventEmitter<void>();
|
||||||
@Output() testsPanelSelected = new EventEmitter<void>();
|
@Output() testsPanelSelected = new EventEmitter<void>();
|
||||||
|
@Output() testsExcalidrawSelected = new EventEmitter<void>();
|
||||||
@Output() helpPageSelected = new EventEmitter<void>();
|
@Output() helpPageSelected = new EventEmitter<void>();
|
||||||
@Output() aboutSelected = 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 { ScrollableOverlayDirective } from '../../shared/overlay-scrollbar/scrollable-overlay.directive';
|
||||||
import { MarkdownPlaygroundComponent } from '../../features/tests/markdown-playground/markdown-playground.component';
|
import { MarkdownPlaygroundComponent } from '../../features/tests/markdown-playground/markdown-playground.component';
|
||||||
import { TestsPanelComponent } from '../../features/tests/tests-panel.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 { ParametersPage } from '../../features/parameters/parameters.page';
|
||||||
import { AboutPanelComponent } from '../../features/about/about-panel.component';
|
import { AboutPanelComponent } from '../../features/about/about-panel.component';
|
||||||
import { UrlStateService } from '../../services/url-state.service';
|
import { UrlStateService } from '../../services/url-state.service';
|
||||||
@ -28,12 +29,12 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-shell-nimbus-layout',
|
selector: 'app-shell-nimbus-layout',
|
||||||
standalone: true,
|
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: `
|
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">
|
<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 -->
|
<!-- 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>
|
<div class="note-content-area flex-1 overflow-y-auto px-4 py-4 lg:px-12" appScrollableOverlay>
|
||||||
<app-note-viewer
|
<app-note-viewer
|
||||||
[note]="selectedNote || null"
|
[note]="selectedNote || null"
|
||||||
@ -74,6 +75,7 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
|||||||
(quickLinkSelected)="onQuickLink($event)"
|
(quickLinkSelected)="onQuickLink($event)"
|
||||||
(markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()"
|
(markdownPlaygroundSelected)="onMarkdownPlaygroundSelected()"
|
||||||
(testsPanelSelected)="onTestsPanelSelected()"
|
(testsPanelSelected)="onTestsPanelSelected()"
|
||||||
|
(testsExcalidrawSelected)="onTestsExcalidrawSelected()"
|
||||||
(helpPageSelected)="onHelpPageSelected()"
|
(helpPageSelected)="onHelpPageSelected()"
|
||||||
(aboutSelected)="onAboutSelected()"
|
(aboutSelected)="onAboutSelected()"
|
||||||
(noteCreated)="onNoteCreated($event)"
|
(noteCreated)="onNoteCreated($event)"
|
||||||
@ -146,6 +148,7 @@ import { InPageSearchOverlayComponent } from '../../shared/search/in-page-search
|
|||||||
<div *ngSwitchCase="'tests'" class="p-3">
|
<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)="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)="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>
|
||||||
<div *ngSwitchCase="'playground'" class="p-3">
|
<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>
|
<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-markdown-playground *ngIf="activeView === 'markdown-playground'"></app-markdown-playground>
|
||||||
<app-parameters *ngIf="activeView === 'parameters'"></app-parameters>
|
<app-parameters *ngIf="activeView === 'parameters'"></app-parameters>
|
||||||
<app-tests-panel *ngIf="activeView === 'tests-panel'"></app-tests-panel>
|
<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"
|
[note]="selectedNote || null"
|
||||||
[noteHtmlContent]="renderedNoteContent"
|
[noteHtmlContent]="renderedNoteContent"
|
||||||
[allNotes]="vault.allNotes()"
|
[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-markdown-playground *ngIf="activeView === 'markdown-playground'"></app-markdown-playground>
|
||||||
<app-parameters *ngIf="activeView === 'parameters'"></app-parameters>
|
<app-parameters *ngIf="activeView === 'parameters'"></app-parameters>
|
||||||
<app-tests-panel *ngIf="activeView === 'tests-panel'"></app-tests-panel>
|
<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>
|
</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>
|
<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>
|
<app-tests-panel></app-tests-panel>
|
||||||
</div>
|
</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 {
|
} @else {
|
||||||
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
<div class="note-content-area h-full px-3 py-3 animate-fadeIn" style="overflow-y: auto !important;" appScrollableOverlay>
|
||||||
@if (selectedNote) {
|
@if (selectedNote) {
|
||||||
@ -370,6 +379,7 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
|||||||
@Output() parametersOpened = new EventEmitter<void>();
|
@Output() parametersOpened = new EventEmitter<void>();
|
||||||
@Output() helpPageRequested = new EventEmitter<void>();
|
@Output() helpPageRequested = new EventEmitter<void>();
|
||||||
@Output() testsPanelRequested = new EventEmitter<void>();
|
@Output() testsPanelRequested = new EventEmitter<void>();
|
||||||
|
@Output() testsExcalidrawRequested = new EventEmitter<void>();
|
||||||
|
|
||||||
folderFilter: string | null = null;
|
folderFilter: string | null = null;
|
||||||
listQuery: string = '';
|
listQuery: string = '';
|
||||||
@ -857,6 +867,10 @@ export class AppShellNimbusLayoutComponent implements AfterViewInit {
|
|||||||
this.testsPanelRequested.emit();
|
this.testsPanelRequested.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTestsExcalidrawSelected(): void {
|
||||||
|
this.testsExcalidrawRequested.emit();
|
||||||
|
}
|
||||||
|
|
||||||
onParametersOpen(): void {
|
onParametersOpen(): void {
|
||||||
this.parametersOpened.emit();
|
this.parametersOpened.emit();
|
||||||
}
|
}
|
||||||
|
|||||||
78
vault/.obsidian/workspace.json
vendored
78
vault/.obsidian/workspace.json
vendored
@ -4,19 +4,17 @@
|
|||||||
"type": "split",
|
"type": "split",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "22c2ec9062224eb0",
|
"id": "b123bf2cfbaf681c",
|
||||||
"type": "tabs",
|
"type": "tabs",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "9ad6b7932a502fb2",
|
"id": "a71c9cf3bbb86552",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"state": {
|
"state": {
|
||||||
"type": "excalidraw",
|
"type": "empty",
|
||||||
"state": {
|
"state": {},
|
||||||
"file": "Drawing 2025-10-27 16.43.48.excalidraw.md"
|
"icon": "lucide-file",
|
||||||
},
|
"title": "New tab"
|
||||||
"icon": "excalidraw-icon",
|
|
||||||
"title": "Drawing 2025-10-27 16.43.48.excalidraw"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -161,7 +159,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 234.5
|
"width": 200,
|
||||||
|
"collapsed": true
|
||||||
},
|
},
|
||||||
"left-ribbon": {
|
"left-ribbon": {
|
||||||
"hiddenItems": {
|
"hiddenItems": {
|
||||||
@ -175,32 +174,43 @@
|
|||||||
"obsidian-excalidraw-plugin:New drawing": false
|
"obsidian-excalidraw-plugin:New drawing": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "9ad6b7932a502fb2",
|
"active": "a71c9cf3bbb86552",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"Drawing 2025-10-27 16.43.48.excalidraw.md.tmp",
|
"dessin_05.excalidraw.md",
|
||||||
"Drawing 2025-10-27 16.43.48.excalidraw.md.bak",
|
"dessin_03.excalidraw.md",
|
||||||
"test-drawing.excalidraw.md",
|
"dessin_05.excalidraw.md.bak",
|
||||||
"Drawing 2025-10-27 16.43.48.excalidraw.md",
|
"dessin_04.excalidraw.md.tmp",
|
||||||
"Untitled.canvas",
|
"dessin_04.excalidraw.md.bak",
|
||||||
"test.md",
|
"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",
|
"HOME.md",
|
||||||
"folder1/test2.md",
|
"Drawing-20251027-1914.excalidraw.md",
|
||||||
"folder2/test2.md",
|
"Drawing-20251027-1705.excalidraw.md",
|
||||||
"folder2",
|
"Drawing 2025-10-27 16.43.48.excalidraw.md",
|
||||||
"folder1",
|
"test-drawing.excalidraw.md",
|
||||||
"NonExistentNote.md",
|
"Untitled.canvas",
|
||||||
"tata/titi-coco.md",
|
"test.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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
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