Append submenu
This commit is contained in:
@@ -35,11 +35,17 @@ This is a ComfyUI extension that adds a server-side notes sidebar for managing m
|
||||
## API Endpoints
|
||||
|
||||
The extension exposes these REST endpoints:
|
||||
|
||||
### Notes Management
|
||||
- `GET /notes_sidebar/list` - List all notes
|
||||
- `GET /notes_sidebar/note/{note_name}` - Get specific note content
|
||||
- `POST /notes_sidebar/note/{note_name}` - Save/create note
|
||||
- `DELETE /notes_sidebar/note/{note_name}` - Delete note
|
||||
|
||||
### LoRA Integration
|
||||
- `GET /notes_sidebar/loras` - List all available LoRA files
|
||||
- `GET /notes_sidebar/loras_with_metadata` - List LoRAs with associated metadata from `.txt` files
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Extension Structure
|
||||
@@ -56,10 +62,11 @@ The extension exposes these REST endpoints:
|
||||
- Active note tracking with tab highlighting
|
||||
- Content persistence across sidebar open/close cycles
|
||||
- Real-time tab rendering on note operations
|
||||
- Button layout: Save/Rename on left, Refresh/Delete on right
|
||||
- Button layout: Save/Rename/LoRA submenu on left, Refresh/Delete on right
|
||||
- **Unsaved Changes Tracking**: Visual indicators (*) for modified notes
|
||||
- **Content Preservation**: Tab switching preserves unsaved content
|
||||
- **Change Detection**: Automatic tracking of modifications vs saved state
|
||||
- **LoRA Integration**: Submenu with three formatting options for appending LoRA lists
|
||||
|
||||
### Unsaved Changes Behavior
|
||||
- Notes with modifications show asterisk (*) in tab name
|
||||
@@ -74,6 +81,7 @@ The extension exposes these REST endpoints:
|
||||
- **API Design**: RESTful endpoints with JSON responses and consistent error handling
|
||||
- **UI Updates**: Event-driven architecture prevents manual DOM manipulation
|
||||
- **Extension Integration**: Follows ComfyUI's extension patterns (NODE_CLASS_MAPPINGS, registerExtension)
|
||||
- **ComfyUI Integration**: Leverages `folder_paths` module for LoRA discovery and metadata loading
|
||||
|
||||
## Development Commands
|
||||
|
||||
@@ -88,6 +96,8 @@ No automated tests defined. Manual testing workflow:
|
||||
2. Verify sidebar tab appears with "Notes" label and book icon
|
||||
3. Test CRUD operations: create, edit, save, rename, delete notes
|
||||
4. Verify tab switching and content persistence
|
||||
5. Test LoRA integration: verify submenu appears on click with three format options
|
||||
6. Verify unsaved changes tracking with asterisk (*) indicators
|
||||
|
||||
### Development Workflow
|
||||
1. Modify Python code in `__init__.py` for backend changes
|
||||
|
||||
+69
-20
@@ -173,7 +173,7 @@ class NotesSidebar {
|
||||
}
|
||||
|
||||
updateButtonState() {
|
||||
if (!this.saveBtn || !this.renameBtn || !this.deleteBtn || !this.appendLorasBtn || !this.appendLorasTagsBtn || !this.appendLorasMetaBtn) return;
|
||||
if (!this.saveBtn || !this.renameBtn || !this.deleteBtn || !this.appendLorasBtn) return;
|
||||
|
||||
const hasActiveNote = !!this.activeNote;
|
||||
const hasUnsavedChanges = hasActiveNote && this.unsavedChanges.get(this.activeNote);
|
||||
@@ -185,8 +185,6 @@ class NotesSidebar {
|
||||
this.renameBtn.disabled = !hasActiveNote;
|
||||
this.deleteBtn.disabled = !hasActiveNote;
|
||||
this.appendLorasBtn.disabled = !hasActiveNote;
|
||||
this.appendLorasTagsBtn.disabled = !hasActiveNote;
|
||||
this.appendLorasMetaBtn.disabled = !hasActiveNote;
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -290,32 +288,48 @@ class NotesSidebar {
|
||||
this.deleteBtn.onclick = () => this.deleteActiveNote();
|
||||
this.deleteBtn.disabled = true;
|
||||
|
||||
// LoRA submenu container
|
||||
this.loraMenuContainer = document.createElement('div');
|
||||
this.loraMenuContainer.className = 'notes-submenu-container';
|
||||
|
||||
this.appendLorasBtn = document.createElement('button');
|
||||
this.appendLorasBtn.innerHTML = '<i class="pi pi-plus-circle"></i>';
|
||||
this.appendLorasBtn.className = 'notes-btn append-loras';
|
||||
this.appendLorasBtn.title = 'Append LoRAs List';
|
||||
this.appendLorasBtn.onclick = () => this.appendLorasList();
|
||||
this.appendLorasBtn.title = 'Append';
|
||||
this.appendLorasBtn.onclick = () => this.toggleLoraMenu();
|
||||
this.appendLorasBtn.disabled = true;
|
||||
|
||||
this.appendLorasTagsBtn = document.createElement('button');
|
||||
this.appendLorasTagsBtn.innerHTML = '<i class="pi pi-tags"></i>';
|
||||
this.appendLorasTagsBtn.className = 'notes-btn append-loras-tags';
|
||||
this.appendLorasTagsBtn.title = 'Append LoRAs as Tags';
|
||||
this.appendLorasTagsBtn.onclick = () => this.appendLorasTags();
|
||||
this.appendLorasTagsBtn.disabled = true;
|
||||
// Create submenu
|
||||
this.loraSubmenu = document.createElement('div');
|
||||
this.loraSubmenu.className = 'notes-submenu';
|
||||
this.loraSubmenu.style.display = 'none';
|
||||
|
||||
this.appendLorasMetaBtn = document.createElement('button');
|
||||
this.appendLorasMetaBtn.innerHTML = '<i class="pi pi-info-circle"></i>';
|
||||
this.appendLorasMetaBtn.className = 'notes-btn append-loras-meta';
|
||||
this.appendLorasMetaBtn.title = 'Append LoRAs with Metadata';
|
||||
this.appendLorasMetaBtn.onclick = () => this.appendLorasWithMetadata();
|
||||
this.appendLorasMetaBtn.disabled = true;
|
||||
// Create submenu items
|
||||
const rawOption = document.createElement('div');
|
||||
rawOption.className = 'notes-submenu-item';
|
||||
rawOption.textContent = 'Append raw LoRA filenames list';
|
||||
rawOption.onclick = () => this.selectLoraOption('raw');
|
||||
|
||||
const tagOption = document.createElement('div');
|
||||
tagOption.className = 'notes-submenu-item';
|
||||
tagOption.textContent = "Append LoRAs in '<lora:name>' format";
|
||||
tagOption.onclick = () => this.selectLoraOption('tag');
|
||||
|
||||
const metaOption = document.createElement('div');
|
||||
metaOption.className = 'notes-submenu-item';
|
||||
metaOption.textContent = "Append LoRAs in '<lora:name>, metadata' format";
|
||||
metaOption.onclick = () => this.selectLoraOption('meta');
|
||||
|
||||
this.loraSubmenu.appendChild(rawOption);
|
||||
this.loraSubmenu.appendChild(tagOption);
|
||||
this.loraSubmenu.appendChild(metaOption);
|
||||
|
||||
this.loraMenuContainer.appendChild(this.appendLorasBtn);
|
||||
this.loraMenuContainer.appendChild(this.loraSubmenu);
|
||||
|
||||
leftButtonContainer.appendChild(this.saveBtn);
|
||||
leftButtonContainer.appendChild(this.renameBtn);
|
||||
leftButtonContainer.appendChild(this.appendLorasBtn);
|
||||
leftButtonContainer.appendChild(this.appendLorasTagsBtn);
|
||||
leftButtonContainer.appendChild(this.appendLorasMetaBtn);
|
||||
leftButtonContainer.appendChild(this.loraMenuContainer);
|
||||
rightButtonContainer.appendChild(this.deleteBtn);
|
||||
|
||||
buttonContainer.appendChild(leftButtonContainer);
|
||||
@@ -579,6 +593,41 @@ class NotesSidebar {
|
||||
this.store.setUnsavedChanges(this.activeNote, hasUnsavedChanges);
|
||||
}
|
||||
|
||||
toggleLoraMenu() {
|
||||
if (this.loraSubmenu.style.display === 'none') {
|
||||
this.loraSubmenu.style.display = 'block';
|
||||
// Add click listener to close menu when clicking outside
|
||||
document.addEventListener('click', this.closeLoraMenuHandler);
|
||||
} else {
|
||||
this.loraSubmenu.style.display = 'none';
|
||||
document.removeEventListener('click', this.closeLoraMenuHandler);
|
||||
}
|
||||
}
|
||||
|
||||
closeLoraMenuHandler = (event) => {
|
||||
if (!this.loraMenuContainer.contains(event.target)) {
|
||||
this.loraSubmenu.style.display = 'none';
|
||||
document.removeEventListener('click', this.closeLoraMenuHandler);
|
||||
}
|
||||
}
|
||||
|
||||
selectLoraOption(type) {
|
||||
this.loraSubmenu.style.display = 'none';
|
||||
document.removeEventListener('click', this.closeLoraMenuHandler);
|
||||
|
||||
switch(type) {
|
||||
case 'raw':
|
||||
this.appendLorasList();
|
||||
break;
|
||||
case 'tag':
|
||||
this.appendLorasTags();
|
||||
break;
|
||||
case 'meta':
|
||||
this.appendLorasWithMetadata();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async appendLorasList() {
|
||||
try {
|
||||
const response = await fetch('/notes_sidebar/loras');
|
||||
|
||||
+32
-4
@@ -152,10 +152,38 @@
|
||||
background: #9C27B0;
|
||||
}
|
||||
|
||||
.notes-btn.append-loras-tags {
|
||||
background: #673AB7;
|
||||
/* Submenu styles */
|
||||
.notes-submenu-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.notes-btn.append-loras-meta {
|
||||
background: #3F51B5;
|
||||
.notes-submenu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background: #2a2a2a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
min-width: 200px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.notes-submenu-item {
|
||||
padding: 8px 12px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #444;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notes-submenu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.notes-submenu-item:hover {
|
||||
background: #444;
|
||||
}
|
||||
Reference in New Issue
Block a user