mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
feat: enhance download and move modals with improved folder path input, autocomplete, and folder tree integration
This commit is contained in:
@@ -1,8 +1,4 @@
|
|||||||
/* Download Modal Styles */
|
/* Download Modal Styles */
|
||||||
.download-step {
|
|
||||||
margin: var(--space-2) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
}
|
}
|
||||||
@@ -184,7 +180,7 @@
|
|||||||
|
|
||||||
.path-suggestions {
|
.path-suggestions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 42%;
|
top: 46%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export class FolderTreeManager {
|
|||||||
this.expandedNodes = new Set();
|
this.expandedNodes = new Set();
|
||||||
this.pathSuggestions = [];
|
this.pathSuggestions = [];
|
||||||
this.onPathChangeCallback = null;
|
this.onPathChangeCallback = null;
|
||||||
|
this.activeSuggestionIndex = -1;
|
||||||
|
this.elementsPrefix = '';
|
||||||
|
|
||||||
// Bind methods
|
// Bind methods
|
||||||
this.handleTreeClick = this.handleTreeClick.bind(this);
|
this.handleTreeClick = this.handleTreeClick.bind(this);
|
||||||
@@ -15,33 +17,31 @@ export class FolderTreeManager {
|
|||||||
this.handlePathSuggestionClick = this.handlePathSuggestionClick.bind(this);
|
this.handlePathSuggestionClick = this.handlePathSuggestionClick.bind(this);
|
||||||
this.handleCreateFolder = this.handleCreateFolder.bind(this);
|
this.handleCreateFolder = this.handleCreateFolder.bind(this);
|
||||||
this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this);
|
this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this);
|
||||||
|
this.handlePathKeyDown = this.handlePathKeyDown.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the folder tree manager
|
* Initialize the folder tree manager
|
||||||
* @param {Object} config - Configuration object
|
* @param {Object} config - Configuration object
|
||||||
* @param {Function} config.onPathChange - Callback when path changes
|
* @param {Function} config.onPathChange - Callback when path changes
|
||||||
|
* @param {string} config.elementsPrefix - Prefix for element IDs (e.g., 'move' for move modal)
|
||||||
*/
|
*/
|
||||||
init(config = {}) {
|
init(config = {}) {
|
||||||
this.onPathChangeCallback = config.onPathChange;
|
this.onPathChangeCallback = config.onPathChange;
|
||||||
|
this.elementsPrefix = config.elementsPrefix || '';
|
||||||
this.setupEventHandlers();
|
this.setupEventHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventHandlers() {
|
setupEventHandlers() {
|
||||||
const pathInput = document.getElementById('folderPath');
|
const pathInput = document.getElementById(this.getElementId('folderPath'));
|
||||||
const createFolderBtn = document.getElementById('createFolderBtn');
|
const createFolderBtn = document.getElementById(this.getElementId('createFolderBtn'));
|
||||||
const folderTree = document.getElementById('folderTree');
|
const folderTree = document.getElementById(this.getElementId('folderTree'));
|
||||||
const breadcrumbNav = document.getElementById('breadcrumbNav');
|
const breadcrumbNav = document.getElementById(this.getElementId('breadcrumbNav'));
|
||||||
const pathSuggestions = document.getElementById('pathSuggestions');
|
const pathSuggestions = document.getElementById(this.getElementId('pathSuggestions'));
|
||||||
|
|
||||||
if (pathInput) {
|
if (pathInput) {
|
||||||
pathInput.addEventListener('input', this.handlePathInput);
|
pathInput.addEventListener('input', this.handlePathInput);
|
||||||
pathInput.addEventListener('keydown', (e) => {
|
pathInput.addEventListener('keydown', this.handlePathKeyDown);
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
this.selectCurrentInput();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createFolderBtn) {
|
if (createFolderBtn) {
|
||||||
@@ -62,13 +62,81 @@ export class FolderTreeManager {
|
|||||||
|
|
||||||
// Hide suggestions when clicking outside
|
// Hide suggestions when clicking outside
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const pathInput = document.getElementById('folderPath');
|
const pathInput = document.getElementById(this.getElementId('folderPath'));
|
||||||
const suggestions = document.getElementById('pathSuggestions');
|
const suggestions = document.getElementById(this.getElementId('pathSuggestions'));
|
||||||
|
|
||||||
if (pathInput && suggestions &&
|
if (pathInput && suggestions &&
|
||||||
!pathInput.contains(e.target) &&
|
!pathInput.contains(e.target) &&
|
||||||
!suggestions.contains(e.target)) {
|
!suggestions.contains(e.target)) {
|
||||||
suggestions.style.display = 'none';
|
suggestions.style.display = 'none';
|
||||||
|
this.activeSuggestionIndex = -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element ID with prefix
|
||||||
|
*/
|
||||||
|
getElementId(elementName) {
|
||||||
|
return this.elementsPrefix ? `${this.elementsPrefix}${elementName.charAt(0).toUpperCase()}${elementName.slice(1)}` : elementName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle path input key events with enhanced keyboard navigation
|
||||||
|
*/
|
||||||
|
handlePathKeyDown(event) {
|
||||||
|
const suggestions = document.getElementById(this.getElementId('pathSuggestions'));
|
||||||
|
const isVisible = suggestions && suggestions.style.display !== 'none';
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
const suggestionItems = suggestions.querySelectorAll('.path-suggestion');
|
||||||
|
const maxIndex = suggestionItems.length - 1;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Escape':
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.hideSuggestions();
|
||||||
|
this.activeSuggestionIndex = -1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
|
this.activeSuggestionIndex = Math.min(this.activeSuggestionIndex + 1, maxIndex);
|
||||||
|
this.updateActiveSuggestion(suggestionItems);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault();
|
||||||
|
this.activeSuggestionIndex = Math.max(this.activeSuggestionIndex - 1, -1);
|
||||||
|
this.updateActiveSuggestion(suggestionItems);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.activeSuggestionIndex >= 0 && suggestionItems[this.activeSuggestionIndex]) {
|
||||||
|
const path = suggestionItems[this.activeSuggestionIndex].dataset.path;
|
||||||
|
this.selectPath(path);
|
||||||
|
this.hideSuggestions();
|
||||||
|
} else {
|
||||||
|
this.selectCurrentInput();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.selectCurrentInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update active suggestion highlighting
|
||||||
|
*/
|
||||||
|
updateActiveSuggestion(suggestionItems) {
|
||||||
|
suggestionItems.forEach((item, index) => {
|
||||||
|
item.classList.toggle('active', index === this.activeSuggestionIndex);
|
||||||
|
if (index === this.activeSuggestionIndex) {
|
||||||
|
item.scrollIntoView({ block: 'nearest' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -105,7 +173,7 @@ export class FolderTreeManager {
|
|||||||
* Render the complete folder tree
|
* Render the complete folder tree
|
||||||
*/
|
*/
|
||||||
renderTree() {
|
renderTree() {
|
||||||
const folderTree = document.getElementById('folderTree');
|
const folderTree = document.getElementById(this.getElementId('folderTree'));
|
||||||
if (!folderTree) return;
|
if (!folderTree) return;
|
||||||
|
|
||||||
folderTree.innerHTML = this.renderTreeNode(this.treeData, '');
|
folderTree.innerHTML = this.renderTreeNode(this.treeData, '');
|
||||||
@@ -183,6 +251,8 @@ export class FolderTreeManager {
|
|||||||
const input = event.target;
|
const input = event.target;
|
||||||
const query = input.value.toLowerCase();
|
const query = input.value.toLowerCase();
|
||||||
|
|
||||||
|
this.activeSuggestionIndex = -1; // Reset active suggestion
|
||||||
|
|
||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
this.hideSuggestions();
|
this.hideSuggestions();
|
||||||
return;
|
return;
|
||||||
@@ -199,7 +269,7 @@ export class FolderTreeManager {
|
|||||||
* Show path suggestions
|
* Show path suggestions
|
||||||
*/
|
*/
|
||||||
showSuggestions(suggestions, query) {
|
showSuggestions(suggestions, query) {
|
||||||
const suggestionsEl = document.getElementById('pathSuggestions');
|
const suggestionsEl = document.getElementById(this.getElementId('pathSuggestions'));
|
||||||
if (!suggestionsEl) return;
|
if (!suggestionsEl) return;
|
||||||
|
|
||||||
if (suggestions.length === 0) {
|
if (suggestions.length === 0) {
|
||||||
@@ -213,13 +283,14 @@ export class FolderTreeManager {
|
|||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
suggestionsEl.style.display = 'block';
|
suggestionsEl.style.display = 'block';
|
||||||
|
this.activeSuggestionIndex = -1; // Reset active index
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide path suggestions
|
* Hide path suggestions
|
||||||
*/
|
*/
|
||||||
hideSuggestions() {
|
hideSuggestions() {
|
||||||
const suggestionsEl = document.getElementById('pathSuggestions');
|
const suggestionsEl = document.getElementById(this.getElementId('pathSuggestions'));
|
||||||
if (suggestionsEl) {
|
if (suggestionsEl) {
|
||||||
suggestionsEl.style.display = 'none';
|
suggestionsEl.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -264,7 +335,7 @@ export class FolderTreeManager {
|
|||||||
// Find the parent node in the tree
|
// Find the parent node in the tree
|
||||||
const parentNode = parentPath ?
|
const parentNode = parentPath ?
|
||||||
document.querySelector(`[data-path="${parentPath}"]`) :
|
document.querySelector(`[data-path="${parentPath}"]`) :
|
||||||
document.getElementById('folderTree');
|
document.getElementById(this.getElementId('folderTree'));
|
||||||
|
|
||||||
if (!parentNode) return;
|
if (!parentNode) return;
|
||||||
|
|
||||||
@@ -369,22 +440,25 @@ export class FolderTreeManager {
|
|||||||
this.selectedPath = path;
|
this.selectedPath = path;
|
||||||
|
|
||||||
// Update path input
|
// Update path input
|
||||||
const pathInput = document.getElementById('folderPath');
|
const pathInput = document.getElementById(this.getElementId('folderPath'));
|
||||||
if (pathInput) {
|
if (pathInput) {
|
||||||
pathInput.value = path;
|
pathInput.value = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update tree selection
|
// Update tree selection
|
||||||
document.querySelectorAll('.tree-node-content').forEach(node => {
|
const treeContainer = document.getElementById(this.getElementId('folderTree'));
|
||||||
node.classList.remove('selected');
|
if (treeContainer) {
|
||||||
});
|
treeContainer.querySelectorAll('.tree-node-content').forEach(node => {
|
||||||
|
node.classList.remove('selected');
|
||||||
const selectedNode = document.querySelector(`[data-path="${path}"] .tree-node-content`);
|
});
|
||||||
if (selectedNode) {
|
|
||||||
selectedNode.classList.add('selected');
|
|
||||||
|
|
||||||
// Expand parents to show selection
|
const selectedNode = treeContainer.querySelector(`[data-path="${path}"] .tree-node-content`);
|
||||||
this.expandPathParents(path);
|
if (selectedNode) {
|
||||||
|
selectedNode.classList.add('selected');
|
||||||
|
|
||||||
|
// Expand parents to show selection
|
||||||
|
this.expandPathParents(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update breadcrumbs
|
// Update breadcrumbs
|
||||||
@@ -415,7 +489,7 @@ export class FolderTreeManager {
|
|||||||
* Update breadcrumb navigation
|
* Update breadcrumb navigation
|
||||||
*/
|
*/
|
||||||
updateBreadcrumbs(path) {
|
updateBreadcrumbs(path) {
|
||||||
const breadcrumbNav = document.getElementById('breadcrumbNav');
|
const breadcrumbNav = document.getElementById(this.getElementId('breadcrumbNav'));
|
||||||
if (!breadcrumbNav) return;
|
if (!breadcrumbNav) return;
|
||||||
|
|
||||||
const parts = path ? path.split('/') : [];
|
const parts = path ? path.split('/') : [];
|
||||||
@@ -449,7 +523,7 @@ export class FolderTreeManager {
|
|||||||
* Select current input value as path
|
* Select current input value as path
|
||||||
*/
|
*/
|
||||||
selectCurrentInput() {
|
selectCurrentInput() {
|
||||||
const pathInput = document.getElementById('folderPath');
|
const pathInput = document.getElementById(this.getElementId('folderPath'));
|
||||||
if (pathInput) {
|
if (pathInput) {
|
||||||
const path = pathInput.value.trim();
|
const path = pathInput.value.trim();
|
||||||
this.selectPath(path);
|
this.selectPath(path);
|
||||||
@@ -474,14 +548,15 @@ export class FolderTreeManager {
|
|||||||
* Clean up event handlers
|
* Clean up event handlers
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
const pathInput = document.getElementById('folderPath');
|
const pathInput = document.getElementById(this.getElementId('folderPath'));
|
||||||
const createFolderBtn = document.getElementById('createFolderBtn');
|
const createFolderBtn = document.getElementById(this.getElementId('createFolderBtn'));
|
||||||
const folderTree = document.getElementById('folderTree');
|
const folderTree = document.getElementById(this.getElementId('folderTree'));
|
||||||
const breadcrumbNav = document.getElementById('breadcrumbNav');
|
const breadcrumbNav = document.getElementById(this.getElementId('breadcrumbNav'));
|
||||||
const pathSuggestions = document.getElementById('pathSuggestions');
|
const pathSuggestions = document.getElementById(this.getElementId('pathSuggestions'));
|
||||||
|
|
||||||
if (pathInput) {
|
if (pathInput) {
|
||||||
pathInput.removeEventListener('input', this.handlePathInput);
|
pathInput.removeEventListener('input', this.handlePathInput);
|
||||||
|
pathInput.removeEventListener('keydown', this.handlePathKeyDown);
|
||||||
}
|
}
|
||||||
if (createFolderBtn) {
|
if (createFolderBtn) {
|
||||||
createFolderBtn.removeEventListener('click', this.handleCreateFolder);
|
createFolderBtn.removeEventListener('click', this.handleCreateFolder);
|
||||||
|
|||||||
@@ -306,6 +306,12 @@ export class DownloadManager {
|
|||||||
modelRoot.value = defaultRoot;
|
modelRoot.value = defaultRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set autocomplete="off" on folderPath input
|
||||||
|
const folderPathInput = document.getElementById('folderPath');
|
||||||
|
if (folderPathInput) {
|
||||||
|
folderPathInput.setAttribute('autocomplete', 'off');
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize folder tree
|
// Initialize folder tree
|
||||||
await this.initializeFolderTree();
|
await this.initializeFolderTree();
|
||||||
|
|
||||||
|
|||||||
@@ -4,48 +4,31 @@ import { modalManager } from './ModalManager.js';
|
|||||||
import { bulkManager } from './BulkManager.js';
|
import { bulkManager } from './BulkManager.js';
|
||||||
import { getStorageItem } from '../utils/storageHelpers.js';
|
import { getStorageItem } from '../utils/storageHelpers.js';
|
||||||
import { getModelApiClient } from '../api/modelApiFactory.js';
|
import { getModelApiClient } from '../api/modelApiFactory.js';
|
||||||
|
import { FolderTreeManager } from '../components/FolderTreeManager.js';
|
||||||
|
|
||||||
class MoveManager {
|
class MoveManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.currentFilePath = null;
|
this.currentFilePath = null;
|
||||||
this.bulkFilePaths = null;
|
this.bulkFilePaths = null;
|
||||||
this.modal = document.getElementById('moveModal');
|
this.folderTreeManager = new FolderTreeManager();
|
||||||
this.modelRootSelect = document.getElementById('moveModelRoot');
|
this.initialized = false;
|
||||||
this.folderBrowser = document.getElementById('moveFolderBrowser');
|
|
||||||
this.newFolderInput = document.getElementById('moveNewFolder');
|
// Bind methods
|
||||||
this.pathDisplay = document.getElementById('moveTargetPathDisplay');
|
this.updateTargetPath = this.updateTargetPath.bind(this);
|
||||||
this.modalTitle = document.getElementById('moveModalTitle');
|
|
||||||
this.rootLabel = document.getElementById('moveRootLabel');
|
|
||||||
|
|
||||||
this.initializeEventListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeEventListeners() {
|
initializeEventListeners() {
|
||||||
|
if (this.initialized) return;
|
||||||
|
|
||||||
|
const modelRootSelect = document.getElementById('moveModelRoot');
|
||||||
|
|
||||||
// Initialize model root directory selector
|
// Initialize model root directory selector
|
||||||
this.modelRootSelect.addEventListener('change', () => this.updatePathPreview());
|
modelRootSelect.addEventListener('change', async () => {
|
||||||
|
await this.initializeFolderTree();
|
||||||
// Folder selection event
|
this.updateTargetPath();
|
||||||
this.folderBrowser.addEventListener('click', (e) => {
|
|
||||||
const folderItem = e.target.closest('.folder-item');
|
|
||||||
if (!folderItem) return;
|
|
||||||
|
|
||||||
// If clicking already selected folder, deselect it
|
|
||||||
if (folderItem.classList.contains('selected')) {
|
|
||||||
folderItem.classList.remove('selected');
|
|
||||||
} else {
|
|
||||||
// Deselect other folders
|
|
||||||
this.folderBrowser.querySelectorAll('.folder-item').forEach(item => {
|
|
||||||
item.classList.remove('selected');
|
|
||||||
});
|
|
||||||
// Select current folder
|
|
||||||
folderItem.classList.add('selected');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updatePathPreview();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// New folder input event
|
this.initialized = true;
|
||||||
this.newFolderInput.addEventListener('input', () => this.updatePathPreview());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async showMoveModal(filePath, modelType = null) {
|
async showMoveModal(filePath, modelType = null) {
|
||||||
@@ -65,31 +48,30 @@ class MoveManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.bulkFilePaths = selectedPaths;
|
this.bulkFilePaths = selectedPaths;
|
||||||
this.modalTitle.textContent = `Move ${selectedPaths.length} ${modelConfig.displayName}s`;
|
document.getElementById('moveModalTitle').textContent = `Move ${selectedPaths.length} ${modelConfig.displayName}s`;
|
||||||
} else {
|
} else {
|
||||||
// Single file mode
|
// Single file mode
|
||||||
this.currentFilePath = filePath;
|
this.currentFilePath = filePath;
|
||||||
this.modalTitle.textContent = `Move ${modelConfig.displayName}`;
|
document.getElementById('moveModalTitle').textContent = `Move ${modelConfig.displayName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update UI labels based on model type
|
// Update UI labels based on model type
|
||||||
this.rootLabel.textContent = `Select ${modelConfig.displayName} Root:`;
|
document.getElementById('moveRootLabel').textContent = `Select ${modelConfig.displayName} Root:`;
|
||||||
this.pathDisplay.querySelector('.path-text').textContent = `Select a ${modelConfig.displayName.toLowerCase()} root directory`;
|
document.getElementById('moveTargetPathDisplay').querySelector('.path-text').textContent = `Select a ${modelConfig.displayName.toLowerCase()} root directory`;
|
||||||
|
|
||||||
// Clear previous selections
|
// Clear folder path input
|
||||||
this.folderBrowser.querySelectorAll('.folder-item').forEach(item => {
|
const folderPathInput = document.getElementById('moveFolderPath');
|
||||||
item.classList.remove('selected');
|
if (folderPathInput) {
|
||||||
});
|
folderPathInput.value = '';
|
||||||
this.newFolderInput.value = '';
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch model roots
|
// Fetch model roots
|
||||||
|
const modelRootSelect = document.getElementById('moveModelRoot');
|
||||||
let rootsData;
|
let rootsData;
|
||||||
if (modelType) {
|
if (modelType) {
|
||||||
// For checkpoints, use the specific API method that considers modelType
|
|
||||||
rootsData = await apiClient.fetchModelRoots(modelType);
|
rootsData = await apiClient.fetchModelRoots(modelType);
|
||||||
} else {
|
} else {
|
||||||
// For other model types, use the generic method
|
|
||||||
rootsData = await apiClient.fetchModelRoots();
|
rootsData = await apiClient.fetchModelRoots();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,27 +80,38 @@ class MoveManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Populate model root selector
|
// Populate model root selector
|
||||||
this.modelRootSelect.innerHTML = rootsData.roots.map(root =>
|
modelRootSelect.innerHTML = rootsData.roots.map(root =>
|
||||||
`<option value="${root}">${root}</option>`
|
`<option value="${root}">${root}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
// Set default root if available
|
// Set default root if available
|
||||||
const settingsKey = `default_${currentPageType.slice(0, -1)}_root`; // Remove 's' from plural
|
const settingsKey = `default_${currentPageType.slice(0, -1)}_root`;
|
||||||
const defaultRoot = getStorageItem('settings', {})[settingsKey];
|
const defaultRoot = getStorageItem('settings', {})[settingsKey];
|
||||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||||
this.modelRootSelect.value = defaultRoot;
|
modelRootSelect.value = defaultRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch folders dynamically
|
// Initialize event listeners
|
||||||
const foldersData = await apiClient.fetchModelFolders();
|
this.initializeEventListeners();
|
||||||
|
|
||||||
// Update folder browser with dynamic content
|
// Setup folder tree manager
|
||||||
this.folderBrowser.innerHTML = foldersData.folders.map(folder =>
|
this.folderTreeManager.init({
|
||||||
`<div class="folder-item" data-folder="${folder}">${folder}</div>`
|
onPathChange: (path) => {
|
||||||
).join('');
|
this.updateTargetPath();
|
||||||
|
},
|
||||||
|
elementsPrefix: 'move'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize folder tree
|
||||||
|
await this.initializeFolderTree();
|
||||||
|
|
||||||
this.updatePathPreview();
|
this.updateTargetPath();
|
||||||
modalManager.showModal('moveModal');
|
modalManager.showModal('moveModal', null, () => {
|
||||||
|
// Cleanup on modal close
|
||||||
|
if (this.folderTreeManager) {
|
||||||
|
this.folderTreeManager.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching ${modelConfig.displayName.toLowerCase()} roots or folders:`, error);
|
console.error(`Error fetching ${modelConfig.displayName.toLowerCase()} roots or folders:`, error);
|
||||||
@@ -126,36 +119,60 @@ class MoveManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePathPreview() {
|
async initializeFolderTree() {
|
||||||
const selectedRoot = this.modelRootSelect.value;
|
try {
|
||||||
const selectedFolder = this.folderBrowser.querySelector('.folder-item.selected')?.dataset.folder || '';
|
const apiClient = getModelApiClient();
|
||||||
const newFolder = this.newFolderInput.value.trim();
|
// Fetch unified folder tree
|
||||||
|
const treeData = await apiClient.fetchUnifiedFolderTree();
|
||||||
let targetPath = selectedRoot;
|
|
||||||
if (selectedFolder) {
|
if (treeData.success) {
|
||||||
targetPath = `${targetPath}/${selectedFolder}`;
|
// Load tree data into folder tree manager
|
||||||
|
await this.folderTreeManager.loadTree(treeData.tree);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to fetch folder tree:', treeData.error);
|
||||||
|
showToast('Failed to load folder tree', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing folder tree:', error);
|
||||||
|
showToast('Error loading folder tree', 'error');
|
||||||
}
|
}
|
||||||
if (newFolder) {
|
}
|
||||||
targetPath = `${targetPath}/${newFolder}`;
|
|
||||||
|
updateTargetPath() {
|
||||||
|
const pathDisplay = document.getElementById('moveTargetPathDisplay');
|
||||||
|
const modelRoot = document.getElementById('moveModelRoot').value;
|
||||||
|
const apiClient = getModelApiClient();
|
||||||
|
const config = apiClient.apiConfig.config;
|
||||||
|
|
||||||
|
let fullPath = modelRoot || `Select a ${config.displayName.toLowerCase()} root directory`;
|
||||||
|
|
||||||
|
if (modelRoot) {
|
||||||
|
const selectedPath = this.folderTreeManager ? this.folderTreeManager.getSelectedPath() : '';
|
||||||
|
if (selectedPath) {
|
||||||
|
fullPath += '/' + selectedPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pathDisplay.querySelector('.path-text').textContent = targetPath;
|
pathDisplay.innerHTML = `<span class="path-text">${fullPath}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveModel() {
|
async moveModel() {
|
||||||
const selectedRoot = this.modelRootSelect.value;
|
const selectedRoot = document.getElementById('moveModelRoot').value;
|
||||||
const selectedFolder = this.folderBrowser.querySelector('.folder-item.selected')?.dataset.folder || '';
|
|
||||||
const newFolder = this.newFolderInput.value.trim();
|
|
||||||
|
|
||||||
let targetPath = selectedRoot;
|
|
||||||
if (selectedFolder) {
|
|
||||||
targetPath = `${targetPath}/${selectedFolder}`;
|
|
||||||
}
|
|
||||||
if (newFolder) {
|
|
||||||
targetPath = `${targetPath}/${newFolder}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiClient = getModelApiClient();
|
const apiClient = getModelApiClient();
|
||||||
|
const config = apiClient.apiConfig.config;
|
||||||
|
|
||||||
|
if (!selectedRoot) {
|
||||||
|
showToast(`Please select a ${config.displayName.toLowerCase()} root directory`, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get selected folder path from folder tree manager
|
||||||
|
const targetFolder = this.folderTreeManager.getSelectedPath();
|
||||||
|
|
||||||
|
let targetPath = selectedRoot;
|
||||||
|
if (targetFolder) {
|
||||||
|
targetPath = `${targetPath}/${targetFolder}`;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.bulkFilePaths) {
|
if (this.bulkFilePaths) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<!-- Unified Download Modal for all model types -->
|
<!-- Unified Download Modal for all model types -->
|
||||||
<div id="downloadModal" class="modal">
|
<div id="downloadModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<button class="close" id="closeDownloadModal">×</button>
|
<div class="modal-header">
|
||||||
<h2 id="downloadModalTitle">Download Model from URL</h2>
|
<button class="close" id="closeDownloadModal">×</button>
|
||||||
|
<h2 id="downloadModalTitle">Download Model from URL</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Step 1: URL Input -->
|
<!-- Step 1: URL Input -->
|
||||||
<div class="download-step" id="urlStep">
|
<div class="download-step" id="urlStep">
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="folderPath">Target Folder Path:</label>
|
<label for="folderPath">Target Folder Path:</label>
|
||||||
<div class="path-input-container">
|
<div class="path-input-container">
|
||||||
<input type="text" id="folderPath" placeholder="Type folder path or select from tree below..." />
|
<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">
|
<button type="button" id="createFolderBtn" class="create-folder-btn" title="Create new folder">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<span class="close" onclick="modalManager.closeModal('moveModal')">×</span>
|
<span class="close" onclick="modalManager.closeModal('moveModal')">×</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="location-selection">
|
<div class="location-selection">
|
||||||
|
<!-- Path preview -->
|
||||||
<div class="path-preview">
|
<div class="path-preview">
|
||||||
<label>Target Location Preview:</label>
|
<label>Target Location Preview:</label>
|
||||||
<div class="path-display" id="moveTargetPathDisplay">
|
<div class="path-display" id="moveTargetPathDisplay">
|
||||||
@@ -14,18 +15,37 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label id="moveRootLabel">Select Model Root:</label>
|
<label for="moveModelRoot" id="moveRootLabel">Select Model Root:</label>
|
||||||
<select id="moveModelRoot"></select>
|
<select id="moveModelRoot"></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Path input with autocomplete -->
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>Target Folder:</label>
|
<label for="moveFolderPath">Target Folder Path:</label>
|
||||||
<div class="folder-browser" id="moveFolderBrowser">
|
<div class="path-input-container">
|
||||||
<!-- Folders will be loaded dynamically -->
|
<input type="text" id="moveFolderPath" placeholder="Type folder path or select from tree below..." autocomplete="off" />
|
||||||
|
<button type="button" id="moveCreateFolderBtn" class="create-folder-btn" title="Create new folder">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="path-suggestions" id="movePathSuggestions" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Breadcrumb navigation -->
|
||||||
|
<div class="breadcrumb-nav" id="moveBreadcrumbNav">
|
||||||
|
<span class="breadcrumb-item root" data-path="">
|
||||||
|
<i class="fas fa-home"></i> Root
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hierarchical folder tree -->
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="moveNewFolder">New Folder (optional):</label>
|
<label>Browse Folders:</label>
|
||||||
<input type="text" id="moveNewFolder" placeholder="Enter folder name" />
|
<div class="folder-tree-container">
|
||||||
|
<div class="folder-tree" id="moveFolderTree">
|
||||||
|
<!-- Tree will be loaded dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user