Files
arcum42-ComfyUI_SageUtils/js/file/fileManager.js
T

656 lines
22 KiB
JavaScript

/**
* Generic File Manager Component
* Combines file browser, editor, and preview into a cohesive file management interface
*/
import { GenericFileBrowser } from './fileBrowser.js';
import { GenericFileEditor } from './fileEditor.js';
import { GenericFilePreview } from './filePreview.js';
/**
* Generic File Manager Class
* Orchestrates file browser, editor, and preview components
*/
export class GenericFileManager {
constructor(configKey, options = {}) {
this.configKey = configKey;
this.options = {
showPreview: true,
showEditor: true,
layout: 'vertical', // 'vertical' or 'horizontal'
previewFirst: false, // whether to show preview above editor
tabbedView: false, // whether to show preview and editor in tabs
defaultTab: 'preview', // 'preview' or 'editor' - which tab to show by default
...options
};
this.fileBrowser = null;
this.fileEditor = null;
this.filePreview = null;
this.container = null;
this.callbacks = options.callbacks || {};
}
/**
* Creates and returns the complete file manager UI
* @param {HTMLElement} parentContainer - Container to append the manager to
* @returns {Object} - Object containing references to all components
*/
render(parentContainer) {
// Create main container
this.container = document.createElement('div');
this.container.className = 'generic-file-manager';
this.container.style.cssText = `
display: flex;
flex-direction: ${this.options.layout === 'horizontal' ? 'row' : 'column'};
gap: 15px;
`;
// Create file browser
this.fileBrowser = new GenericFileBrowser(this.configKey, {
onFileSelect: (filename) => this.handleFileSelection(filename),
onFileCreate: () => this.handleFileCreate(),
onFolderCreate: () => this.handleFolderCreate(),
onError: (error) => this.handleError(error, 'Browser'),
...this.callbacks.browser
});
const browserContainer = document.createElement('div');
browserContainer.className = 'file-manager-browser';
if (this.options.layout === 'horizontal') {
browserContainer.style.cssText = `
flex: 0 0 300px;
min-width: 300px;
`;
}
this.fileBrowser.render(browserContainer);
this.container.appendChild(browserContainer);
// Create content container for editor and preview
const contentContainer = document.createElement('div');
contentContainer.className = 'file-manager-content';
contentContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 15px;
flex: 1;
`;
// Create file editor if enabled
if (this.options.showEditor) {
this.fileEditor = new GenericFileEditor(this.configKey, {
onSave: (filename, content) => this.handleFileSave(filename, content),
onCancel: () => this.handleFileCancel(),
onChange: (content) => this.handleContentChange(content),
onLoad: (filename, content) => this.handleFileLoad(filename, content),
onError: (error) => this.handleError(error, 'Editor'),
...this.callbacks.editor
});
}
// Create file preview if enabled
if (this.options.showPreview) {
this.filePreview = new GenericFilePreview(this.configKey, {
onError: (error) => this.handleError(error, 'Preview'),
...this.callbacks.preview
});
}
// Add components to content container
if (this.options.tabbedView && this.options.showEditor && this.options.showPreview) {
// Create tabbed view for editor and preview
this.createTabbedView(contentContainer);
} else {
// Original behavior - separate components
if (this.options.previewFirst) {
// Preview first, then editor
if (this.filePreview) {
const previewContainer = document.createElement('div');
previewContainer.className = 'file-manager-preview';
this.filePreview.render(previewContainer);
contentContainer.appendChild(previewContainer);
}
if (this.fileEditor) {
const editorContainer = document.createElement('div');
editorContainer.className = 'file-manager-editor';
this.fileEditor.render(editorContainer);
contentContainer.appendChild(editorContainer);
}
} else {
// Editor first, then preview (default behavior)
if (this.fileEditor) {
const editorContainer = document.createElement('div');
editorContainer.className = 'file-manager-editor';
this.fileEditor.render(editorContainer);
contentContainer.appendChild(editorContainer);
}
if (this.filePreview) {
const previewContainer = document.createElement('div');
previewContainer.className = 'file-manager-preview';
this.filePreview.render(previewContainer);
contentContainer.appendChild(previewContainer);
}
}
}
this.container.appendChild(contentContainer);
// Add to parent container
if (parentContainer) {
parentContainer.appendChild(this.container);
}
return {
container: this.container,
fileBrowser: this.fileBrowser,
fileEditor: this.fileEditor,
filePreview: this.filePreview
};
}
/**
* Creates a tabbed view containing both editor and preview
* @param {HTMLElement} contentContainer - Container to add the tabbed view to
*/
createTabbedView(contentContainer) {
// Create tab container
const tabbedContainer = document.createElement('div');
tabbedContainer.className = 'file-manager-tabbed';
tabbedContainer.style.cssText = `
display: flex;
flex-direction: column;
flex: 1;
`;
// Create tab header
const tabHeader = document.createElement('div');
tabHeader.className = 'tab-header';
tabHeader.style.cssText = `
display: flex;
border-bottom: 2px solid #4CAF50;
margin-bottom: 10px;
background: #1a1a1a;
`;
// Create tab buttons
const previewTab = document.createElement('button');
previewTab.textContent = 'Preview';
previewTab.className = 'tab-button preview-tab';
previewTab.style.cssText = `
padding: 10px 20px;
border: none;
background: #2a2a2a;
color: #ccc;
cursor: pointer;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
margin-right: 2px;
transition: all 0.2s ease;
`;
const editorTab = document.createElement('button');
editorTab.textContent = 'Editor';
editorTab.className = 'tab-button editor-tab';
editorTab.style.cssText = `
padding: 10px 20px;
border: none;
background: #2a2a2a;
color: #ccc;
cursor: pointer;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
transition: all 0.2s ease;
`;
// Add tabs to header
tabHeader.appendChild(previewTab);
tabHeader.appendChild(editorTab);
// Create content area
const tabContent = document.createElement('div');
tabContent.className = 'tab-content';
tabContent.style.cssText = `
flex: 1;
position: relative;
`;
// Create containers for each tab
const previewContainer = document.createElement('div');
previewContainer.className = 'file-manager-preview tab-panel';
previewContainer.style.cssText = `
display: ${this.options.defaultTab === 'preview' ? 'block' : 'none'};
`;
const editorContainer = document.createElement('div');
editorContainer.className = 'file-manager-editor tab-panel';
editorContainer.style.cssText = `
display: ${this.options.defaultTab === 'editor' ? 'block' : 'none'};
`;
// Render components into their containers
if (this.filePreview) {
this.filePreview.render(previewContainer);
}
if (this.fileEditor) {
this.fileEditor.render(editorContainer);
}
// Add containers to tab content
tabContent.appendChild(previewContainer);
tabContent.appendChild(editorContainer);
// Set up tab switching
const switchToPreview = () => {
previewTab.style.background = '#4CAF50';
previewTab.style.color = 'white';
previewTab.style.fontWeight = 'bold';
editorTab.style.background = '#2a2a2a';
editorTab.style.color = '#ccc';
editorTab.style.fontWeight = 'normal';
previewContainer.style.display = 'block';
editorContainer.style.display = 'none';
};
const switchToEditor = () => {
editorTab.style.background = '#4CAF50';
editorTab.style.color = 'white';
editorTab.style.fontWeight = 'bold';
previewTab.style.background = '#2a2a2a';
previewTab.style.color = '#ccc';
previewTab.style.fontWeight = 'normal';
editorContainer.style.display = 'block';
previewContainer.style.display = 'none';
};
// Add event listeners
previewTab.addEventListener('click', switchToPreview);
editorTab.addEventListener('click', switchToEditor);
// Set initial active tab
if (this.options.defaultTab === 'preview') {
switchToPreview();
} else {
switchToEditor();
}
// Assemble tabbed container
tabbedContainer.appendChild(tabHeader);
tabbedContainer.appendChild(tabContent);
// Add to content container
contentContainer.appendChild(tabbedContainer);
// Store references for external access
this.tabElements = {
tabbedContainer,
tabHeader,
tabContent,
previewTab,
editorTab,
previewContainer,
editorContainer,
switchToPreview,
switchToEditor
};
}
/**
* Handles file selection from the browser
* @param {string} filename - Selected filename
*/
async handleFileSelection(filename) {
try {
// Load file in editor if available
if (this.fileEditor) {
await this.fileEditor.loadFile(filename);
}
// Update preview if available
if (this.filePreview) {
const content = this.fileEditor ? this.fileEditor.getContent() : null;
this.filePreview.updatePreview(filename, content);
}
// Trigger callback
if (this.callbacks.onFileSelect) {
this.callbacks.onFileSelect(filename);
}
} catch (error) {
this.handleError(error, 'File Selection');
}
}
/**
* Handles file creation
*/
handleFileCreate() {
const filename = prompt('Enter filename:');
if (filename) {
console.log('FileManager: Creating new file:', filename);
// Determine the current path from the file browser
let currentPath = '';
if (this.fileBrowser && typeof this.fileBrowser.getCurrentPath === 'function') {
currentPath = this.fileBrowser.getCurrentPath();
console.log('FileManager: Current path from browser:', currentPath);
}
// Handle path differently based on configuration
let fullPath = filename;
if (this.configKey === 'wildcards') {
// Wildcards support subdirectories - use full path
if (currentPath && currentPath !== '' && currentPath !== '/') {
// Ensure no double slashes
if (currentPath.endsWith('/')) {
fullPath = currentPath + filename;
} else {
fullPath = currentPath + '/' + filename;
}
console.log('FileManager: Wildcards - Full path after combining:', fullPath);
} else {
console.log('FileManager: Wildcards - Using filename as-is (no current path):', fullPath);
}
} else {
// Notes only support flat files - ignore current path
console.log('FileManager: Notes - Using filename only (ignoring path):', fullPath);
}
// Check if file already exists
if (this.fileBrowser && this.fileBrowser.files) {
console.log('FileManager: Checking against existing files:', this.fileBrowser.files);
const fileExists = this.fileBrowser.files.some(file => {
// Handle both string filenames and file objects
const existingFilename = typeof file === 'string' ? file : file.name;
return existingFilename === fullPath || existingFilename === filename;
});
if (fileExists) {
console.log('FileManager: File already exists, aborting creation');
alert(`File "${filename}" already exists. Please choose a different name.`);
return;
}
}
console.log('FileManager: Creating new file in editor with path:', fullPath);
// Create new file in editor
if (this.fileEditor) {
this.fileEditor.createNewFile(fullPath);
console.log('FileManager: File created in editor, current file is:', this.fileEditor.getCurrentFile());
// Auto-save the new empty file to actually create it on the server
console.log('FileManager: Auto-saving new file to create it on server');
setTimeout(async () => {
try {
await this.fileEditor.saveFile();
console.log('FileManager: New file auto-saved successfully');
} catch (error) {
console.error('FileManager: Failed to auto-save new file:', error);
}
}, 100); // Small delay to ensure editor is fully set up
}
// Clear preview
if (this.filePreview) {
this.filePreview.clear();
}
// Trigger callback
if (this.callbacks.onFileCreate) {
console.log('FileManager: Triggering onFileCreate callback with:', fullPath);
this.callbacks.onFileCreate(fullPath);
}
}
}
/**
* Handles folder creation
*/
handleFolderCreate() {
const folderName = prompt('Enter folder name:');
if (folderName) {
// Trigger callback
if (this.callbacks.onFolderCreate) {
this.callbacks.onFolderCreate(folderName);
}
}
}
/**
* Handles file save
* @param {string} filename - Saved filename
* @param {string|null} content - File content (null for deletion)
*/
handleFileSave(filename, content) {
console.log('FileManager: Handling file save for:', filename, 'content length:', content ? content.length : 'null');
// Refresh browser to show changes
if (this.fileBrowser) {
console.log('FileManager: Refreshing file browser after save');
this.fileBrowser.refresh();
}
// Update preview if content exists
if (this.filePreview && content !== null) {
console.log('FileManager: Updating preview after save');
this.filePreview.updatePreview(filename, content);
} else if (this.filePreview && content === null) {
// File was deleted, clear preview
console.log('FileManager: Clearing preview after deletion');
this.filePreview.clear();
}
// Trigger callback
if (this.callbacks.onFileSave) {
console.log('FileManager: Triggering onFileSave callback');
this.callbacks.onFileSave(filename, content);
}
}
/**
* Handles file cancel/close
*/
handleFileCancel() {
// Clear editor if available
if (this.fileEditor) {
this.fileEditor.clearEditor();
}
// Clear preview if available
if (this.filePreview) {
this.filePreview.clear();
}
// Trigger callback
if (this.callbacks.onFileCancel) {
this.callbacks.onFileCancel();
}
}
/**
* Handles content change in editor
* @param {string} content - New content
*/
handleContentChange(content) {
// Update preview with new content
if (this.filePreview && this.fileEditor) {
const filename = this.fileEditor.getCurrentFile();
if (filename) {
this.filePreview.updatePreview(filename, content);
}
}
// Trigger callback
if (this.callbacks.onContentChange) {
this.callbacks.onContentChange(content);
}
}
/**
* Handles file load
* @param {string} filename - Loaded filename
* @param {string} content - File content
*/
handleFileLoad(filename, content) {
// Update preview
if (this.filePreview) {
this.filePreview.updatePreview(filename, content);
}
// Trigger callback
if (this.callbacks.onFileLoad) {
this.callbacks.onFileLoad(filename, content);
}
}
/**
* Handles errors from any component
* @param {Error} error - Error object
* @param {string} source - Source component that generated the error
*/
handleError(error, source) {
console.error(`File Manager ${source} Error:`, error);
// Trigger error callback
if (this.callbacks.onError) {
this.callbacks.onError(error, source);
}
}
/**
* Refreshes all components
*/
refresh() {
if (this.fileBrowser) {
this.fileBrowser.refresh();
}
}
/**
* Gets the currently selected file
* @returns {string|null} - Selected filename or null
*/
getSelectedFile() {
if (this.fileBrowser) {
return this.fileBrowser.getSelectedFile();
}
return null;
}
/**
* Gets the current editor content
* @returns {string} - Current content
*/
getContent() {
if (this.fileEditor) {
return this.fileEditor.getContent();
}
return '';
}
/**
* Sets the editor content
* @param {string} content - Content to set
*/
setContent(content) {
if (this.fileEditor) {
this.fileEditor.setContent(content);
}
}
/**
* Checks if current file is modified
* @returns {boolean} - True if modified
*/
isModified() {
if (this.fileEditor) {
return this.fileEditor.getIsModified();
}
return false;
}
/**
* Shows or hides the preview section
* @param {boolean} show - Whether to show the preview
*/
togglePreview(show) {
if (this.filePreview && this.filePreview.container) {
this.filePreview.container.style.display = show ? 'block' : 'none';
}
}
/**
* Shows or hides the editor section
* @param {boolean} show - Whether to show the editor
*/
toggleEditor(show) {
if (this.fileEditor && this.fileEditor.container) {
this.fileEditor.container.style.display = show ? 'block' : 'none';
}
}
/**
* Changes the configuration key and updates all components
* @param {string} newConfigKey - New configuration key (e.g., 'notes', 'wildcards')
*/
changeConfiguration(newConfigKey) {
this.configKey = newConfigKey;
// Update file browser configuration
if (this.fileBrowser) {
this.fileBrowser.changeConfiguration(newConfigKey);
}
// Update file editor configuration
if (this.fileEditor) {
this.fileEditor.changeConfiguration(newConfigKey);
}
// Update file preview configuration
if (this.filePreview) {
this.filePreview.changeConfiguration(newConfigKey);
}
// Trigger callbacks if available
if (this.callbacks.onConfigChange) {
this.callbacks.onConfigChange(newConfigKey);
}
}
/**
* Destroys the file manager and all components
*/
destroy() {
if (this.fileBrowser) {
this.fileBrowser.destroy();
}
if (this.fileEditor) {
this.fileEditor.destroy();
}
if (this.filePreview) {
this.filePreview.destroy();
}
if (this.container && this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
this.fileBrowser = null;
this.fileEditor = null;
this.filePreview = null;
this.container = null;
}
}
export default GenericFileManager;