mis-custom-nodes / Comfyui-Resolution-Master /js /utils /CustomPresetsManager.js
MarkLilly's picture
Add files using upload-large-folder tool
70ceb8b verified
Raw History Blame Contribute Delete
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);
}
}
}