mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 22:52:12 -03:00
feat: enhance alphabet bar with toggle functionality and visual indicators
This commit is contained in:
@@ -1,24 +1,102 @@
|
|||||||
/* Alphabet Bar Component */
|
/* Alphabet Bar Component */
|
||||||
|
.alphabet-bar-container {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-bar-container.collapsed {
|
||||||
|
transform: translateY(-50%) translateX(-90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New visual indicator for when a letter is active and bar is collapsed */
|
||||||
|
.alphabet-bar-container.collapsed .toggle-alphabet-bar.has-active-letter {
|
||||||
|
border-color: var(--lora-accent);
|
||||||
|
background: oklch(var(--lora-accent) / 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-bar-container.collapsed .toggle-alphabet-bar.has-active-letter::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 7px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: var(--lora-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse-active 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-active {
|
||||||
|
0% { transform: scale(0.8); opacity: 0.7; }
|
||||||
|
50% { transform: scale(1.1); opacity: 1; }
|
||||||
|
100% { transform: scale(0.8); opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
.alphabet-bar {
|
.alphabet-bar {
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: var(--border-radius-xs);
|
border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0;
|
||||||
padding: 4px;
|
padding: 8px 4px;
|
||||||
margin: 8px 0;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-bar::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-bar::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-alphabet-bar {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-left: none;
|
||||||
|
border-radius: 0 var(--border-radius-xs) var(--border-radius-xs) 0;
|
||||||
|
padding: 8px 4px;
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
color: var(--text-color);
|
||||||
|
width: 20px;
|
||||||
|
height: 40px;
|
||||||
|
align-self: center;
|
||||||
|
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-alphabet-bar:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-alphabet-bar i {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alphabet-bar-container.collapsed .toggle-alphabet-bar i {
|
||||||
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.letter-chip {
|
.letter-chip {
|
||||||
padding: 4px 6px;
|
padding: 4px 2px;
|
||||||
border-radius: var(--border-radius-xs);
|
border-radius: var(--border-radius-xs);
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-width: 20px;
|
min-width: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
@@ -28,7 +106,7 @@
|
|||||||
.letter-chip:hover {
|
.letter-chip:hover {
|
||||||
background: var(--lora-accent);
|
background: var(--lora-accent);
|
||||||
color: white;
|
color: white;
|
||||||
transform: translateY(-1px);
|
transform: scale(1.1);
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,35 +122,33 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide the count by default, only show in tooltip */
|
||||||
.letter-chip .count {
|
.letter-chip .count {
|
||||||
font-size: 0.75em;
|
display: none;
|
||||||
margin-left: 2px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alphabet-bar-section {
|
|
||||||
margin: 6px 0;
|
|
||||||
padding: 0 2px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.alphabet-bar-title {
|
.alphabet-bar-title {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
margin-right: 8px;
|
margin-bottom: 6px;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
transform: rotate(180deg);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.alphabet-bar {
|
.alphabet-bar-container {
|
||||||
padding: 3px;
|
transform: translateY(-50%) translateX(-90%);
|
||||||
gap: 3px;
|
}
|
||||||
|
|
||||||
|
.alphabet-bar-container.active {
|
||||||
|
transform: translateY(-50%) translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.letter-chip {
|
.letter-chip {
|
||||||
padding: 3px 5px;
|
padding: 3px 1px;
|
||||||
min-width: 16px;
|
min-width: 20px;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +156,7 @@
|
|||||||
/* Keyframe animations for the active letter */
|
/* Keyframe animations for the active letter */
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0% { transform: scale(1); }
|
0% { transform: scale(1); }
|
||||||
50% { transform: scale(1.05); }
|
50% { transform: scale(1.1); }
|
||||||
100% { transform: scale(1); }
|
100% { transform: scale(1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export class AlphabetBar {
|
|||||||
|
|
||||||
// Restore the active letter filter from storage if available
|
// Restore the active letter filter from storage if available
|
||||||
this.restoreActiveLetterFilter();
|
this.restoreActiveLetterFilter();
|
||||||
|
|
||||||
|
// Restore collapse state from storage
|
||||||
|
this.restoreCollapseState();
|
||||||
|
|
||||||
|
// Update the toggle button indicator if there's an active letter filter
|
||||||
|
this.updateToggleIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,20 +73,21 @@ export class AlphabetBar {
|
|||||||
|
|
||||||
letterChips.forEach(chip => {
|
letterChips.forEach(chip => {
|
||||||
const letter = chip.dataset.letter;
|
const letter = chip.dataset.letter;
|
||||||
const countSpan = chip.querySelector('.count');
|
|
||||||
const count = this.letterCounts[letter] || 0;
|
const count = this.letterCounts[letter] || 0;
|
||||||
|
|
||||||
// Update the count display
|
// Update the title attribute for tooltip display
|
||||||
|
if (count > 0) {
|
||||||
|
chip.title = `${letter}: ${count} LoRAs`;
|
||||||
|
chip.classList.remove('disabled');
|
||||||
|
} else {
|
||||||
|
chip.title = `${letter}: No LoRAs`;
|
||||||
|
chip.classList.add('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the count span for backward compatibility
|
||||||
|
const countSpan = chip.querySelector('.count');
|
||||||
if (countSpan) {
|
if (countSpan) {
|
||||||
if (count > 0) {
|
countSpan.textContent = ` (${count})`;
|
||||||
countSpan.textContent = ` (${count})`;
|
|
||||||
chip.title = `${letter}: ${count} LoRAs`;
|
|
||||||
chip.classList.remove('disabled');
|
|
||||||
} else {
|
|
||||||
countSpan.textContent = '';
|
|
||||||
chip.title = `${letter}: No LoRAs`;
|
|
||||||
chip.classList.add('disabled');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,6 +97,8 @@ export class AlphabetBar {
|
|||||||
*/
|
*/
|
||||||
initEventListeners() {
|
initEventListeners() {
|
||||||
const alphabetBar = document.querySelector('.alphabet-bar');
|
const alphabetBar = document.querySelector('.alphabet-bar');
|
||||||
|
const toggleButton = document.querySelector('.toggle-alphabet-bar');
|
||||||
|
const alphabetBarContainer = document.querySelector('.alphabet-bar-container');
|
||||||
|
|
||||||
if (alphabetBar) {
|
if (alphabetBar) {
|
||||||
// Use event delegation for letter chips
|
// Use event delegation for letter chips
|
||||||
@@ -101,6 +110,25 @@ export class AlphabetBar {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add toggle button listener
|
||||||
|
if (toggleButton && alphabetBarContainer) {
|
||||||
|
toggleButton.addEventListener('click', () => {
|
||||||
|
alphabetBarContainer.classList.toggle('collapsed');
|
||||||
|
|
||||||
|
// If expanding and there's an active letter, scroll it into view
|
||||||
|
if (!alphabetBarContainer.classList.contains('collapsed')) {
|
||||||
|
this.scrollActiveLetterIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save collapse state to storage
|
||||||
|
setStorageItem(`${this.pageType}_alphabetBarCollapsed`,
|
||||||
|
alphabetBarContainer.classList.contains('collapsed'));
|
||||||
|
|
||||||
|
// Update toggle indicator
|
||||||
|
this.updateToggleIndicator();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add keyboard shortcut listeners
|
// Add keyboard shortcut listeners
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
// Alt + letter shortcuts
|
// Alt + letter shortcuts
|
||||||
@@ -147,6 +175,26 @@ export class AlphabetBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the collapse state from storage
|
||||||
|
*/
|
||||||
|
restoreCollapseState() {
|
||||||
|
const alphabetBarContainer = document.querySelector('.alphabet-bar-container');
|
||||||
|
|
||||||
|
if (alphabetBarContainer) {
|
||||||
|
const isCollapsed = getStorageItem(`${this.pageType}_alphabetBarCollapsed`);
|
||||||
|
|
||||||
|
// If there's a stored preference, apply it
|
||||||
|
if (isCollapsed !== null) {
|
||||||
|
if (isCollapsed) {
|
||||||
|
alphabetBarContainer.classList.add('collapsed');
|
||||||
|
} else {
|
||||||
|
alphabetBarContainer.classList.remove('collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle letter chip click
|
* Handle letter chip click
|
||||||
* @param {HTMLElement} letterChip - The letter chip that was clicked
|
* @param {HTMLElement} letterChip - The letter chip that was clicked
|
||||||
@@ -175,6 +223,9 @@ export class AlphabetBar {
|
|||||||
setStorageItem(`${this.pageType}_activeLetterFilter`, null);
|
setStorageItem(`${this.pageType}_activeLetterFilter`, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update visual indicator on toggle button
|
||||||
|
this.updateToggleIndicator();
|
||||||
|
|
||||||
// Trigger a reload with the new filter
|
// Trigger a reload with the new filter
|
||||||
resetAndReload(true);
|
resetAndReload(true);
|
||||||
}
|
}
|
||||||
@@ -191,6 +242,9 @@ export class AlphabetBar {
|
|||||||
if (letterChip && !letterChip.classList.contains('disabled')) {
|
if (letterChip && !letterChip.classList.contains('disabled')) {
|
||||||
letterChip.classList.add('active');
|
letterChip.classList.add('active');
|
||||||
this.pageState.activeLetterFilter = activeLetterFilter;
|
this.pageState.activeLetterFilter = activeLetterFilter;
|
||||||
|
|
||||||
|
// Scroll the active letter into view if the alphabet bar is expanded
|
||||||
|
this.scrollActiveLetterIntoView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,6 +263,9 @@ export class AlphabetBar {
|
|||||||
|
|
||||||
// Remove from storage
|
// Remove from storage
|
||||||
setStorageItem(`${this.pageType}_activeLetterFilter`, null);
|
setStorageItem(`${this.pageType}_activeLetterFilter`, null);
|
||||||
|
|
||||||
|
// Update the toggle button indicator
|
||||||
|
this.updateToggleIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,4 +276,44 @@ export class AlphabetBar {
|
|||||||
this.letterCounts = { ...newCounts };
|
this.letterCounts = { ...newCounts };
|
||||||
this.updateLetterCountsDisplay();
|
this.updateLetterCountsDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the toggle button visual indicator based on active filter
|
||||||
|
*/
|
||||||
|
updateToggleIndicator() {
|
||||||
|
const toggleButton = document.querySelector('.toggle-alphabet-bar');
|
||||||
|
const hasActiveFilter = this.pageState.activeLetterFilter !== null;
|
||||||
|
|
||||||
|
if (toggleButton) {
|
||||||
|
if (hasActiveFilter) {
|
||||||
|
toggleButton.classList.add('has-active-letter');
|
||||||
|
} else {
|
||||||
|
toggleButton.classList.remove('has-active-letter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll the active letter into view if the alphabet bar is expanded
|
||||||
|
*/
|
||||||
|
scrollActiveLetterIntoView() {
|
||||||
|
if (!this.pageState.activeLetterFilter) return;
|
||||||
|
|
||||||
|
|
||||||
|
const alphabetBarContainer = document.querySelector('.alphabet-bar-container');
|
||||||
|
if (alphabetBarContainer) {
|
||||||
|
const activeLetterChip = document.querySelector(`.letter-chip.active`);
|
||||||
|
|
||||||
|
if (activeLetterChip) {
|
||||||
|
// Use a small timeout to ensure the alphabet bar is fully expanded
|
||||||
|
setTimeout(() => {
|
||||||
|
activeLetterChip.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
inline: 'center'
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -157,16 +157,4 @@ export class LorasControls extends PageControls {
|
|||||||
// Expose the alphabet bar to the global scope for debugging
|
// Expose the alphabet bar to the global scope for debugging
|
||||||
window.alphabetBar = this.alphabetBar;
|
window.alphabetBar = this.alphabetBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Override resetAndReload to update letter counts
|
|
||||||
*/
|
|
||||||
async resetAndReload(updateFolders = false) {
|
|
||||||
await super.resetAndReload(updateFolders);
|
|
||||||
|
|
||||||
// Update letter counts after reload if alphabet bar exists
|
|
||||||
if (this.alphabetBar) {
|
|
||||||
this.alphabetBar.fetchLetterCounts();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="alphabet-bar-section">
|
<div class="alphabet-bar-container collapsed">
|
||||||
<div class="alphabet-bar">
|
<div class="alphabet-bar">
|
||||||
<span class="alphabet-bar-title">Filter by:</span>
|
<!-- <span class="alphabet-bar-title">Filter by</span> -->
|
||||||
<div class="letter-chip" data-letter="#" title="Numbers">
|
<div class="letter-chip" data-letter="#" title="Numbers">
|
||||||
#<span class="count"></span>
|
#<span class="count"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,4 +16,7 @@
|
|||||||
漢<span class="count"></span>
|
漢<span class="count"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="toggle-alphabet-bar" title="Toggle alphabet filter">
|
||||||
|
<i class="fas fa-chevron-right"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user