mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-17 11:03:22 -03:00
feat(recipes): add toolbar toggle and settings preview for masonry layout
This commit is contained in:
@@ -911,6 +911,93 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Recipes layout segmented control with visual previews */
|
||||
.layout-options-control {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.layout-options {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-option {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--lora-surface);
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.layout-option:hover,
|
||||
.layout-option:focus-visible {
|
||||
border-color: var(--lora-accent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.layout-option.active {
|
||||
border-color: var(--lora-accent);
|
||||
background-color: rgba(from var(--lora-accent) r g b / 0.12);
|
||||
color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.layout-option-label {
|
||||
font-size: 0.85em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout-option-preview {
|
||||
width: 72px;
|
||||
height: 44px;
|
||||
padding: 4px;
|
||||
border-radius: var(--border-radius-xs);
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layout-option-preview span {
|
||||
background: currentColor;
|
||||
opacity: 0.4;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.layout-preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.layout-preview-masonry {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.layout-preview-masonry span {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-preview-masonry span:nth-child(2) {
|
||||
height: 60%;
|
||||
}
|
||||
|
||||
.layout-preview-masonry span:nth-child(3) {
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
/* Range Slider Control */
|
||||
.range-control {
|
||||
width: 100%;
|
||||
|
||||
@@ -168,6 +168,34 @@
|
||||
border-color: var(--lora-accent);
|
||||
}
|
||||
|
||||
/* Recipes layout toggle (grid / masonry) — segmented control in the toolbar */
|
||||
.layout-toggle-group {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.layout-toggle-group .layout-toggle-btn {
|
||||
min-width: 36px;
|
||||
width: 36px;
|
||||
padding: 4px 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.layout-toggle-group .layout-toggle-btn:first-child {
|
||||
border-radius: var(--border-radius-xs) 0 0 var(--border-radius-xs);
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.layout-toggle-group .layout-toggle-btn:last-child {
|
||||
border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0;
|
||||
}
|
||||
|
||||
.layout-toggle-group .layout-toggle-btn:hover,
|
||||
.layout-toggle-group .layout-toggle-btn:focus-visible {
|
||||
transform: none;
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
|
||||
/* Keyboard shortcut indicator styling */
|
||||
.shortcut-key {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1017,11 +1017,8 @@ export class SettingsManager {
|
||||
displayDensitySelect.value = state.global.settings.display_density || 'default';
|
||||
}
|
||||
|
||||
// Set recipes layout setting
|
||||
const recipesLayoutSelect = document.getElementById('recipesLayout');
|
||||
if (recipesLayoutSelect) {
|
||||
recipesLayoutSelect.value = state.global.settings.recipes_layout || 'grid';
|
||||
}
|
||||
// Set recipes layout setting (segmented control active state)
|
||||
this.updateRecipesLayoutControls(state.global.settings.recipes_layout || 'grid');
|
||||
|
||||
// Set card info display setting
|
||||
const cardInfoDisplaySelect = document.getElementById('cardInfoDisplay');
|
||||
@@ -2294,19 +2291,18 @@ export class SettingsManager {
|
||||
: element.value;
|
||||
|
||||
try {
|
||||
// Recipes layout has its own shared entry point used by both the
|
||||
// settings modal segmented control and the recipes page toolbar toggle
|
||||
if (settingKey === 'recipes_layout') {
|
||||
return this.saveRecipesLayout(element.value);
|
||||
}
|
||||
|
||||
// Update frontend state with mapped keys
|
||||
await this.saveSetting(settingKey, value);
|
||||
|
||||
// Apply frontend settings immediately
|
||||
this.applyFrontendSettings();
|
||||
|
||||
// Dispatch layout change event; the scroller instance is about to be rebuilt,
|
||||
// so calculateLayout() must NOT run on the old instance here
|
||||
if (settingKey === 'recipes_layout') {
|
||||
window.dispatchEvent(new CustomEvent('lm:recipes-layout-changed'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate layout when display density changes
|
||||
if (settingKey === 'display_density' && state.virtualScroller) {
|
||||
state.virtualScroller.calculateLayout();
|
||||
@@ -2334,6 +2330,47 @@ export class SettingsManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the recipes page layout (grid | masonry) and rebuild the scroller.
|
||||
* Shared entry point for the settings modal segmented control and the
|
||||
* recipes page toolbar toggle; both stay in sync via
|
||||
* updateRecipesLayoutControls().
|
||||
*/
|
||||
async saveRecipesLayout(value) {
|
||||
if (value !== 'grid' && value !== 'masonry') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update frontend state with mapped keys
|
||||
await this.saveSetting('recipes_layout', value);
|
||||
|
||||
// Apply frontend settings immediately
|
||||
this.applyFrontendSettings();
|
||||
|
||||
// Dispatch layout change event; the scroller instance is about to be rebuilt,
|
||||
// so calculateLayout() must NOT run on the old instance here
|
||||
window.dispatchEvent(new CustomEvent('lm:recipes-layout-changed'));
|
||||
|
||||
this.updateRecipesLayoutControls(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the active state of every recipes layout control
|
||||
* (settings modal segmented control and recipes page toolbar toggle).
|
||||
*/
|
||||
updateRecipesLayoutControls(value) {
|
||||
document.querySelectorAll('[data-recipes-layout]').forEach((control) => {
|
||||
const active = control.dataset.recipesLayout === value;
|
||||
control.classList.toggle('active', active);
|
||||
if (control.hasAttribute('aria-pressed')) {
|
||||
control.setAttribute('aria-pressed', String(active));
|
||||
}
|
||||
if (control.hasAttribute('aria-checked')) {
|
||||
control.setAttribute('aria-checked', String(active));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async saveRangeSetting(elementId, displayId, settingKey) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
@@ -282,6 +282,30 @@ class RecipeManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Layout toggle (grid / masonry) — shares the recipes_layout setting with
|
||||
// the settings modal segmented control; active states stay in sync via
|
||||
// settingsManager.updateRecipesLayoutControls() after each save
|
||||
const layoutToggleBtns = document.querySelectorAll('.layout-toggle-btn');
|
||||
if (layoutToggleBtns.length) {
|
||||
const currentLayout = state.global.settings?.recipes_layout || 'grid';
|
||||
layoutToggleBtns.forEach((btn) => {
|
||||
const isActive = btn.dataset.recipesLayout === currentLayout;
|
||||
btn.classList.toggle('active', isActive);
|
||||
btn.setAttribute('aria-pressed', String(isActive));
|
||||
btn.addEventListener('click', async () => {
|
||||
const layout = btn.dataset.recipesLayout;
|
||||
if ((state.global.settings?.recipes_layout || 'grid') === layout) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await window.settingsManager?.saveRecipesLayout(layout);
|
||||
} catch (error) {
|
||||
console.error('Failed to switch recipes layout:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Rebuild the scroller on layout switch; in duplicates mode defer until
|
||||
// exitDuplicateMode re-enables the scroller (direct recreation would dispose
|
||||
// the old instance while initializeVirtualScroll skips duplicates mode)
|
||||
|
||||
Reference in New Issue
Block a user