feat(tikz): add new circuit and flowchart components to AssetsManager; enhance library tab functionality in LeftSidebar

This commit is contained in:
JK 2026-06-14 11:20:28 +02:00
parent 53def164d5
commit 67c6e83bf9
5 changed files with 137 additions and 19 deletions

View file

@ -135,6 +135,34 @@ export class AssetsManager {
category: 'Circuits',
svgMarkup: `<svg viewBox="0 0 50 20" width="40" height="20" style="color: var(--text-normal);"><path d="M0,10 L10,10 Q14,3 18,10 Q22,3 26,10 Q30,3 34,10 Q38,3 40,10 L50,10" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>`,
tikzCommand: '\\draw ({x}, {y}) to[L, l={label}] ({x2}, {y2});'
},
{
name: 'Battery',
type: 'wire',
category: 'Circuits',
svgMarkup: `<svg viewBox="0 0 50 20" width="40" height="20" style="color: var(--text-normal);"><line x1="0" y1="10" x2="20" y2="10" stroke="currentColor" stroke-width="2"/><line x1="20" y1="2" x2="20" y2="18" stroke="currentColor" stroke-width="2"/><line x1="24" y1="6" x2="24" y2="14" stroke="currentColor" stroke-width="3"/><line x1="28" y1="2" x2="28" y2="18" stroke="currentColor" stroke-width="2"/><line x1="32" y1="6" x2="32" y2="14" stroke="currentColor" stroke-width="3"/><line x1="32" y1="10" x2="50" y2="10" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\draw ({x}, {y}) to[battery2, l={label}] ({x2}, {y2});'
},
{
name: 'Diode',
type: 'wire',
category: 'Circuits',
svgMarkup: `<svg viewBox="0 0 50 20" width="40" height="20" style="color: var(--text-normal);"><line x1="0" y1="10" x2="20" y2="10" stroke="currentColor" stroke-width="2"/><polygon points="20,4 20,16 32,10" fill="none" stroke="currentColor" stroke-width="2"/><line x1="32" y1="4" x2="32" y2="16" stroke="currentColor" stroke-width="2"/><line x1="32" y1="10" x2="50" y2="10" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\draw ({x}, {y}) to[D, l={label}] ({x2}, {y2});'
},
{
name: 'AC Source',
type: 'wire',
category: 'Circuits',
svgMarkup: `<svg viewBox="0 0 50 20" width="40" height="20" style="color: var(--text-normal);"><line x1="0" y1="10" x2="15" y2="10" stroke="currentColor" stroke-width="2"/><circle cx="25" cy="10" r="10" fill="none" stroke="currentColor" stroke-width="2"/><path d="M 20 10 Q 22.5 5 25 10 T 30 10" fill="none" stroke="currentColor" stroke-width="2"/><line x1="35" y1="10" x2="50" y2="10" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\draw ({x}, {y}) to[vsourcesin, l={label}] ({x2}, {y2});'
},
{
name: 'Ground',
type: 'component',
category: 'Circuits',
svgMarkup: `<svg viewBox="0 0 40 40" width="30" height="30" style="color: var(--text-normal);"><line x1="20" y1="5" x2="20" y2="25" stroke="currentColor" stroke-width="2"/><line x1="10" y1="25" x2="30" y2="25" stroke="currentColor" stroke-width="2"/><line x1="13" y1="29" x2="27" y2="29" stroke="currentColor" stroke-width="2"/><line x1="17" y1="33" x2="23" y2="33" stroke="currentColor" stroke-width="2"/></svg>`,
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: `<svg viewBox="0 0 50 30" width="40" height="25" style="color: var(--text-normal);"><rect x="5" y="5" width="40" height="20" rx="10" ry="10" fill="none" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\node[draw, rounded corners=10pt] at ({x}, {y}) (start1) {{label}};'
},
{
name: 'Process',
type: 'component',
category: 'Flowcharts',
svgMarkup: `<svg viewBox="0 0 50 30" width="40" height="25" style="color: var(--text-normal);"><rect x="5" y="5" width="40" height="20" fill="none" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\node[draw, minimum width=1.5cm, minimum height=0.8cm] at ({x}, {y}) (proc1) {{label}};'
},
{
name: 'Decision',
type: 'component',
category: 'Flowcharts',
svgMarkup: `<svg viewBox="0 0 50 30" width="40" height="25" style="color: var(--text-normal);"><polygon points="25,3 45,15 25,27 5,15" fill="none" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\node[draw, diamond, aspect=1.5] at ({x}, {y}) (dec1) {{label}};'
},
{
name: 'Input/Output',
type: 'component',
category: 'Flowcharts',
svgMarkup: `<svg viewBox="0 0 50 30" width="40" height="25" style="color: var(--text-normal);"><polygon points="10,5 45,5 40,25 5,25" fill="none" stroke="currentColor" stroke-width="2"/></svg>`,
tikzCommand: '\\node[draw, trapezium, trapezium left angle=70, trapezium right angle=110] at ({x}, {y}) (io1) {{label}};'
}
]
}
];

View file

@ -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<string, ComponentTemplate[]>();
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);
});
}

View file

@ -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;

View file

@ -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<PluginSettings> = {
// TikZJax Settings
enableTikzjax: true,
invertColorsInDarkMode: true
invertColorsInDarkMode: true,
pinnedTikzComponents: []
};

View file

@ -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);