From 67c6e83bf96caf962c7efa672a0a136d7ded114f Mon Sep 17 00:00:00 2001 From: JK Date: Sun, 14 Jun 2026 11:20:28 +0200 Subject: [PATCH] feat(tikz): add new circuit and flowchart components to AssetsManager; enhance library tab functionality in LeftSidebar --- src/features/tikz-editor/assets-manager.ts | 63 ++++++++++++++ .../tikz-editor/components/left-sidebar.ts | 83 +++++++++++++++---- src/features/tikz-editor/tikz-editor-modal.ts | 3 + src/settings/settings.ts | 4 +- src/styles/main.css | 3 +- 5 files changed, 137 insertions(+), 19 deletions(-) diff --git a/src/features/tikz-editor/assets-manager.ts b/src/features/tikz-editor/assets-manager.ts index 9fdb06d..041dd68 100644 --- a/src/features/tikz-editor/assets-manager.ts +++ b/src/features/tikz-editor/assets-manager.ts @@ -135,6 +135,34 @@ export class AssetsManager { category: 'Circuits', svgMarkup: ``, tikzCommand: '\\draw ({x}, {y}) to[L, l={label}] ({x2}, {y2});' + }, + { + name: 'Battery', + type: 'wire', + category: 'Circuits', + svgMarkup: ``, + tikzCommand: '\\draw ({x}, {y}) to[battery2, l={label}] ({x2}, {y2});' + }, + { + name: 'Diode', + type: 'wire', + category: 'Circuits', + svgMarkup: ``, + tikzCommand: '\\draw ({x}, {y}) to[D, l={label}] ({x2}, {y2});' + }, + { + name: 'AC Source', + type: 'wire', + category: 'Circuits', + svgMarkup: ``, + tikzCommand: '\\draw ({x}, {y}) to[vsourcesin, l={label}] ({x2}, {y2});' + }, + { + name: 'Ground', + type: 'component', + category: 'Circuits', + svgMarkup: ``, + tikzCommand: '\\draw ({x}, {y}) node[ground] {{{label}}};' } ] }, @@ -167,6 +195,41 @@ export class AssetsManager { tikzCommand: '\\node[not gate US, draw] at ({x}, {y}) (not1) {{label}};' } ] + }, + { + name: 'tikz-flowchart', + displayName: 'Flowcharts', + installed: false, + components: [ + { + name: 'Start/End', + type: 'component', + category: 'Flowcharts', + svgMarkup: ``, + tikzCommand: '\\node[draw, rounded corners=10pt] at ({x}, {y}) (start1) {{label}};' + }, + { + name: 'Process', + type: 'component', + category: 'Flowcharts', + svgMarkup: ``, + tikzCommand: '\\node[draw, minimum width=1.5cm, minimum height=0.8cm] at ({x}, {y}) (proc1) {{label}};' + }, + { + name: 'Decision', + type: 'component', + category: 'Flowcharts', + svgMarkup: ``, + tikzCommand: '\\node[draw, diamond, aspect=1.5] at ({x}, {y}) (dec1) {{label}};' + }, + { + name: 'Input/Output', + type: 'component', + category: 'Flowcharts', + svgMarkup: ``, + tikzCommand: '\\node[draw, trapezium, trapezium left angle=70, trapezium right angle=110] at ({x}, {y}) (io1) {{label}};' + } + ] } ]; diff --git a/src/features/tikz-editor/components/left-sidebar.ts b/src/features/tikz-editor/components/left-sidebar.ts index 2567781..1e8fa6f 100644 --- a/src/features/tikz-editor/components/left-sidebar.ts +++ b/src/features/tikz-editor/components/left-sidebar.ts @@ -2,6 +2,8 @@ import { type TikzEditorContext, type ComponentTemplate } from '../types'; import { AssetsManager } from '../assets-manager'; export class LeftSidebar { + private activeLibraryTab: 'basic' | 'all' = 'basic'; + constructor( private context: TikzEditorContext, private containerEl: HTMLElement @@ -126,6 +128,33 @@ export class LeftSidebar { this.renderLibraryList(); }; + // 2.5 Library Tabs + const tabsContainer = this.containerEl.createDiv({ cls: 'library-tabs' }); + const basicTabBtn = tabsContainer.createEl('button', { + cls: `tab-btn${this.activeLibraryTab === 'basic' ? ' active' : ''}`, + text: 'Basic' + }); + const allTabBtn = tabsContainer.createEl('button', { + cls: `tab-btn${this.activeLibraryTab === 'all' ? ' active' : ''}`, + text: 'All' + }); + + basicTabBtn.onclick = () => { + if (this.activeLibraryTab === 'basic') return; + this.activeLibraryTab = 'basic'; + basicTabBtn.addClass('active'); + allTabBtn.removeClass('active'); + this.renderLibraryList(); + }; + + allTabBtn.onclick = () => { + if (this.activeLibraryTab === 'all') return; + this.activeLibraryTab = 'all'; + allTabBtn.addClass('active'); + basicTabBtn.removeClass('active'); + this.renderLibraryList(); + }; + // 3. Component Library List Container this.containerEl.createDiv({ cls: 'library' }); this.renderLibraryList(); @@ -209,7 +238,9 @@ export class LeftSidebar { // Category components builder const core = AssetsManager.getCoreComponents(); - const extra = this.context.getPackages().flatMap(p => p.components); + const extra = this.context.getPackages() + .filter(p => p.installed) + .flatMap(p => p.components); const all = [...core, ...extra]; const pinnedComponents = this.context.getPinnedComponents(); @@ -218,26 +249,44 @@ export class LeftSidebar { ); const basic = [...core, ...pinned]; - // Filter by query - const searchQuery = this.context.getSearchQuery(); - let filtered = all; - if (searchQuery.trim()) { - filtered = all.filter( - c => - c.name.toLowerCase().includes(searchQuery.toLowerCase()) || - c.category.toLowerCase().includes(searchQuery.toLowerCase()) - ); - } + const searchQuery = this.context.getSearchQuery().trim().toLowerCase(); const categoriesMap = new Map(); - if (!searchQuery.trim()) { - categoriesMap.set('Basic', basic); + + if (this.activeLibraryTab === 'basic') { + // Basic Tab: Show commonly used (basic) components, optionally filtered by search + const filteredBasic = searchQuery + ? basic.filter( + c => + c.name.toLowerCase().includes(searchQuery) || + c.category.toLowerCase().includes(searchQuery) + ) + : basic; + + if (filteredBasic.length > 0) { + categoriesMap.set('Basic', filteredBasic); + } } else { - filtered.forEach(c => { - if (!categoriesMap.has(c.category)) { - categoriesMap.set(c.category, []); + // All Tab: Show all components grouped by their categories + const filteredAll = searchQuery + ? all.filter( + c => + c.name.toLowerCase().includes(searchQuery) || + c.category.toLowerCase().includes(searchQuery) + ) + : all; + + filteredAll.forEach(c => { + // Core components are displayed under 'Basic' + const category = core.some(coreComp => coreComp.name === c.name) ? 'Basic' : c.category; + + if (!categoriesMap.has(category)) { + categoriesMap.set(category, []); + } + const catList = categoriesMap.get(category); + if (catList && !catList.some(comp => comp.name === c.name)) { + catList.push(c); } - categoriesMap.get(c.category)?.push(c); }); } diff --git a/src/features/tikz-editor/tikz-editor-modal.ts b/src/features/tikz-editor/tikz-editor-modal.ts index 2d11dc9..efdcff6 100644 --- a/src/features/tikz-editor/tikz-editor-modal.ts +++ b/src/features/tikz-editor/tikz-editor-modal.ts @@ -82,6 +82,7 @@ export class TikzEditorModal extends Modal implements TikzEditorContext { private onSaveCallback?: (newSource: string) => void ) { super(app); + this.pinnedComponents = this.plugin.settings.pinnedTikzComponents || []; this.codec = new TikzCodec( x => this.toCanvasX(x), y => this.toCanvasY(y), @@ -149,6 +150,8 @@ export class TikzEditorModal extends Modal implements TikzEditorContext { } setPinnedComponents(pinned: string[]) { this.pinnedComponents = pinned; + this.plugin.settings.pinnedTikzComponents = pinned; + void this.plugin.saveSettings(); } getActiveTab() { return this.activeTab; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index d2c4b4b..b64d56f 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -77,6 +77,7 @@ export interface PluginSettings { // TikZJax Settings enableTikzjax: boolean; invertColorsInDarkMode: boolean; + pinnedTikzComponents?: string[]; // Version tracking currentVersion: string | null; @@ -155,5 +156,6 @@ export const DEFAULT_SETTINGS: Required = { // TikZJax Settings enableTikzjax: true, - invertColorsInDarkMode: true + invertColorsInDarkMode: true, + pinnedTikzComponents: [] }; diff --git a/src/styles/main.css b/src/styles/main.css index 2d1f12f..30386d6 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -1290,7 +1290,8 @@ div.tikz-editor-modal .modal-close-button { flex-shrink: 0; } -.tikz-editor-ui .tabs-header { +.tikz-editor-ui .tabs-header, +.tikz-editor-ui .library-tabs { display: flex; border-bottom: 1px solid var(--background-modifier-border); background-color: var(--background-secondary);