mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
refactor: Simplify API calls and enhance model moving functionality
This commit is contained in:
@@ -329,8 +329,6 @@ class ModelRouteUtils:
|
|||||||
# Update hash index if available
|
# Update hash index if available
|
||||||
if hasattr(scanner, '_hash_index') and scanner._hash_index:
|
if hasattr(scanner, '_hash_index') and scanner._hash_index:
|
||||||
scanner._hash_index.remove_by_path(file_path)
|
scanner._hash_index.remove_by_path(file_path)
|
||||||
|
|
||||||
await scanner._save_cache_to_disk()
|
|
||||||
|
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
'success': True,
|
'success': True,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { showToast } from '../utils/uiHelpers.js';
|
import { showToast, updateFolderTags } from '../utils/uiHelpers.js';
|
||||||
import { getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
|
import { getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
|
||||||
import {
|
import {
|
||||||
getCompleteApiConfig,
|
getCompleteApiConfig,
|
||||||
@@ -79,6 +79,53 @@ class ModelApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset and reload models with virtual scrolling
|
||||||
|
*/
|
||||||
|
async loadMoreWithVirtualScroll(resetPage = false, updateFolders = false) {
|
||||||
|
const pageState = this.getPageState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
state.loadingManager.showSimpleLoading(`Loading more ${this.apiConfig.config.displayName}s...`);
|
||||||
|
|
||||||
|
pageState.isLoading = true;
|
||||||
|
if (resetPage) {
|
||||||
|
pageState.currentPage = 1; // Reset to first page
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the current page
|
||||||
|
const startTime = performance.now();
|
||||||
|
const result = await this.fetchModelsPage(pageState.currentPage, pageState.pageSize);
|
||||||
|
const endTime = performance.now();
|
||||||
|
console.log(`fetchModelsPage耗时: ${(endTime - startTime).toFixed(2)} ms`);
|
||||||
|
|
||||||
|
// Update the virtual scroller
|
||||||
|
state.virtualScroller.refreshWithData(
|
||||||
|
result.items,
|
||||||
|
result.totalItems,
|
||||||
|
result.hasMore
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
pageState.hasMore = result.hasMore;
|
||||||
|
pageState.currentPage = pageState.currentPage + 1;
|
||||||
|
|
||||||
|
// Update folders if needed
|
||||||
|
if (updateFolders && result.folders) {
|
||||||
|
updateFolderTags(result.folders);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reloading ${this.apiConfig.config.displayName}s:`, error);
|
||||||
|
showToast(`Failed to reload ${this.apiConfig.config.displayName}s: ${error.message}`, 'error');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
pageState.isLoading = false;
|
||||||
|
state.loadingManager.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a model
|
* Delete a model
|
||||||
*/
|
*/
|
||||||
@@ -355,7 +402,7 @@ class ModelApiClient {
|
|||||||
/**
|
/**
|
||||||
* Fetch CivitAI metadata for all models
|
* Fetch CivitAI metadata for all models
|
||||||
*/
|
*/
|
||||||
async fetchCivitaiMetadata(resetAndReloadFunction) {
|
async fetchCivitaiMetadata() {
|
||||||
let ws = null;
|
let ws = null;
|
||||||
|
|
||||||
await state.loadingManager.showWithProgress(async (loading) => {
|
await state.loadingManager.showWithProgress(async (loading) => {
|
||||||
@@ -416,10 +463,6 @@ class ModelApiClient {
|
|||||||
|
|
||||||
await operationComplete;
|
await operationComplete;
|
||||||
|
|
||||||
if (typeof resetAndReloadFunction === 'function') {
|
|
||||||
await resetAndReloadFunction();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching metadata:', error);
|
console.error('Error fetching metadata:', error);
|
||||||
showToast('Failed to fetch metadata: ' + error.message, 'error');
|
showToast('Failed to fetch metadata: ' + error.message, 'error');
|
||||||
@@ -434,6 +477,110 @@ class ModelApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a single model to target path
|
||||||
|
* @returns {string|null} - The new file path if moved, null if not moved
|
||||||
|
*/
|
||||||
|
async moveSingleModel(filePath, targetPath) {
|
||||||
|
if (filePath.substring(0, filePath.lastIndexOf('/')) === targetPath) {
|
||||||
|
showToast('Model is already in the selected folder', 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.specific.moveModel, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_path: filePath,
|
||||||
|
target_path: targetPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (result && result.error) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
throw new Error('Failed to move model');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result && result.message) {
|
||||||
|
showToast(result.message, 'info');
|
||||||
|
} else {
|
||||||
|
showToast('Model moved successfully', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return new file path if move succeeded
|
||||||
|
if (result.success) {
|
||||||
|
return targetPath;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move multiple models to target path
|
||||||
|
* @returns {Array<string>} - Array of new file paths that were moved successfully
|
||||||
|
*/
|
||||||
|
async moveBulkModels(filePaths, targetPath) {
|
||||||
|
const movedPaths = filePaths.filter(path => {
|
||||||
|
return path.substring(0, path.lastIndexOf('/')) !== targetPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (movedPaths.length === 0) {
|
||||||
|
showToast('All selected models are already in the target folder', 'info');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.specific.moveBulk, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_paths: movedPaths,
|
||||||
|
target_path: targetPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to move models');
|
||||||
|
}
|
||||||
|
|
||||||
|
let successFilePaths = [];
|
||||||
|
if (result.success) {
|
||||||
|
if (result.failure_count > 0) {
|
||||||
|
showToast(`Moved ${result.success_count} models, ${result.failure_count} failed`, 'warning');
|
||||||
|
console.log('Move operation results:', result.results);
|
||||||
|
const failedFiles = result.results
|
||||||
|
.filter(r => !r.success)
|
||||||
|
.map(r => {
|
||||||
|
const fileName = r.path.substring(r.path.lastIndexOf('/') + 1);
|
||||||
|
return `${fileName}: ${r.message}`;
|
||||||
|
});
|
||||||
|
if (failedFiles.length > 0) {
|
||||||
|
const failureMessage = failedFiles.length <= 3
|
||||||
|
? failedFiles.join('\n')
|
||||||
|
: failedFiles.slice(0, 3).join('\n') + `\n(and ${failedFiles.length - 3} more)`;
|
||||||
|
showToast(`Failed moves:\n${failureMessage}`, 'warning', 6000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(`Successfully moved ${result.success_count} models`, 'success');
|
||||||
|
}
|
||||||
|
// Collect new file paths for successful moves
|
||||||
|
successFilePaths = result.results
|
||||||
|
.filter(r => r.success)
|
||||||
|
.map(r => r.path);
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || 'Failed to move models');
|
||||||
|
}
|
||||||
|
return successFilePaths;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build query parameters for API requests
|
* Build query parameters for API requests
|
||||||
*/
|
*/
|
||||||
@@ -527,48 +674,4 @@ export function getModelApiClient() {
|
|||||||
}
|
}
|
||||||
_singletonClient.setModelType(state.currentPageType);
|
_singletonClient.setModelType(state.currentPageType);
|
||||||
return _singletonClient;
|
return _singletonClient;
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy compatibility exports
|
|
||||||
export async function fetchModelsPage(options = {}) {
|
|
||||||
const { modelType = getCurrentModelType(), ...rest } = options;
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.fetchModelsPage(rest.page, rest.pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteModel(filePath, modelType = null) {
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.deleteModel(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function excludeModel(filePath, modelType = null) {
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.excludeModel(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function renameModelFile(filePath, newFileName, modelType = null) {
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.renameModelFile(filePath, newFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function replaceModelPreview(filePath, modelType = null) {
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.replaceModelPreview(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function refreshModels(options = {}) {
|
|
||||||
const { modelType = getCurrentModelType(), fullRebuild = false } = options;
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.refreshModels(fullRebuild);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function refreshSingleModelMetadata(filePath, modelType = null) {
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.refreshSingleModelMetadata(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchCivitaiMetadata(options = {}) {
|
|
||||||
const { modelType = getCurrentModelType(), resetAndReloadFunction } = options;
|
|
||||||
const client = createModelApiClient(modelType);
|
|
||||||
return client.fetchCivitaiMetadata(resetAndReloadFunction);
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ export const replacePreview = (filePath) => checkpointApiClient.replaceModelPrev
|
|||||||
export const saveModelMetadata = (filePath, data) => checkpointApiClient.saveModelMetadata(filePath, data);
|
export const saveModelMetadata = (filePath, data) => checkpointApiClient.saveModelMetadata(filePath, data);
|
||||||
export const refreshCheckpoints = (fullRebuild = false) => checkpointApiClient.refreshModels(fullRebuild);
|
export const refreshCheckpoints = (fullRebuild = false) => checkpointApiClient.refreshModels(fullRebuild);
|
||||||
export const refreshSingleCheckpointMetadata = (filePath) => checkpointApiClient.refreshSingleModelMetadata(filePath);
|
export const refreshSingleCheckpointMetadata = (filePath) => checkpointApiClient.refreshSingleModelMetadata(filePath);
|
||||||
export const fetchCivitai = (resetAndReloadFunction) => checkpointApiClient.fetchCivitaiMetadata(resetAndReloadFunction);
|
export const fetchCivitai = () => checkpointApiClient.fetchCivitaiMetadata();
|
||||||
|
|
||||||
// Pagination functions
|
// Pagination functions
|
||||||
export const fetchCheckpointsPage = (page = 1, pageSize = 50) => checkpointApiClient.fetchModelsPage(page, pageSize);
|
export const fetchCheckpointsPage = (page = 1, pageSize = 50) => checkpointApiClient.fetchModelsPage(page, pageSize);
|
||||||
@@ -23,7 +23,7 @@ export async function loadMoreCheckpoints(resetPage = false, updateFolders = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resetAndReload(updateFolders = false) {
|
export async function resetAndReload(updateFolders = false) {
|
||||||
return checkpointApiClient.resetAndReloadWithVirtualScroll(updateFolders);
|
return checkpointApiClient.loadMoreWithVirtualScroll(true, updateFolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkpoint-specific functions
|
// Checkpoint-specific functions
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const replacePreview = (filePath) => loraApiClient.replaceModelPreview(fi
|
|||||||
export const saveModelMetadata = (filePath, data) => loraApiClient.saveModelMetadata(filePath, data);
|
export const saveModelMetadata = (filePath, data) => loraApiClient.saveModelMetadata(filePath, data);
|
||||||
export const refreshLoras = (fullRebuild = false) => loraApiClient.refreshModels(fullRebuild);
|
export const refreshLoras = (fullRebuild = false) => loraApiClient.refreshModels(fullRebuild);
|
||||||
export const refreshSingleLoraMetadata = (filePath) => loraApiClient.refreshSingleModelMetadata(filePath);
|
export const refreshSingleLoraMetadata = (filePath) => loraApiClient.refreshSingleModelMetadata(filePath);
|
||||||
export const fetchCivitai = (resetAndReloadFunction) => loraApiClient.fetchCivitaiMetadata(resetAndReloadFunction);
|
export const fetchCivitai = () => loraApiClient.fetchCivitaiMetadata();
|
||||||
|
|
||||||
// Pagination functions
|
// Pagination functions
|
||||||
export const fetchLorasPage = (page = 1, pageSize = 100) => loraApiClient.fetchModelsPage(page, pageSize);
|
export const fetchLorasPage = (page = 1, pageSize = 100) => loraApiClient.fetchModelsPage(page, pageSize);
|
||||||
@@ -23,7 +23,7 @@ export async function loadMoreLoras(resetPage = false, updateFolders = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resetAndReload(updateFolders = false) {
|
export async function resetAndReload(updateFolders = false) {
|
||||||
return loraApiClient.resetAndReloadWithVirtualScroll(updateFolders);
|
return loraApiClient.loadMoreWithVirtualScroll(true, updateFolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoRA-specific functions that don't have common equivalents
|
// LoRA-specific functions that don't have common equivalents
|
||||||
|
|||||||
@@ -155,8 +155,6 @@ export class DownloadManager {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(earlyAccessBadge);
|
|
||||||
|
|
||||||
// Status badge for local models
|
// Status badge for local models
|
||||||
const localStatus = existsLocally ?
|
const localStatus = existsLocally ?
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { showToast, updateFolderTags } from '../utils/uiHelpers.js';
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { modalManager } from './ModalManager.js';
|
import { modalManager } from './ModalManager.js';
|
||||||
import { getStorageItem } from '../utils/storageHelpers.js';
|
import { getStorageItem } from '../utils/storageHelpers.js';
|
||||||
|
import { getModelApiClient } from '../api/baseModelApi.js';
|
||||||
|
|
||||||
class MoveManager {
|
class MoveManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -145,45 +146,46 @@ class MoveManager {
|
|||||||
targetPath = `${targetPath}/${newFolder}`;
|
targetPath = `${targetPath}/${newFolder}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const apiClient = getModelApiClient();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.bulkFilePaths) {
|
if (this.bulkFilePaths) {
|
||||||
// Bulk move mode
|
// Bulk move mode
|
||||||
await this.moveBulkModels(this.bulkFilePaths, targetPath);
|
const movedFilePaths = await apiClient.moveBulkModels(this.bulkFilePaths, targetPath);
|
||||||
|
|
||||||
// Update virtual scroller if in active folder view
|
// Update virtual scroller if in active folder view
|
||||||
const pageState = getCurrentPageState();
|
const pageState = getCurrentPageState();
|
||||||
if (pageState.activeFolder !== null && state.virtualScroller) {
|
if (pageState.activeFolder !== null && state.virtualScroller) {
|
||||||
// Remove moved items from virtual scroller instead of reloading
|
// Remove only successfully moved items
|
||||||
this.bulkFilePaths.forEach(filePath => {
|
movedFilePaths.forEach(newFilePath => {
|
||||||
state.virtualScroller.removeItemByFilePath(filePath);
|
// Find original filePath by matching filename
|
||||||
|
const filename = newFilePath.substring(newFilePath.lastIndexOf('/') + 1);
|
||||||
|
const originalFilePath = this.bulkFilePaths.find(fp => fp.endsWith('/' + filename));
|
||||||
|
if (originalFilePath) {
|
||||||
|
state.virtualScroller.removeItemByFilePath(originalFilePath);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Update the model cards' filepath in the DOM
|
// Update the model cards' filepath in the DOM
|
||||||
this.bulkFilePaths.forEach(filePath => {
|
movedFilePaths.forEach(newFilePath => {
|
||||||
// Extract filename from original path
|
const filename = newFilePath.substring(newFilePath.lastIndexOf('/') + 1);
|
||||||
const filename = filePath.substring(filePath.lastIndexOf('/') + 1);
|
const originalFilePath = this.bulkFilePaths.find(fp => fp.endsWith('/' + filename));
|
||||||
// Construct new filepath
|
if (originalFilePath) {
|
||||||
const newFilePath = `${targetPath}/${filename}`;
|
state.virtualScroller.updateSingleItem(originalFilePath, {file_path: newFilePath});
|
||||||
|
}
|
||||||
state.virtualScroller.updateSingleItem(filePath, {file_path: newFilePath});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Single move mode
|
// Single move mode
|
||||||
await this.moveSingleModel(this.currentFilePath, targetPath);
|
const newFilePath = await apiClient.moveSingleModel(this.currentFilePath, targetPath);
|
||||||
|
|
||||||
// Update virtual scroller if in active folder view
|
|
||||||
const pageState = getCurrentPageState();
|
|
||||||
if (pageState.activeFolder !== null && state.virtualScroller) {
|
|
||||||
// Remove moved item from virtual scroller instead of reloading
|
|
||||||
state.virtualScroller.removeItemByFilePath(this.currentFilePath);
|
|
||||||
} else {
|
|
||||||
// Extract filename from original path
|
|
||||||
const filename = this.currentFilePath.substring(this.currentFilePath.lastIndexOf('/') + 1);
|
|
||||||
// Construct new filepath
|
|
||||||
const newFilePath = `${targetPath}/${filename}`;
|
|
||||||
|
|
||||||
state.virtualScroller.updateSingleItem(this.currentFilePath, {file_path: newFilePath});
|
const pageState = getCurrentPageState();
|
||||||
|
if (newFilePath) {
|
||||||
|
if (pageState.activeFolder !== null && state.virtualScroller) {
|
||||||
|
state.virtualScroller.removeItemByFilePath(this.currentFilePath);
|
||||||
|
} else {
|
||||||
|
state.virtualScroller.updateSingleItem(this.currentFilePath, {file_path: newFilePath});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,102 +212,6 @@ class MoveManager {
|
|||||||
showToast('Failed to move model(s): ' + error.message, 'error');
|
showToast('Failed to move model(s): ' + error.message, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveSingleModel(filePath, targetPath) {
|
|
||||||
// show toast if current path is same as target path
|
|
||||||
if (filePath.substring(0, filePath.lastIndexOf('/')) === targetPath) {
|
|
||||||
showToast('Model is already in the selected folder', 'info');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch('/api/move_model', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
file_path: filePath,
|
|
||||||
target_path: targetPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (result && result.error) {
|
|
||||||
throw new Error(result.error);
|
|
||||||
}
|
|
||||||
throw new Error('Failed to move model');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result && result.message) {
|
|
||||||
showToast(result.message, 'info');
|
|
||||||
} else {
|
|
||||||
showToast('Model moved successfully', 'success');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async moveBulkModels(filePaths, targetPath) {
|
|
||||||
// Filter out models already in the target path
|
|
||||||
const movedPaths = filePaths.filter(path => {
|
|
||||||
return path.substring(0, path.lastIndexOf('/')) !== targetPath;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (movedPaths.length === 0) {
|
|
||||||
showToast('All selected models are already in the target folder', 'info');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch('/api/move_models_bulk', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
file_paths: movedPaths,
|
|
||||||
target_path: targetPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to move models');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display results with more details
|
|
||||||
if (result.success) {
|
|
||||||
if (result.failure_count > 0) {
|
|
||||||
// Some files failed to move
|
|
||||||
showToast(`Moved ${result.success_count} models, ${result.failure_count} failed`, 'warning');
|
|
||||||
|
|
||||||
// Log details about failures
|
|
||||||
console.log('Move operation results:', result.results);
|
|
||||||
|
|
||||||
// Get list of failed files with reasons
|
|
||||||
const failedFiles = result.results
|
|
||||||
.filter(r => !r.success)
|
|
||||||
.map(r => {
|
|
||||||
const fileName = r.path.substring(r.path.lastIndexOf('/') + 1);
|
|
||||||
return `${fileName}: ${r.message}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show first few failures in a toast
|
|
||||||
if (failedFiles.length > 0) {
|
|
||||||
const failureMessage = failedFiles.length <= 3
|
|
||||||
? failedFiles.join('\n')
|
|
||||||
: failedFiles.slice(0, 3).join('\n') + `\n(and ${failedFiles.length - 3} more)`;
|
|
||||||
|
|
||||||
showToast(`Failed moves:\n${failureMessage}`, 'warning', 6000);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// All files moved successfully
|
|
||||||
showToast(`Successfully moved ${result.success_count} models`, 'success');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message || 'Failed to move models');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const moveManager = new MoveManager();
|
export const moveManager = new MoveManager();
|
||||||
|
|||||||
@@ -164,23 +164,6 @@ export class VirtualScroller {
|
|||||||
|
|
||||||
// Calculate the left offset to center the grid within the content area
|
// Calculate the left offset to center the grid within the content area
|
||||||
this.leftOffset = Math.max(0, (availableContentWidth - actualGridWidth) / 2);
|
this.leftOffset = Math.max(0, (availableContentWidth - actualGridWidth) / 2);
|
||||||
|
|
||||||
// Log layout info
|
|
||||||
console.log('Virtual Scroll Layout:', {
|
|
||||||
containerWidth,
|
|
||||||
availableContentWidth,
|
|
||||||
actualGridWidth,
|
|
||||||
columnsCount: this.columnsCount,
|
|
||||||
itemWidth: this.itemWidth,
|
|
||||||
itemHeight: this.itemHeight,
|
|
||||||
leftOffset: this.leftOffset,
|
|
||||||
paddingLeft,
|
|
||||||
paddingRight,
|
|
||||||
displayDensity,
|
|
||||||
maxColumns,
|
|
||||||
baseCardWidth,
|
|
||||||
rowGap: this.rowGap
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update grid element max-width to match available width
|
// Update grid element max-width to match available width
|
||||||
this.gridElement.style.maxWidth = `${actualGridWidth}px`;
|
this.gridElement.style.maxWidth = `${actualGridWidth}px`;
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export async function confirmDelete() {
|
|||||||
export function closeDeleteModal() {
|
export function closeDeleteModal() {
|
||||||
modalManager.closeModal('deleteModal');
|
modalManager.closeModal('deleteModal');
|
||||||
pendingDeletePath = null;
|
pendingDeletePath = null;
|
||||||
pendingModelType = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions for the exclude modal
|
// Functions for the exclude modal
|
||||||
@@ -67,7 +66,6 @@ export function showExcludeModal(filePath) {
|
|||||||
export function closeExcludeModal() {
|
export function closeExcludeModal() {
|
||||||
modalManager.closeModal('excludeModal');
|
modalManager.closeModal('excludeModal');
|
||||||
pendingExcludePath = null;
|
pendingExcludePath = null;
|
||||||
pendingExcludeModelType = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function confirmExclude() {
|
export async function confirmExclude() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { state } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { resetAndReload } from '../api/loraApi.js';
|
import { resetAndReload } from '../api/loraApi.js';
|
||||||
import { getStorageItem, setStorageItem } from './storageHelpers.js';
|
import { getStorageItem, setStorageItem } from './storageHelpers.js';
|
||||||
import { NODE_TYPES, NODE_TYPE_ICONS, DEFAULT_NODE_COLOR } from './constants.js';
|
import { NODE_TYPES, NODE_TYPE_ICONS, DEFAULT_NODE_COLOR } from './constants.js';
|
||||||
@@ -626,7 +626,8 @@ export function updateFolderTags(folders) {
|
|||||||
if (!folderTagsContainer) return;
|
if (!folderTagsContainer) return;
|
||||||
|
|
||||||
// Keep track of currently selected folder
|
// Keep track of currently selected folder
|
||||||
const currentFolder = this.pageState.activeFolder;
|
const pageState = getCurrentPageState();
|
||||||
|
const currentFolder = pageState.activeFolder;
|
||||||
|
|
||||||
// Create HTML for folder tags
|
// Create HTML for folder tags
|
||||||
const tagsHTML = folders.map(folder => {
|
const tagsHTML = folders.map(folder => {
|
||||||
|
|||||||
Reference in New Issue
Block a user