Download Comfyui-Resolution-Master/js/utils/CustomPresetsManager.js from MarkLilly/mis-custom-nodes: direct link, hf CLI and curl.
- Browser
- Download file 27.4 kB
-
https://huggingface.co/MarkLilly/mis-custom-nodes/resolve/main/Comfyui-Resolution-Master/js/utils/CustomPresetsManager.js
- Command line
-
hf download hf://MarkLilly/mis-custom-nodes/Comfyui-Resolution-Master/js/utils/CustomPresetsManager.js
-
curl -L -o CustomPresetsManager.js https://huggingface.co/MarkLilly/mis-custom-nodes/resolve/main/Comfyui-Resolution-Master/js/utils/CustomPresetsManager.js
27.4 kB
| // CustomPresetsManager.js - Manages custom user presets with persistence | |
| import { createModuleLogger } from "./LoggerUtils.js"; | |
| const log = createModuleLogger('CustomPresetsManager'); | |
| export class CustomPresetsManager { | |
| constructor(resolutionMasterInstance) { | |
| this.rm = resolutionMasterInstance; | |
| this.customPresets = {}; | |
| this.hiddenBuiltInPresets = {}; | |
| this.loadCustomPresets(); | |
| } | |
| /** | |
| * Loads custom presets from node properties | |
| */ | |
| loadCustomPresets() { | |
| try { | |
| const props = this.rm.node.properties; | |
| if (props.customPresetsJSON && typeof props.customPresetsJSON === 'string') { | |
| const data = JSON.parse(props.customPresetsJSON); | |
| // Support both old and new format | |
| if (data.customPresets) { | |
| // New format with hiddenBuiltInPresets | |
| this.customPresets = data.customPresets || {}; | |
| this.hiddenBuiltInPresets = data.hiddenBuiltInPresets || {}; | |
| } else { | |
| // Old format - just custom presets | |
| this.customPresets = data; | |
| this.hiddenBuiltInPresets = {}; | |
| } | |
| // Clean up empty categories | |
| this.cleanEmptyCategories(); | |
| log.debug('Loaded custom presets:', this.customPresets); | |
| log.debug('Loaded hidden built-in presets:', this.hiddenBuiltInPresets); | |
| } else { | |
| this.customPresets = {}; | |
| this.hiddenBuiltInPresets = {}; | |
| log.debug('No custom presets found, initialized empty'); | |
| } | |
| } catch (error) { | |
| log.error('Error loading custom presets:', error); | |
| this.customPresets = {}; | |
| this.hiddenBuiltInPresets = {}; | |
| } | |
| } | |
| /** | |
| * Saves custom presets to node properties | |
| */ | |
| saveCustomPresets() { | |
| try { | |
| // Clean up empty categories before saving | |
| this.cleanEmptyCategories(); | |
| const data = { | |
| customPresets: this.customPresets, | |
| hiddenBuiltInPresets: this.hiddenBuiltInPresets | |
| }; | |
| this.rm.node.properties.customPresetsJSON = JSON.stringify(data); | |
| log.debug('Saved custom presets and hidden built-in presets to node properties'); | |
| } catch (error) { | |
| log.error('Error saving custom presets:', error); | |
| } | |
| } | |
| /** | |
| * Removes empty categories from customPresets | |
| */ | |
| cleanEmptyCategories() { | |
| Object.keys(this.customPresets).forEach(category => { | |
| if (!this.customPresets[category] || | |
| typeof this.customPresets[category] !== 'object' || | |
| Object.keys(this.customPresets[category]).length === 0) { | |
| delete this.customPresets[category]; | |
| log.debug(`Removed empty category: ${category}`); | |
| } | |
| }); | |
| } | |
| /** | |
| * Gets all custom presets | |
| * @returns {Object} Custom presets object | |
| */ | |
| getCustomPresets() { | |
| return this.customPresets; | |
| } | |
| /** | |
| * Gets merged presets (built-in + custom) | |
| * Custom presets OVERRIDE built-in presets with the same name | |
| * Hidden built-in presets are FILTERED OUT | |
| * @param {Object} builtInPresets - Built-in preset categories | |
| * @returns {Object} Merged presets with custom indicator and hidden flag | |
| */ | |
| getMergedPresets(builtInPresets) { | |
| // Start with deep copy of built-in presets | |
| const merged = {}; | |
| // First, copy all built-in presets (including hidden ones, but mark them) | |
| Object.entries(builtInPresets).forEach(([categoryName, presets]) => { | |
| merged[categoryName] = {}; | |
| Object.entries(presets).forEach(([presetName, dimensions]) => { | |
| // Mark as hidden if in hiddenBuiltInPresets list | |
| const isHidden = this.isBuiltInPresetHidden(categoryName, presetName); | |
| merged[categoryName][presetName] = { | |
| width: dimensions.width, | |
| height: dimensions.height, | |
| isCustom: false, | |
| isHidden: isHidden | |
| }; | |
| }); | |
| }); | |
| // Then, add/override with custom presets | |
| Object.entries(this.customPresets).forEach(([categoryName, presets]) => { | |
| if (!merged[categoryName]) { | |
| // New custom category | |
| merged[categoryName] = {}; | |
| } | |
| Object.entries(presets).forEach(([presetName, dimensions]) => { | |
| // This will override built-in preset if it exists | |
| merged[categoryName][presetName] = { | |
| width: dimensions.width, | |
| height: dimensions.height, | |
| isCustom: true, | |
| isHidden: false, | |
| originalCategory: categoryName | |
| }; | |
| }); | |
| }); | |
| return merged; | |
| } | |
| /** | |
| * Adds a new preset or updates existing one | |
| * @param {string} category - Category name | |
| * @param {string} name - Preset name | |
| * @param {number} width - Width value | |
| * @param {number} height - Height value | |
| * @returns {boolean} Success status | |
| */ | |
| addPreset(category, name, width, height) { | |
| try { | |
| if (!this.customPresets[category]) { | |
| this.customPresets[category] = {}; | |
| } | |
| this.customPresets[category][name] = { | |
| width: parseInt(width), | |
| height: parseInt(height) | |
| }; | |
| this.saveCustomPresets(); | |
| log.debug(`Added/Updated preset: ${category}/${name} (${width}x${height})`); | |
| return true; | |
| } catch (error) { | |
| log.error('Error adding preset:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Deletes a preset | |
| * @param {string} category - Category name | |
| * @param {string} name - Preset name | |
| * @returns {boolean} Success status | |
| */ | |
| deletePreset(category, name) { | |
| try { | |
| if (this.customPresets[category] && this.customPresets[category][name]) { | |
| delete this.customPresets[category][name]; | |
| // Remove category if empty | |
| if (Object.keys(this.customPresets[category]).length === 0) { | |
| delete this.customPresets[category]; | |
| } | |
| this.saveCustomPresets(); | |
| log.debug(`Deleted preset: ${category}/${name}`); | |
| return true; | |
| } | |
| return false; | |
| } catch (error) { | |
| log.error('Error deleting preset:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Renames a preset | |
| * @param {string} category - Category name | |
| * @param {string} oldName - Old preset name | |
| * @param {string} newName - New preset name | |
| * @returns {boolean} Success status | |
| */ | |
| renamePreset(category, oldName, newName) { | |
| try { | |
| if (this.customPresets[category] && this.customPresets[category][oldName]) { | |
| const preset = this.customPresets[category][oldName]; | |
| this.customPresets[category][newName] = preset; | |
| delete this.customPresets[category][oldName]; | |
| this.saveCustomPresets(); | |
| log.debug(`Renamed preset: ${category}/${oldName} -> ${newName}`); | |
| return true; | |
| } | |
| return false; | |
| } catch (error) { | |
| log.error('Error renaming preset:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Updates a preset (name and/or dimensions) while preserving its position in the list | |
| * @param {string} category - Category name | |
| * @param {string} oldName - Current preset name | |
| * @param {string} newName - New preset name | |
| * @param {number} width - New width value | |
| * @param {number} height - New height value | |
| * @returns {boolean} Success status | |
| */ | |
| updatePreset(category, oldName, newName, width, height) { | |
| try { | |
| if (!this.customPresets[category] || !this.customPresets[category][oldName]) { | |
| log.warn(`Preset ${category}/${oldName} does not exist`); | |
| return false; | |
| } | |
| // If both name and dimensions are the same, no update needed | |
| const oldPreset = this.customPresets[category][oldName]; | |
| if (oldName === newName && | |
| oldPreset.width === parseInt(width) && | |
| oldPreset.height === parseInt(height)) { | |
| return true; | |
| } | |
| // Check if new name already exists (and it's not the same preset) | |
| if (oldName !== newName && this.customPresets[category][newName]) { | |
| log.warn(`Preset ${category}/${newName} already exists`); | |
| return false; | |
| } | |
| // Convert category presets to array of entries to preserve order | |
| const entries = Object.entries(this.customPresets[category]); | |
| // Find the index of the old preset | |
| const index = entries.findIndex(([name]) => name === oldName); | |
| if (index === -1) { | |
| log.warn(`Preset ${category}/${oldName} not found in entries`); | |
| return false; | |
| } | |
| // Replace the entry with updated preset | |
| entries[index] = [newName, { | |
| width: parseInt(width), | |
| height: parseInt(height) | |
| }]; | |
| // Rebuild the category object from entries (preserves order) | |
| this.customPresets[category] = Object.fromEntries(entries); | |
| this.saveCustomPresets(); | |
| log.debug(`Updated preset: ${category}/${oldName} -> ${newName} (${width}x${height})`); | |
| return true; | |
| } catch (error) { | |
| log.error('Error updating preset:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Renames a category while preserving its position in the list | |
| * @param {string} oldName - Current category name | |
| * @param {string} newName - New category name | |
| * @returns {boolean} Success status | |
| */ | |
| renameCategory(oldName, newName) { | |
| // Validate input | |
| const trimmedNewName = newName.trim(); | |
| if (!trimmedNewName) { | |
| log.warn('New category name cannot be empty'); | |
| return false; | |
| } | |
| // Check if old category exists | |
| if (!this.customPresets[oldName]) { | |
| log.warn(`Category ${oldName} does not exist`); | |
| return false; | |
| } | |
| // Check if new name already exists (and it's not the same category) | |
| if (oldName !== trimmedNewName && this.customPresets[trimmedNewName]) { | |
| log.warn(`Category ${trimmedNewName} already exists`); | |
| return false; | |
| } | |
| // If names are the same (after trim), no change needed | |
| if (oldName === trimmedNewName) { | |
| return true; | |
| } | |
| // Convert customPresets to array of entries to preserve order | |
| const entries = Object.entries(this.customPresets); | |
| // Find the index of the old category | |
| const index = entries.findIndex(([categoryName]) => categoryName === oldName); | |
| if (index === -1) { | |
| log.warn(`Category ${oldName} not found in entries`); | |
| return false; | |
| } | |
| // Replace the entry with new category name (keeping the same presets) | |
| entries[index] = [trimmedNewName, entries[index][1]]; | |
| // Rebuild the customPresets object from entries (preserves order) | |
| this.customPresets = Object.fromEntries(entries); | |
| this.saveCustomPresets(); | |
| log.debug(`Renamed category: ${oldName} -> ${trimmedNewName}`); | |
| return true; | |
| } | |
| /** | |
| * Duplicates a preset (works with both built-in and custom) | |
| * @param {string} sourceCategory - Source category name | |
| * @param {string} sourceName - Source preset name | |
| * @param {string} targetCategory - Target category name | |
| * @param {string} targetName - Target preset name | |
| * @param {Object} builtInPresets - Built-in presets to check | |
| * @returns {boolean} Success status | |
| */ | |
| duplicatePreset(sourceCategory, sourceName, targetCategory, targetName, builtInPresets) { | |
| try { | |
| let sourcePreset = null; | |
| // Check custom presets first | |
| if (this.customPresets[sourceCategory] && this.customPresets[sourceCategory][sourceName]) { | |
| sourcePreset = this.customPresets[sourceCategory][sourceName]; | |
| } | |
| // Check built-in presets | |
| else if (builtInPresets[sourceCategory] && builtInPresets[sourceCategory][sourceName]) { | |
| sourcePreset = builtInPresets[sourceCategory][sourceName]; | |
| } | |
| if (sourcePreset) { | |
| return this.addPreset(targetCategory, targetName, sourcePreset.width, sourcePreset.height); | |
| } | |
| return false; | |
| } catch (error) { | |
| log.error('Error duplicating preset:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Exports custom presets and hidden built-in presets to JSON string | |
| * @returns {string} JSON string of custom presets and hidden built-in presets | |
| */ | |
| exportToJSON() { | |
| try { | |
| const data = { | |
| customPresets: this.customPresets, | |
| hiddenBuiltInPresets: this.hiddenBuiltInPresets | |
| }; | |
| return JSON.stringify(data, null, 2); | |
| } catch (error) { | |
| log.error('Error exporting presets:', error); | |
| return null; | |
| } | |
| } | |
| /** | |
| * Imports presets from JSON string | |
| * Supports both old format (direct presets) and new format (with customPresets and hiddenBuiltInPresets) | |
| * @param {string} jsonString - JSON string to import | |
| * @param {boolean} merge - If true, merge with existing. If false, replace. | |
| * @returns {boolean} Success status | |
| */ | |
| importFromJSON(jsonString, merge = true) { | |
| try { | |
| const imported = JSON.parse(jsonString); | |
| // Validate structure | |
| if (typeof imported !== 'object' || imported === null) { | |
| throw new Error('Invalid preset format'); | |
| } | |
| // Detect format: new format has 'customPresets' field, old format doesn't | |
| let presetsToImport; | |
| let hiddenToImport = {}; | |
| if (imported.customPresets) { | |
| // New format with customPresets and hiddenBuiltInPresets | |
| presetsToImport = imported.customPresets; | |
| hiddenToImport = imported.hiddenBuiltInPresets || {}; | |
| log.debug('Importing new format (with hiddenBuiltInPresets)'); | |
| } else { | |
| // Old format - direct presets object | |
| presetsToImport = imported; | |
| log.debug('Importing old format (without hiddenBuiltInPresets)'); | |
| } | |
| // Validate each preset | |
| for (const [category, presets] of Object.entries(presetsToImport)) { | |
| if (typeof presets !== 'object') { | |
| throw new Error(`Invalid category format: ${category}`); | |
| } | |
| for (const [name, dimensions] of Object.entries(presets)) { | |
| if (!dimensions.width || !dimensions.height || | |
| typeof dimensions.width !== 'number' || | |
| typeof dimensions.height !== 'number') { | |
| throw new Error(`Invalid preset dimensions: ${category}/${name}`); | |
| } | |
| } | |
| } | |
| if (merge) { | |
| // Merge with existing presets | |
| Object.entries(presetsToImport).forEach(([category, presets]) => { | |
| if (!this.customPresets[category]) { | |
| this.customPresets[category] = {}; | |
| } | |
| Object.assign(this.customPresets[category], presets); | |
| }); | |
| // Merge hidden built-in presets | |
| Object.entries(hiddenToImport).forEach(([category, hiddenPresets]) => { | |
| if (!this.hiddenBuiltInPresets[category]) { | |
| this.hiddenBuiltInPresets[category] = []; | |
| } | |
| // Merge arrays, avoiding duplicates | |
| hiddenPresets.forEach(presetName => { | |
| if (!this.hiddenBuiltInPresets[category].includes(presetName)) { | |
| this.hiddenBuiltInPresets[category].push(presetName); | |
| } | |
| }); | |
| }); | |
| } else { | |
| // Replace all presets | |
| this.customPresets = presetsToImport; | |
| this.hiddenBuiltInPresets = hiddenToImport; | |
| } | |
| this.saveCustomPresets(); | |
| log.debug('Imported presets successfully', merge ? '(merged)' : '(replaced)'); | |
| return true; | |
| } catch (error) { | |
| log.error('Error importing presets:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Clears all custom presets | |
| * @returns {boolean} Success status | |
| */ | |
| clearAllPresets() { | |
| try { | |
| this.customPresets = {}; | |
| this.saveCustomPresets(); | |
| log.debug('Cleared all custom presets'); | |
| return true; | |
| } catch (error) { | |
| log.error('Error clearing presets:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Gets preset count statistics | |
| * @returns {Object} Statistics object | |
| */ | |
| getStatistics() { | |
| let totalCategories = 0; | |
| let totalPresets = 0; | |
| Object.entries(this.customPresets).forEach(([category, presets]) => { | |
| totalCategories++; | |
| totalPresets += Object.keys(presets).length; | |
| }); | |
| return { | |
| categories: totalCategories, | |
| presets: totalPresets | |
| }; | |
| } | |
| /** | |
| * Checks if a preset name exists in a category | |
| * @param {string} category - Category name | |
| * @param {string} name - Preset name | |
| * @returns {boolean} True if exists | |
| */ | |
| presetExists(category, name) { | |
| return !!(this.customPresets[category] && this.customPresets[category][name]); | |
| } | |
| /** | |
| * Checks if a category exists | |
| * @param {string} category - Category name | |
| * @returns {boolean} True if exists | |
| */ | |
| categoryExists(category) { | |
| return !!this.customPresets[category]; | |
| } | |
| /** | |
| * Checks if a preset is a custom preset (vs built-in) | |
| * @param {string} category - Category name | |
| * @param {string} name - Preset name | |
| * @returns {boolean} True if custom preset | |
| */ | |
| isCustomPreset(category, name) { | |
| return this.presetExists(category, name); | |
| } | |
| /** | |
| * Reorders categories by moving one category to a new position | |
| * @param {string} categoryName - Category to move | |
| * @param {number} newIndex - New position index | |
| * @returns {boolean} Success status | |
| */ | |
| reorderCategories(categoryName, newIndex) { | |
| try { | |
| // Convert to array of entries | |
| const entries = Object.entries(this.customPresets); | |
| // Find current index | |
| const currentIndex = entries.findIndex(([name]) => name === categoryName); | |
| if (currentIndex === -1) { | |
| log.warn(`Category ${categoryName} not found`); | |
| return false; | |
| } | |
| // Remove from current position | |
| const [movedEntry] = entries.splice(currentIndex, 1); | |
| // Insert at new position | |
| entries.splice(newIndex, 0, movedEntry); | |
| // Rebuild object with new order | |
| this.customPresets = Object.fromEntries(entries); | |
| this.saveCustomPresets(); | |
| log.debug(`Reordered category ${categoryName}: ${currentIndex} -> ${newIndex}`); | |
| return true; | |
| } catch (error) { | |
| log.error('Error reordering categories:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Reorders presets within a category | |
| * @param {string} category - Category name | |
| * @param {string} presetName - Preset to move | |
| * @param {number} newIndex - New position index | |
| * @returns {boolean} Success status | |
| */ | |
| reorderPresets(category, presetName, newIndex) { | |
| try { | |
| if (!this.customPresets[category]) { | |
| log.warn(`Category ${category} not found`); | |
| return false; | |
| } | |
| // Convert to array of entries | |
| const entries = Object.entries(this.customPresets[category]); | |
| // Find current index | |
| const currentIndex = entries.findIndex(([name]) => name === presetName); | |
| if (currentIndex === -1) { | |
| log.warn(`Preset ${presetName} not found in category ${category}`); | |
| return false; | |
| } | |
| // Remove from current position | |
| const [movedEntry] = entries.splice(currentIndex, 1); | |
| // Insert at new position | |
| entries.splice(newIndex, 0, movedEntry); | |
| // Rebuild category with new order | |
| this.customPresets[category] = Object.fromEntries(entries); | |
| this.saveCustomPresets(); | |
| log.debug(`Reordered preset ${category}/${presetName}: ${currentIndex} -> ${newIndex}`); | |
| return true; | |
| } catch (error) { | |
| log.error('Error reordering presets:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Moves a preset from one category to another | |
| * @param {string} sourceCategory - Source category name | |
| * @param {string} presetName - Preset name to move | |
| * @param {string} targetCategory - Target category name | |
| * @param {number} targetIndex - Index in target category (optional, defaults to end) | |
| * @returns {boolean} Success status | |
| */ | |
| movePreset(sourceCategory, presetName, targetCategory, targetIndex = -1) { | |
| try { | |
| // Check if source preset exists | |
| if (!this.customPresets[sourceCategory] || !this.customPresets[sourceCategory][presetName]) { | |
| log.warn(`Source preset ${sourceCategory}/${presetName} not found`); | |
| return false; | |
| } | |
| // Get the preset data | |
| const presetData = this.customPresets[sourceCategory][presetName]; | |
| // Create target category if it doesn't exist | |
| if (!this.customPresets[targetCategory]) { | |
| this.customPresets[targetCategory] = {}; | |
| } | |
| // Check if preset with same name already exists in target | |
| if (this.customPresets[targetCategory][presetName]) { | |
| log.warn(`Preset ${presetName} already exists in category ${targetCategory}`); | |
| return false; | |
| } | |
| // Convert target category to array for insertion at specific index | |
| const targetEntries = Object.entries(this.customPresets[targetCategory]); | |
| // Insert at specified index (or end if -1) | |
| if (targetIndex === -1 || targetIndex >= targetEntries.length) { | |
| targetEntries.push([presetName, presetData]); | |
| } else { | |
| targetEntries.splice(targetIndex, 0, [presetName, presetData]); | |
| } | |
| // Rebuild target category | |
| this.customPresets[targetCategory] = Object.fromEntries(targetEntries); | |
| // Remove from source category | |
| delete this.customPresets[sourceCategory][presetName]; | |
| // Remove source category if empty | |
| if (Object.keys(this.customPresets[sourceCategory]).length === 0) { | |
| delete this.customPresets[sourceCategory]; | |
| } | |
| this.saveCustomPresets(); | |
| log.debug(`Moved preset ${presetName} from ${sourceCategory} to ${targetCategory} at index ${targetIndex}`); | |
| return true; | |
| } catch (error) { | |
| log.error('Error moving preset:', error); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Gets all hidden built-in presets | |
| * @returns {Object} Hidden built-in presets object | |
| */ | |
| getHiddenBuiltInPresets() { | |
| return this.hiddenBuiltInPresets; | |
| } | |
| /** | |
| * Checks if a built-in preset is hidden | |
| * @param {string} category - Category name | |
| * @param {string} name - Preset name | |
| * @returns {boolean} True if hidden | |
| */ | |
| isBuiltInPresetHidden(category, name) { | |
| return !!(this.hiddenBuiltInPresets[category] && | |
| this.hiddenBuiltInPresets[category].includes(name)); | |
| } | |
| /** | |
| * Toggles visibility of a built-in preset (hide/unhide) | |
| * @param {string} category - Category name | |
| * @param {string} name - Preset name | |
| * @returns {boolean} New visibility state (true = now hidden, false = now visible) | |
| */ | |
| toggleBuiltInPresetVisibility(category, name) { | |
| try { | |
| if (!this.hiddenBuiltInPresets[category]) { | |
| this.hiddenBuiltInPresets[category] = []; | |
| } | |
| const index = this.hiddenBuiltInPresets[category].indexOf(name); | |
| if (index === -1) { | |
| // Not hidden yet - hide it | |
| this.hiddenBuiltInPresets[category].push(name); | |
| log.debug(`Hidden built-in preset: ${category}/${name}`); | |
| this.saveCustomPresets(); | |
| return true; // Now hidden | |
| } else { | |
| // Already hidden - unhide it | |
| this.hiddenBuiltInPresets[category].splice(index, 1); | |
| // Remove category if empty | |
| if (this.hiddenBuiltInPresets[category].length === 0) { | |
| delete this.hiddenBuiltInPresets[category]; | |
| } | |
| log.debug(`Unhidden built-in preset: ${category}/${name}`); | |
| this.saveCustomPresets(); | |
| return false; // Now visible | |
| } | |
| } catch (error) { | |
| log.error('Error toggling built-in preset visibility:', error); | |
| return this.isBuiltInPresetHidden(category, name); | |
| } | |
| } | |
| } | |