mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: add default path toggle and update download modal for improved path selection
This commit is contained in:
@@ -395,9 +395,16 @@
|
||||
border: 1px dashed var(--border-color);
|
||||
}
|
||||
|
||||
.path-preview label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
.path-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.path-preview-header label {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: 0.9em;
|
||||
opacity: 0.8;
|
||||
@@ -416,6 +423,70 @@
|
||||
border-radius: var(--border-radius-xs);
|
||||
}
|
||||
|
||||
/* Inline Toggle Styles */
|
||||
.inline-toggle-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inline-toggle-label {
|
||||
font-size: 0.85em;
|
||||
color: var(--text-color);
|
||||
opacity: 0.9;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.inline-toggle-container .toggle-switch {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.inline-toggle-container .toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.inline-toggle-container .toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.inline-toggle-container .toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.inline-toggle-container .toggle-switch input:checked + .toggle-slider {
|
||||
background-color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.inline-toggle-container .toggle-switch input:checked + .toggle-slider:before {
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
/* Dark theme adjustments */
|
||||
[data-theme="dark"] .version-item {
|
||||
background: var(--lora-surface);
|
||||
@@ -426,8 +497,18 @@
|
||||
border-color: var(--lora-border);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .toggle-slider:before {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* Enhance the local badge to make it more noticeable */
|
||||
.version-item.exists-locally {
|
||||
background: oklch(var(--lora-accent) / 0.05);
|
||||
border-left: 4px solid var(--lora-accent);
|
||||
}
|
||||
|
||||
.manual-path-selection.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -613,7 +613,7 @@ export class BaseModelApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async downloadModel(modelId, versionId, modelRoot, relativePath, downloadId) {
|
||||
async downloadModel(modelId, versionId, modelRoot, relativePath, useDefaultPaths = false, downloadId) {
|
||||
try {
|
||||
const response = await fetch(DOWNLOAD_ENDPOINTS.download, {
|
||||
method: 'POST',
|
||||
@@ -623,6 +623,7 @@ export class BaseModelApiClient {
|
||||
model_version_id: versionId,
|
||||
model_root: modelRoot,
|
||||
relative_path: relativePath,
|
||||
use_default_paths: useDefaultPaths,
|
||||
download_id: downloadId
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { modalManager } from './ModalManager.js';
|
||||
import { showToast } from '../utils/uiHelpers.js';
|
||||
import { state } from '../state/index.js';
|
||||
import { LoadingManager } from './LoadingManager.js';
|
||||
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
|
||||
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
||||
@@ -16,7 +17,8 @@ export class DownloadManager {
|
||||
this.initialized = false;
|
||||
this.selectedFolder = '';
|
||||
this.apiClient = null;
|
||||
|
||||
this.useDefaultPath = false;
|
||||
|
||||
this.loadingManager = new LoadingManager();
|
||||
this.folderTreeManager = new FolderTreeManager();
|
||||
this.folderClickHandler = null;
|
||||
@@ -29,6 +31,7 @@ export class DownloadManager {
|
||||
this.handleBackToUrl = this.backToUrl.bind(this);
|
||||
this.handleBackToVersions = this.backToVersions.bind(this);
|
||||
this.handleCloseModal = this.closeModal.bind(this);
|
||||
this.handleToggleDefaultPath = this.toggleDefaultPath.bind(this);
|
||||
}
|
||||
|
||||
showDownloadModal() {
|
||||
@@ -73,6 +76,9 @@ export class DownloadManager {
|
||||
document.getElementById('backToUrlBtn').addEventListener('click', this.handleBackToUrl);
|
||||
document.getElementById('backToVersionsBtn').addEventListener('click', this.handleBackToVersions);
|
||||
document.getElementById('closeDownloadModal').addEventListener('click', this.handleCloseModal);
|
||||
|
||||
// Default path toggle handler
|
||||
document.getElementById('useDefaultPath').addEventListener('change', this.handleToggleDefaultPath);
|
||||
}
|
||||
|
||||
updateModalLabels() {
|
||||
@@ -126,6 +132,9 @@ export class DownloadManager {
|
||||
if (this.folderTreeManager) {
|
||||
this.folderTreeManager.clearSelection();
|
||||
}
|
||||
|
||||
// Reset default path toggle
|
||||
this.loadDefaultPathSetting();
|
||||
}
|
||||
|
||||
async validateAndFetchVersions() {
|
||||
@@ -329,12 +338,63 @@ export class DownloadManager {
|
||||
this.updateTargetPath();
|
||||
});
|
||||
|
||||
// Load default path setting for current model type
|
||||
this.loadDefaultPathSetting();
|
||||
|
||||
this.updateTargetPath();
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
loadDefaultPathSetting() {
|
||||
const modelType = this.apiClient.modelType;
|
||||
const storageKey = `use_default_path_${modelType}`;
|
||||
this.useDefaultPath = getStorageItem(storageKey, false);
|
||||
|
||||
const toggleInput = document.getElementById('useDefaultPath');
|
||||
if (toggleInput) {
|
||||
toggleInput.checked = this.useDefaultPath;
|
||||
this.updatePathSelectionUI();
|
||||
}
|
||||
}
|
||||
|
||||
toggleDefaultPath(event) {
|
||||
this.useDefaultPath = event.target.checked;
|
||||
|
||||
// Save to localStorage per model type
|
||||
const modelType = this.apiClient.modelType;
|
||||
const storageKey = `use_default_path_${modelType}`;
|
||||
setStorageItem(storageKey, this.useDefaultPath);
|
||||
|
||||
this.updatePathSelectionUI();
|
||||
this.updateTargetPath();
|
||||
}
|
||||
|
||||
updatePathSelectionUI() {
|
||||
const manualSelection = document.getElementById('manualPathSelection');
|
||||
|
||||
// Always show manual path selection, but disable/enable based on useDefaultPath
|
||||
manualSelection.style.display = 'block';
|
||||
if (this.useDefaultPath) {
|
||||
manualSelection.classList.add('disabled');
|
||||
// Disable all inputs and buttons inside manualSelection
|
||||
manualSelection.querySelectorAll('input, select, button').forEach(el => {
|
||||
el.disabled = true;
|
||||
el.tabIndex = -1;
|
||||
});
|
||||
} else {
|
||||
manualSelection.classList.remove('disabled');
|
||||
manualSelection.querySelectorAll('input, select, button').forEach(el => {
|
||||
el.disabled = false;
|
||||
el.tabIndex = 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Always update the main path display
|
||||
this.updateTargetPath();
|
||||
}
|
||||
|
||||
backToUrl() {
|
||||
document.getElementById('versionStep').style.display = 'none';
|
||||
document.getElementById('urlStep').style.display = 'block';
|
||||
@@ -362,8 +422,16 @@ export class DownloadManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get selected folder path from folder tree manager
|
||||
const targetFolder = this.folderTreeManager.getSelectedPath();
|
||||
// Determine target folder and use_default_paths parameter
|
||||
let targetFolder = '';
|
||||
let useDefaultPaths = false;
|
||||
|
||||
if (this.useDefaultPath) {
|
||||
useDefaultPaths = true;
|
||||
targetFolder = ''; // Not needed when using default paths
|
||||
} else {
|
||||
targetFolder = this.folderTreeManager.getSelectedPath();
|
||||
}
|
||||
|
||||
try {
|
||||
const updateProgress = this.loadingManager.showDownloadProgress(1);
|
||||
@@ -402,12 +470,13 @@ export class DownloadManager {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
// Start download
|
||||
// Start download with use_default_paths parameter
|
||||
await this.apiClient.downloadModel(
|
||||
this.modelId,
|
||||
this.currentVersion.id,
|
||||
modelRoot,
|
||||
targetFolder,
|
||||
useDefaultPaths,
|
||||
downloadId
|
||||
);
|
||||
|
||||
@@ -418,19 +487,22 @@ export class DownloadManager {
|
||||
|
||||
// Update state and trigger reload
|
||||
const pageState = this.apiClient.getPageState();
|
||||
pageState.activeFolder = targetFolder;
|
||||
|
||||
// Save the active folder preference
|
||||
setStorageItem(`${this.apiClient.modelType}_activeFolder`, targetFolder);
|
||||
|
||||
// Update UI folder selection
|
||||
document.querySelectorAll('.folder-tags .tag').forEach(tag => {
|
||||
const isActive = tag.dataset.folder === targetFolder;
|
||||
tag.classList.toggle('active', isActive);
|
||||
if (isActive && !tag.parentNode.classList.contains('collapsed')) {
|
||||
tag.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
});
|
||||
if (!useDefaultPaths) {
|
||||
pageState.activeFolder = targetFolder;
|
||||
|
||||
// Save the active folder preference
|
||||
setStorageItem(`${this.apiClient.modelType}_activeFolder`, targetFolder);
|
||||
|
||||
// Update UI folder selection
|
||||
document.querySelectorAll('.folder-tags .tag').forEach(tag => {
|
||||
const isActive = tag.dataset.folder === targetFolder;
|
||||
tag.classList.toggle('active', isActive);
|
||||
if (isActive && !tag.parentNode.classList.contains('collapsed')) {
|
||||
tag.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await resetAndReload(true);
|
||||
|
||||
@@ -517,9 +589,23 @@ export class DownloadManager {
|
||||
let fullPath = modelRoot || `Select a ${config.displayName} root directory`;
|
||||
|
||||
if (modelRoot) {
|
||||
const selectedPath = this.folderTreeManager ? this.folderTreeManager.getSelectedPath() : '';
|
||||
if (selectedPath) {
|
||||
fullPath += '/' + selectedPath;
|
||||
if (this.useDefaultPath) {
|
||||
// Show actual template path
|
||||
try {
|
||||
const singularType = this.apiClient.modelType.replace(/s$/, '');
|
||||
const templates = state.global.settings.download_path_templates;
|
||||
const template = templates[singularType];
|
||||
fullPath += `/${template}`;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch template:', error);
|
||||
fullPath += '/[Auto-organized by path template]';
|
||||
}
|
||||
} else {
|
||||
// Show manual path selection
|
||||
const selectedPath = this.folderTreeManager ? this.folderTreeManager.getSelectedPath() : '';
|
||||
if (selectedPath) {
|
||||
fullPath += '/' + selectedPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,44 +32,57 @@
|
||||
<!-- Step 3: Location Selection -->
|
||||
<div class="download-step" id="locationStep" style="display: none;">
|
||||
<div class="location-selection">
|
||||
<!-- Path preview -->
|
||||
<!-- Path preview with inline toggle -->
|
||||
<div class="path-preview">
|
||||
<label>Download Location Preview:</label>
|
||||
<div class="path-preview-header">
|
||||
<label>Download Location Preview:</label>
|
||||
<div class="inline-toggle-container" title="When enabled, files are automatically organized using configured path templates">
|
||||
<span class="inline-toggle-label">Use Default Path</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="useDefaultPath">
|
||||
<label for="useDefaultPath" class="toggle-slider"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="path-display" id="targetPathDisplay">
|
||||
<span class="path-text">Select a root directory</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Root Selection (always visible) -->
|
||||
<div class="input-group">
|
||||
<label for="modelRoot" id="modelRootLabel">Select Model Root:</label>
|
||||
<select id="modelRoot"></select>
|
||||
</div>
|
||||
|
||||
<!-- Path input with autocomplete -->
|
||||
<div class="input-group">
|
||||
<label for="folderPath">Target Folder Path:</label>
|
||||
<div class="path-input-container">
|
||||
<input type="text" id="folderPath" placeholder="Type folder path or select from tree below..." autocomplete="off" />
|
||||
<button type="button" id="createFolderBtn" class="create-folder-btn" title="Create new folder">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
|
||||
<!-- Manual Path Selection (hidden when using default path) -->
|
||||
<div class="manual-path-selection" id="manualPathSelection">
|
||||
<!-- Path input with autocomplete -->
|
||||
<div class="input-group">
|
||||
<label for="folderPath">Target Folder Path:</label>
|
||||
<div class="path-input-container">
|
||||
<input type="text" id="folderPath" placeholder="Type folder path or select from tree below..." autocomplete="off" />
|
||||
<button type="button" id="createFolderBtn" class="create-folder-btn" title="Create new folder">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="path-suggestions" id="pathSuggestions" style="display: none;"></div>
|
||||
</div>
|
||||
<div class="path-suggestions" id="pathSuggestions" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb navigation -->
|
||||
<div class="breadcrumb-nav" id="breadcrumbNav">
|
||||
<span class="breadcrumb-item root" data-path="">
|
||||
<i class="fas fa-home"></i> Root
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Hierarchical folder tree -->
|
||||
<div class="input-group">
|
||||
<label>Browse Folders:</label>
|
||||
<div class="folder-tree-container">
|
||||
<div class="folder-tree" id="folderTree">
|
||||
<!-- Tree will be loaded dynamically -->
|
||||
|
||||
<!-- Breadcrumb navigation -->
|
||||
<div class="breadcrumb-nav" id="breadcrumbNav">
|
||||
<span class="breadcrumb-item root" data-path="">
|
||||
<i class="fas fa-home"></i> Root
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Hierarchical folder tree -->
|
||||
<div class="input-group">
|
||||
<label>Browse Folders:</label>
|
||||
<div class="folder-tree-container">
|
||||
<div class="folder-tree" id="folderTree">
|
||||
<!-- Tree will be loaded dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user