import { App, Modal, Notice, Setting } from 'obsidian'; import PromptfirePlugin from './main'; import { HistoryEntry, formatRelativeTime } from './history'; import { ContextSnapshot, SnapshotManager, generateSnapshotId } from './snapshots'; export class SaveSnapshotModal extends Modal { private plugin: PromptfirePlugin; private entry: HistoryEntry; private snapshotManager: SnapshotManager; private name = ''; private description = ''; constructor(app: App, plugin: PromptfirePlugin, entry: HistoryEntry) { super(app); this.plugin = plugin; this.entry = entry; this.snapshotManager = plugin.snapshotManager; } onOpen() { const { contentEl } = this; contentEl.empty(); contentEl.addClass('promptfire-save-snapshot'); contentEl.createEl('h2', { text: 'Save as Snapshot' }); // Summary const summary = contentEl.createDiv(); summary.style.marginBottom = '15px'; summary.style.padding = '10px'; summary.style.backgroundColor = 'var(--background-secondary)'; summary.style.borderRadius = '4px'; summary.style.fontSize = '12px'; const fileCount = this.entry.metadata.includedFiles.length; const templateName = this.entry.metadata.templateName || 'None'; const activeNote = this.entry.metadata.activeNote || 'None'; summary.createEl('div', { text: `Files: ${fileCount}` }); summary.createEl('div', { text: `Template: ${templateName}` }); summary.createEl('div', { text: `Active note: ${activeNote}` }); // Name field let nameInput: HTMLInputElement; new Setting(contentEl) .setName('Name') .setDesc('A short name for this snapshot (required)') .addText(text => { nameInput = text.inputEl; text.setPlaceholder('e.g., "Auth refactor context"') .onChange(value => { this.name = value; }); }); // Description field new Setting(contentEl) .setName('Description') .setDesc('Optional notes about when to use this snapshot') .addTextArea(text => { text.setPlaceholder('e.g., "Use when working on auth module"') .onChange(value => { this.description = value; }); text.inputEl.rows = 2; text.inputEl.style.width = '100%'; }); // Buttons const buttonContainer = contentEl.createDiv(); buttonContainer.style.display = 'flex'; buttonContainer.style.justifyContent = 'flex-end'; buttonContainer.style.gap = '10px'; buttonContainer.style.marginTop = '20px'; const saveBtn = buttonContainer.createEl('button', { text: 'Save Snapshot', cls: 'mod-cta' }); saveBtn.addEventListener('click', async () => { if (!this.name.trim()) { new Notice('Name is required'); return; } const snapshot: ContextSnapshot = { id: generateSnapshotId(), name: this.name.trim(), description: this.description.trim() || undefined, createdAt: Date.now(), notePaths: this.entry.metadata.includedFiles, activeNotePath: this.entry.metadata.activeNote, templateId: this.entry.metadata.templateId, includeFilenames: this.plugin.settings.includeFilenames, separator: this.plugin.settings.separator, }; const success = await this.snapshotManager.saveSnapshot(snapshot); if (success) { new Notice(`Snapshot saved: ${snapshot.name}`); this.close(); } else { new Notice('Failed to save snapshot'); } }); const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' }); cancelBtn.addEventListener('click', () => this.close()); } onClose() { this.contentEl.empty(); } } export class SnapshotListModal extends Modal { private plugin: PromptfirePlugin; private snapshotManager: SnapshotManager; constructor(app: App, plugin: PromptfirePlugin) { super(app); this.plugin = plugin; this.snapshotManager = plugin.snapshotManager; } async onOpen() { await this.loadAndRender(); } async loadAndRender() { const { contentEl } = this; contentEl.empty(); contentEl.addClass('promptfire-snapshot-list'); contentEl.createEl('h2', { text: 'Context Snapshots' }); const snapshots = await this.snapshotManager.loadSnapshots(); if (snapshots.length === 0) { const notice = contentEl.createEl('p', { text: 'No snapshots yet. Open a history entry and click "Save as Snapshot" to create one.' }); notice.style.fontStyle = 'italic'; notice.style.color = 'var(--text-muted)'; return; } const countInfo = contentEl.createEl('p', { text: `${snapshots.length} snapshot${snapshots.length !== 1 ? 's' : ''}` }); countInfo.style.color = 'var(--text-muted)'; countInfo.style.marginBottom = '10px'; const list = contentEl.createDiv({ cls: 'snapshot-list' }); list.style.maxHeight = '400px'; list.style.overflow = 'auto'; list.style.border = '1px solid var(--background-modifier-border)'; list.style.borderRadius = '4px'; for (const snapshot of snapshots) { const row = list.createDiv({ cls: 'snapshot-row' }); row.style.display = 'flex'; row.style.alignItems = 'center'; row.style.padding = '10px 12px'; row.style.borderBottom = '1px solid var(--background-modifier-border)'; row.style.gap = '10px'; // Info const infoContainer = row.createDiv(); infoContainer.style.flex = '1'; const header = infoContainer.createDiv(); header.style.display = 'flex'; header.style.alignItems = 'center'; header.style.gap = '8px'; const nameEl = header.createEl('span', { text: snapshot.name }); nameEl.style.fontWeight = '500'; const timeEl = header.createEl('span', { text: formatRelativeTime(snapshot.createdAt) }); timeEl.style.color = 'var(--text-muted)'; timeEl.style.fontSize = '12px'; // Stats const stats = infoContainer.createDiv(); stats.style.fontSize = '12px'; stats.style.color = 'var(--text-muted)'; stats.style.marginTop = '4px'; const fileCount = snapshot.notePaths.length; const templateName = snapshot.templateId ? (this.plugin.settings.promptTemplates.find(t => t.id === snapshot.templateId)?.name || 'Unknown template') : 'No template'; stats.setText(`${fileCount} files ยท ${templateName}`); // Description if (snapshot.description) { const desc = infoContainer.createDiv({ text: snapshot.description }); desc.style.fontSize = '12px'; desc.style.fontStyle = 'italic'; desc.style.marginTop = '4px'; } // Actions const actions = row.createDiv(); actions.style.display = 'flex'; actions.style.gap = '4px'; const replayBtn = actions.createEl('button', { text: '\u25B6' }); replayBtn.title = 'Replay snapshot'; replayBtn.style.padding = '4px 8px'; replayBtn.addEventListener('click', async () => { this.close(); await this.plugin.replaySnapshot(snapshot); }); const deleteBtn = actions.createEl('button', { text: '\u2715' }); deleteBtn.title = 'Delete snapshot'; deleteBtn.style.padding = '4px 8px'; deleteBtn.addEventListener('click', async () => { await this.snapshotManager.deleteSnapshot(snapshot.id); new Notice('Snapshot deleted'); await this.loadAndRender(); }); } // Remove bottom border from last row const lastRow = list.lastElementChild as HTMLElement; if (lastRow) { lastRow.style.borderBottom = 'none'; } } onClose() { this.contentEl.empty(); } }