From 30b408533f4286527667a9d16cb307fc5bc9c687 Mon Sep 17 00:00:00 2001 From: Alexopus Date: Fri, 18 Jul 2025 16:50:31 +0200 Subject: [PATCH] Use state and store --- .gitignore | 3 +- js/extension.js | 237 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 172 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index ed8ebf5..496bff0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__ \ No newline at end of file +__pycache__ +notes/ \ No newline at end of file diff --git a/js/extension.js b/js/extension.js index 3153d77..58822e9 100644 --- a/js/extension.js +++ b/js/extension.js @@ -6,19 +6,143 @@ link.rel = 'stylesheet'; link.href = '/extensions/comfyui-notes-sidebar/notes-sidebar.css'; document.head.appendChild(link); +class NotesStore extends EventTarget { + constructor() { + super(); + this.state = { + notes: [], + activeNote: null, + unsavedChanges: new Map(), // noteName -> boolean + noteContent: new Map(), // noteName -> string (current content) + savedContent: new Map() // noteName -> string (last saved content) + }; + } + + getState() { + return { ...this.state }; + } + + setState(updates) { + const prevState = { ...this.state }; + this.state = { ...this.state, ...updates }; + + // Emit state change event + this.dispatchEvent(new CustomEvent('stateChanged', { + detail: { + prevState, + newState: this.getState(), + updates + } + })); + } + + // Convenience methods for common state operations + setNotes(notes) { + this.setState({ notes }); + } + + setActiveNote(activeNote) { + this.setState({ activeNote }); + } + + setNoteContent(noteName, content) { + const noteContent = new Map(this.state.noteContent); + noteContent.set(noteName, content); + this.setState({ noteContent }); + } + + setSavedContent(noteName, content) { + const savedContent = new Map(this.state.savedContent); + savedContent.set(noteName, content); + this.setState({ savedContent }); + } + + setUnsavedChanges(noteName, hasChanges) { + const unsavedChanges = new Map(this.state.unsavedChanges); + unsavedChanges.set(noteName, hasChanges); + this.setState({ unsavedChanges }); + } + + deleteNoteFromState(noteName) { + const unsavedChanges = new Map(this.state.unsavedChanges); + const noteContent = new Map(this.state.noteContent); + const savedContent = new Map(this.state.savedContent); + + unsavedChanges.delete(noteName); + noteContent.delete(noteName); + savedContent.delete(noteName); + + this.setState({ unsavedChanges, noteContent, savedContent }); + } + + renameNoteInState(oldName, newName) { + const unsavedChanges = new Map(this.state.unsavedChanges); + const noteContent = new Map(this.state.noteContent); + const savedContent = new Map(this.state.savedContent); + + // Transfer state from old name to new name + const unsavedState = unsavedChanges.get(oldName) || false; + const content = noteContent.get(oldName) || ''; + const saved = savedContent.get(oldName) || ''; + + // Remove old + unsavedChanges.delete(oldName); + noteContent.delete(oldName); + savedContent.delete(oldName); + + // Add new + unsavedChanges.set(newName, unsavedState); + noteContent.set(newName, content); + savedContent.set(newName, saved); + + this.setState({ unsavedChanges, noteContent, savedContent }); + } +} + class NotesSidebar { constructor() { - this.notes = []; - this.activeNote = null; + this.store = new NotesStore(); this.tabsContainer = null; this.contentArea = null; this.textArea = null; this.isInitialized = false; this.sidebarElement = null; - // Track unsaved changes and content for each note - this.unsavedChanges = new Map(); // noteName -> boolean - this.noteContent = new Map(); // noteName -> string (current content) - this.savedContent = new Map(); // noteName -> string (last saved content) + + // Listen to state changes + this.store.addEventListener('stateChanged', (event) => { + this.onStateChanged(event.detail); + }); + } + + onStateChanged(changeDetails) { + const { prevState, newState, updates } = changeDetails; + + // Update tabs if notes changed, active note changed, or unsaved changes changed + if (updates.notes || updates.activeNote || updates.unsavedChanges) { + this.renderTabs(); + } + + // Update textarea if active note changed or note content changed + if (updates.activeNote || updates.noteContent) { + this.updateTextArea(); + } + } + + get notes() { return this.store.state.notes; } + get activeNote() { return this.store.state.activeNote; } + get unsavedChanges() { return this.store.state.unsavedChanges; } + get noteContent() { return this.store.state.noteContent; } + get savedContent() { return this.store.state.savedContent; } + + updateTextArea() { + if (!this.textArea || !this.activeNote) return; + + // Check if we have unsaved content for this note + if (this.noteContent.has(this.activeNote)) { + this.textArea.value = this.noteContent.get(this.activeNote); + } else { + this.textArea.value = ''; + } } async init() { @@ -35,10 +159,9 @@ class NotesSidebar { if (this.isInitialized && this.sidebarElement) { el.innerHTML = ''; el.appendChild(this.sidebarElement); - // Refresh the active note content when reopening - if (this.activeNote) { - this.loadNoteContent(this.activeNote); - } + // Trigger reactive updates when reopening + this.renderTabs(); + this.updateTextArea(); return; } @@ -127,15 +250,15 @@ class NotesSidebar { try { const response = await fetch('/notes_sidebar/list'); const data = await response.json(); - this.notes = data.notes || []; + const notes = data.notes || []; - if (this.notes.length > 0 && !this.activeNote) { - this.activeNote = this.notes[0]; + this.store.setNotes(notes); + + if (notes.length > 0 && !this.activeNote) { + this.store.setActiveNote(notes[0]); } - this.renderTabs(); - - // Load content for active note after rendering tabs + // Load content for active note after setting state if (this.activeNote) { await this.loadNoteContent(this.activeNote); } @@ -145,6 +268,8 @@ class NotesSidebar { } renderTabs() { + if (!this.tabsContainer) return; + const existingTabs = this.tabsContainer.querySelectorAll('.note-tab'); existingTabs.forEach(tab => tab.remove()); @@ -162,33 +287,28 @@ class NotesSidebar { async selectNote(noteName) { // Save current content before switching if (this.activeNote && this.textArea) { - this.noteContent.set(this.activeNote, this.textArea.value); + this.store.setNoteContent(this.activeNote, this.textArea.value); } - this.activeNote = noteName; + this.store.setActiveNote(noteName); await this.loadNoteContent(noteName); - this.renderTabs(); } async loadNoteContent(noteName) { try { // Check if we have unsaved content for this note if (this.noteContent.has(noteName)) { - // Use the unsaved content - if (this.textArea) { - this.textArea.value = this.noteContent.get(noteName); - } + // Content is already in state, updateTextArea will handle it + return; } else { // Load from server const response = await fetch(`/notes_sidebar/note/${encodeURIComponent(noteName)}`); const data = await response.json(); const content = data.content || ''; - if (this.textArea) { - this.textArea.value = content; - } + // Store both current and saved content - this.noteContent.set(noteName, content); - this.savedContent.set(noteName, content); + this.store.setNoteContent(noteName, content); + this.store.setSavedContent(noteName, content); } } catch (error) { console.error('Error loading note content:', error); @@ -209,10 +329,9 @@ class NotesSidebar { if (response.ok) { console.log('Note saved successfully'); // Mark as saved - this.savedContent.set(this.activeNote, content); - this.noteContent.set(this.activeNote, content); - this.unsavedChanges.set(this.activeNote, false); - this.renderTabs(); // Update tab display + this.store.setSavedContent(this.activeNote, content); + this.store.setNoteContent(this.activeNote, content); + this.store.setUnsavedChanges(this.activeNote, false); } else { console.error('Error saving note'); } @@ -234,17 +353,15 @@ class NotesSidebar { if (response.ok) { // Clean up tracking for deleted note const deletedNote = this.activeNote; - this.unsavedChanges.delete(deletedNote); - this.noteContent.delete(deletedNote); - this.savedContent.delete(deletedNote); + this.store.deleteNoteFromState(deletedNote); await this.refreshNotes(); // Select the first available note or clear if no notes left - this.activeNote = this.notes.length > 0 ? this.notes[0] : null; - if (this.activeNote) { - await this.loadNoteContent(this.activeNote); - } else { - this.textArea.value = ''; + const newActiveNote = this.notes.length > 0 ? this.notes[0] : null; + this.store.setActiveNote(newActiveNote); + + if (newActiveNote) { + await this.loadNoteContent(newActiveNote); } } } catch (error) { @@ -279,25 +396,17 @@ class NotesSidebar { // Delete old note await fetch(`/notes_sidebar/note/${encodeURIComponent(this.activeNote)}`, { method: 'DELETE' }); - // Update tracking maps + // Update tracking maps through store const oldName = this.activeNote; - const unsavedState = this.unsavedChanges.get(oldName) || false; - const content = this.noteContent.get(oldName) || currentContent; + this.store.renameNoteInState(oldName, newName); - // Remove old tracking - this.unsavedChanges.delete(oldName); - this.noteContent.delete(oldName); - this.savedContent.delete(oldName); - - // Add new tracking - this.noteContent.set(newName, content); - this.savedContent.set(newName, currentContent); // The content we just saved - this.unsavedChanges.set(newName, false); // Just saved, so no unsaved changes + // Set saved content to what we just saved + this.store.setSavedContent(newName, currentContent); + this.store.setUnsavedChanges(newName, false); // Update active note and refresh - this.activeNote = newName; + this.store.setActiveNote(newName); await this.refreshNotes(); - this.renderTabs(); } } catch (error) { console.error('Error renaming note:', error); @@ -317,15 +426,12 @@ class NotesSidebar { if (response.ok) { await this.refreshNotes(); - this.activeNote = noteName; - this.textArea.value = ''; + this.store.setActiveNote(noteName); // Initialize tracking for new note - this.noteContent.set(noteName, ''); - this.savedContent.set(noteName, ''); - this.unsavedChanges.set(noteName, false); - - this.renderTabs(); + this.store.setNoteContent(noteName, ''); + this.store.setSavedContent(noteName, ''); + this.store.setUnsavedChanges(noteName, false); } } catch (error) { console.error('Error creating note:', error); @@ -335,7 +441,7 @@ class NotesSidebar { async refreshNotes() { // Preserve current content before refreshing if (this.activeNote && this.textArea) { - this.noteContent.set(this.activeNote, this.textArea.value); + this.store.setNoteContent(this.activeNote, this.textArea.value); } await this.loadNotes(); @@ -348,14 +454,11 @@ class NotesSidebar { const savedContent = this.savedContent.get(this.activeNote) || ''; // Update current content - this.noteContent.set(this.activeNote, currentContent); + this.store.setNoteContent(this.activeNote, currentContent); // Check if content has changed from saved version const hasUnsavedChanges = currentContent !== savedContent; - this.unsavedChanges.set(this.activeNote, hasUnsavedChanges); - - // Update tab display - this.renderTabs(); + this.store.setUnsavedChanges(this.activeNote, hasUnsavedChanges); } }