From 5eb79fbf080df63b978970730c68778bc2ee9d08 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Fri, 13 Feb 2026 13:22:11 +0100 Subject: [PATCH] feat: add sidebar view with form list ItemView that displays all forms with mode badges (C/U), click to open, settings gear button, and empty state. Uses SidebarPluginRef interface to avoid circular dependency with main.ts. Co-Authored-By: Claude Opus 4.6 --- src/ui/form-sidebar.ts | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/ui/form-sidebar.ts diff --git a/src/ui/form-sidebar.ts b/src/ui/form-sidebar.ts new file mode 100644 index 0000000..44fccd6 --- /dev/null +++ b/src/ui/form-sidebar.ts @@ -0,0 +1,94 @@ +import { ItemView, WorkspaceLeaf, setIcon } from 'obsidian'; +import { FormStore } from '../core/form-store'; + +export const VIEW_TYPE_FORMFIRE_SIDEBAR = 'formfire-sidebar'; + +/** + * Interface for the subset of FormfirePlugin that the sidebar needs. + * Avoids circular dependency with main.ts. + */ +export interface SidebarPluginRef { + store: FormStore; + openForm: (id: string) => void; +} + +export class FormfireSidebarView extends ItemView { + private pluginRef: SidebarPluginRef; + + constructor(leaf: WorkspaceLeaf, pluginRef: SidebarPluginRef) { + super(leaf); + this.pluginRef = pluginRef; + } + + getViewType(): string { + return VIEW_TYPE_FORMFIRE_SIDEBAR; + } + + getDisplayText(): string { + return 'Formfire'; + } + + getIcon(): string { + return 'file-input'; + } + + async onOpen(): Promise { + this.render(); + } + + async onClose(): Promise { + this.contentEl.empty(); + } + + public render(): void { + const { contentEl } = this; + contentEl.empty(); + + const container = contentEl.createDiv({ cls: 'ff-sidebar' }); + + // Header + const header = container.createDiv({ cls: 'ff-sidebar-header' }); + header.createSpan({ text: 'Forms', cls: 'ff-sidebar-title' }); + + const settingsBtn = header.createDiv({ cls: 'ff-sidebar-settings' }); + setIcon(settingsBtn, 'settings'); + settingsBtn.setAttribute('aria-label', 'Formfire settings'); + settingsBtn.addEventListener('click', () => { + // Open Obsidian settings and navigate to the Formfire tab + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const appAny = this.app as any; + appAny.setting.open(); + appAny.setting.openTabById('formfire'); + }); + + // Form list + const forms = this.pluginRef.store.getAll(); + + if (forms.length === 0) { + container.createDiv({ + cls: 'ff-sidebar-empty', + text: 'No forms yet. Open settings to create one.', + }); + return; + } + + const listEl = container.createDiv({ cls: 'ff-sidebar-list' }); + + for (const form of forms) { + const item = listEl.createDiv({ cls: 'ff-sidebar-item' }); + + const badgeCls = + form.mode === 'create' ? 'ff-badge-create' : 'ff-badge-update'; + item.createSpan({ + cls: `ff-sidebar-badge ${badgeCls}`, + text: form.mode === 'create' ? 'C' : 'U', + }); + + item.createSpan({ cls: 'ff-sidebar-name', text: form.name }); + + item.addEventListener('click', () => { + this.pluginRef.openForm(form.id); + }); + } + } +}