diff --git a/js/utils/ResolutionMasterConfig.js b/js/utils/ResolutionMasterConfig.js index 33a89d3..e69de29 100644 --- a/js/utils/ResolutionMasterConfig.js +++ b/js/utils/ResolutionMasterConfig.js @@ -1,283 +0,0 @@ -// Configuration file for ResolutionMaster -// Contains tooltips and preset categories to reduce main file size - -export const tooltips = { - // Primary controls (excluding sliders and 2D canvas) - swapBtn: "Swap width and height values", - snapBtn: "Snap current resolution to the nearest snap value", - snapValueArea: "Click to set custom snap value", - - // Output value areas (editable) - widthValueArea: "Click to set custom width value", - heightValueArea: "Click to set custom height value", - batchSizeValueArea: "Click to set custom batch size value", - - // Scaling controls (buttons and dropdowns only) - scaleBtn: "Apply manual scaling factor and reset to 1.0x", - upscaleRadio: "Use manual scaling mode for rescale output", - scaleValueArea: "Click to set custom scale value (e.g., 2.5x)", - - resolutionBtn: "Scale to target resolution (e.g., 1080p)", - resolutionDropdown: "Select target resolution for scaling", - resolutionRadio: "Use resolution-based scaling for rescale output", - resolutionValueArea: "Click to set custom resolution scale factor", - - megapixelsBtn: "Scale to target megapixel count", - megapixelsRadio: "Use megapixel-based scaling for rescale output", - megapixelsValueArea: "Click to set custom megapixel value (e.g., 3.5MP)", - - // Auto-detect controls - autoDetectToggle: "Automatically detect resolution from connected image input", - autoFitBtn: "Find and apply best preset match for current resolution", - autoFitCheckbox: "Automatically find and apply the best preset for the new detected image resolution", - autoResizeBtn: "Apply scaling based on selected mode (Manual/Resolution/Megapixels)", - autoResizeCheckbox: "Automatically apply scaling when new image is detected", - detectedInfo: "Click to apply detected image resolution directly", - - // Preset controls - categoryDropdown: "Select preset category (Standard, SDXL, Flux, HiDream Dev, Qwen-Image, etc.)", - presetDropdown: "Choose specific preset from selected category", - customCalcCheckbox: "Automatically apply model-specific optimizations for the new detected image resolution (read orange information below)", - autoCalcBtn: "Apply model-specific optimizations for current resolution (read orange information below)", - - // Section headers - actionsHeader: "Click to collapse/expand Actions section", - scalingHeader: "Click to collapse/expand Scaling section", - autoDetectHeader: "Click to collapse/expand Auto-Detect section", - presetsHeader: "Click to collapse/expand Presets section" -}; - -// Tooltips for Preset Manager Dialog -export const presetManagerTooltips = { - // Footer buttons - 'add-preset-btn': 'Add a new custom preset to manage your resolution presets', - 'delete-selected-btn': 'Delete all selected presets at once (use Shift+Click to select multiple)', - 'import-btn': 'Import custom presets from a JSON file', - 'export-btn': 'Export your custom presets to a JSON file for backup or sharing', - 'edit-json-btn': 'Edit presets directly in JSON format with syntax highlighting', - 'close-btn': 'Close the Preset Manager dialog', - 'back-btn': 'Return to the preset list view', - - // Add/Edit view - 'category-select-btn': 'Select or create a category for your presets', - 'resolution-master-preset-add-rename-category-btn': 'Rename this category', - 'quick-add-button': 'Add or save this preset to the selected category', - - // List view - 'manage-presets-btn': 'Open the preset manager to add, edit, or delete custom presets', - 'resolution-master-preset-list-edit-btn': 'Edit this item', - 'resolution-master-preset-list-delete-btn': 'Delete this custom preset', - 'resolution-master-preset-toggle-btn': 'Toggle visibility of this built-in preset', - 'resolution-master-preset-list-edit-category-btn': 'Edit this category', - 'resolution-master-preset-list-category-header': 'Drag to reorder categories', - 'resolution-master-preset-list-category-name': 'Double-click to rename', - 'resolution-master-preset-list-name': 'Double-click to rename this preset', - 'resolution-master-preset-list-checkbox': 'Select for bulk deletion (use Shift+Click to select a range)', - 'resolution-master-preset-list-clone-handle': 'Drag to duplicate this preset', - 'resolution-master-aspect-ratio-preset-action-btn': { - 'delete': 'Delete this custom preset', - 'hide': 'Hide this built-in preset from the main selector', - 'unhide': 'Show this hidden preset in the main selector' - }, - - // JSON Editor Dialog - 'json-editor-close-btn': 'Close JSON editor without saving changes', - 'json-editor-format-btn': 'Auto-format JSON with proper indentation (Ctrl+Shift+F)', - 'json-editor-cancel-btn': 'Close editor and discard any changes', - 'json-editor-apply-btn': 'Save changes and update presets configuration' -}; - -export const presetCategories = { - 'Standard': { - '1:1 Square': { width: 512, height: 512 }, - '1:2 Tall': { width: 512, height: 1024 }, - '1:3 Ultra Tall': { width: 512, height: 1536 }, - '2:3 Portrait': { width: 512, height: 768 }, - '3:4 Portrait': { width: 576, height: 768 }, - '4:5 Portrait': { width: 512, height: 640 }, - '4:7 Phone': { width: 512, height: 896 }, - '5:12 Banner': { width: 512, height: 1228 }, - '7:9 Vertical': { width: 512, height: 658 }, - '9:16 Mobile': { width: 576, height: 1024 }, - '9:21 Ultra Mobile': { width: 512, height: 1194 }, - '10:16 Monitor': { width: 640, height: 1024 }, - '13:19 Tall Screen': { width: 512, height: 748 }, - '3:2 Landscape': { width: 768, height: 512 }, - '4:3 Classic': { width: 512, height: 384 }, - '16:9 Widescreen': { width: 768, height: 432 }, - '21:9 Ultrawide': { width: 1024, height: 439 } - }, - 'SDXL': { - '1:1 Square': { width: 1024, height: 1024 }, - '3:4 Portrait': { width: 768, height: 1024 }, - '4:5 Portrait': { width: 915, height: 1144 }, - '5:12 Portrait': { width: 640, height: 1536 }, - '7:9 Portrait': { width: 896, height: 1152 }, - '9:16 Portrait': { width: 768, height: 1344 }, - '13:19 Portrait': { width: 832, height: 1216 }, - '3:2 Landscape': { width: 1254, height: 836 } - }, - 'Flux': { - '1:1 Square': { width: 1024, height: 1024 }, - '2:3 Portrait': { width: 832, height: 1248 }, - '3:4 Portrait': { width: 896, height: 1184 }, - '4:5 Portrait': { width: 928, height: 1152 }, - '9:16 Portrait': { width: 768, height: 1344 }, - '9:21 Portrait': { width: 672, height: 1440 }, - }, - 'WAN': { - // Community Presets - '16:9 Landscape-1280': { width: 1280, height: 720 }, - '16:9 Landscape-832': { width: 832, height: 480 }, - '1:1 Square-512': { width: 512, height: 512 }, - '1:1 Square-768': { width: 768, height: 768 }, - // Original Presets - '1:1 Square-720': { width: 720, height: 720 }, - '2:3 Portrait': { width: 588, height: 882 }, - '3:4 Portrait': { width: 624, height: 832 }, - '9:21 Portrait': { width: 549, height: 1280 }, - '3:2 Landscape': { width: 1080, height: 720 }, - '4:3 Landscape': { width: 960, height: 720 }, - '21:9 Landscape': { width: 1680, height: 720 } - }, - 'HiDream Dev': { - '1:1 Square-1024': { width: 1024, height: 1024 }, - '1:1 Square-1280': { width: 1280, height: 1280 }, - '1:1 Square-1536': { width: 1536, height: 1536 }, - '16:9 Landscape': { width: 1360, height: 768 }, - '3:2 Landscape': { width: 1248, height: 832 }, - '4:3 Landscape': { width: 1168, height: 880 }, - }, - 'Qwen-Image': { - '1:1 Square (Default)': { width: 1328, height: 1328 }, - '16:9 Landscape': { width: 1664, height: 928 }, - '4:3 Landscape': { width: 1472, height: 1140 }, - '3:2 Landscape': { width: 1584, height: 1056 }, - // Tests Presets - '1:1 Square-1024': { width: 1024, height: 1024 }, - '4:3 Landscape': { width: 768, height: 1024 } - }, - 'Social Media': { - // Instagram - 'Instagram Square': { width: 1080, height: 1080 }, - 'Instagram Portrait': { width: 1080, height: 1350 }, - 'Instagram Landscape': { width: 1080, height: 566 }, - 'Instagram Stories/Reels': { width: 1080, height: 1920 }, - 'Instagram Profile': { width: 320, height: 320 }, - - // Facebook - 'Facebook Post': { width: 1200, height: 630 }, - 'Facebook Cover Page': { width: 820, height: 312 }, - 'Facebook Cover Event': { width: 1920, height: 1005 }, - 'Facebook Personal Cover': { width: 1200, height: 445 }, - 'Facebook Profile': { width: 180, height: 180 }, - 'Facebook Stories': { width: 1080, height: 1920 }, - - // X (Twitter) - 'Twitter Post': { width: 1200, height: 675 }, - 'Twitter Header': { width: 1500, height: 500 }, - 'Twitter Profile': { width: 400, height: 400 }, - - // YouTube - 'YouTube Thumbnail': { width: 1280, height: 720 }, - 'YouTube Banner': { width: 2560, height: 1440 }, - 'YouTube Channel Icon': { width: 800, height: 800 }, - 'YouTube Shorts': { width: 1080, height: 1920 }, - - // LinkedIn - 'LinkedIn Post': { width: 1200, height: 627 }, - 'LinkedIn Cover Profile': { width: 1584, height: 396 }, - 'LinkedIn Company Logo': { width: 300, height: 300 }, - 'LinkedIn Company Background': { width: 1128, height: 191 }, - - // TikTok - 'TikTok Video': { width: 1080, height: 1920 }, - 'TikTok Profile': { width: 200, height: 200 }, - - // Pinterest - 'Pinterest Standard Pin': { width: 1000, height: 1500 }, - 'Pinterest Max Pin': { width: 1000, height: 2100 }, - 'Pinterest Profile': { width: 165, height: 165 }, - 'Pinterest Board Cover': { width: 222, height: 150 }, - - // Snapchat - 'Snapchat Story/Ads': { width: 1080, height: 1920 }, - 'Snapchat Profile': { width: 1080, height: 1080 } - }, - 'Print': { - // ISO Standards (Europe, World) - 'A3 Portrait': { width: 3508, height: 4961 }, - 'A4 Portrait': { width: 2480, height: 3508 }, - 'A4 Landscape': { width: 3508, height: 2480 }, - 'A5 Portrait': { width: 1748, height: 2480 }, - 'A6 Portrait': { width: 1240, height: 1748 }, - 'Business Card EU': { width: 1004, height: 590 }, - - // North American Standards - 'Letter Portrait': { width: 2550, height: 3300 }, - 'Legal Portrait': { width: 2550, height: 4200 }, - 'Tabloid': { width: 3300, height: 5100 }, - - // Photo Print Standards - '4x6 Photo': { width: 1200, height: 1800 }, - '5x7 Photo': { width: 1500, height: 2100 }, - '8x10 Photo': { width: 2400, height: 3000 }, - '11x14 Photo': { width: 3300, height: 4200 }, - '16x20 Photo': { width: 4800, height: 6000 }, - '20x24 Photo': { width: 6000, height: 7200 } - }, - 'Cinema': { - // DCI Standards - 'DCI 2K Flat': { width: 1998, height: 1080 }, - 'DCI 2K Scope': { width: 2048, height: 858 }, // professional DCI anamorphic - 'DCI 4K Flat': { width: 3996, height: 2160 }, - 'DCI 4K Scope': { width: 4096, height: 1716 }, - 'DCI Full 2K': { width: 2048, height: 1080 }, - 'DCI Full 4K': { width: 4096, height: 2160 }, - - // IMAX Formats - 'IMAX Digital': { width: 4096, height: 3020 }, // example resolution, varies by source - 'IMAX 1.90:1': { width: 4096, height: 2160 }, - - // Classic Cinema Formats (approximate digital equivalents) - 'Ultra Panavision 70': { width: 7680, height: 2782 }, // approximate digital equivalent - 'Cinerama': { width: 7680, height: 2965 }, // approximate digital equivalent - 'Academy 1.375:1': { width: 1378, height: 1000 }, // simplified variant - 'Academy Original': { width: 1474, height: 1072 }, // closer to original - 'Silent Film 1.33:1': { width: 1440, height: 1080 }, // modern transfer standard - 'Silent Film Classic': { width: 1334, height: 1000 }, // classic variant - - // Legacy Formats - '2.39:1 Anamorphic': { width: 2048, height: 858 }, // general anamorphic (same as DCI Scope) - '1.85:1 Standard': { width: 1998, height: 1080 }, - '2:1 Univisium': { width: 2048, height: 1024 }, - '4:3 Academy': { width: 1440, height: 1080 }, - '1.33:1 Classic': { width: 1436, height: 1080 } - }, - 'Display Resolutions': { - 'CIF': { width: 352, height: 288 }, - 'SVGA': { width: 800, height: 600 }, - 'XGA': { width: 1024, height: 768 }, - 'SXGA': { width: 1280, height: 1024 }, - 'WXGA': { width: 1366, height: 768 }, - 'WSXGA+': { width: 1680, height: 1050 }, - '240p': { width: 426, height: 240 }, - '360p': { width: 640, height: 360 }, - '480p SD': { width: 854, height: 480 }, - '540p qHD': { width: 960, height: 540 }, - '720p HD': { width: 1280, height: 720 }, - '900p HD+': { width: 1600, height: 900 }, - '1080p Full HD': { width: 1920, height: 1080 }, - 'UWFHD': { width: 2560, height: 1080 }, - '1200p WUXGA': { width: 1920, height: 1200 }, - '1440p QHD': { width: 2560, height: 1440 }, - 'UWQHD': { width: 3440, height: 1440 }, - '1600p UXGA': { width: 2560, height: 1600 }, - '1800p QHD+': { width: 3200, height: 1800 }, - '4K UHD': { width: 3840, height: 2160 }, - 'UW4K (5K2K)': { width: 5120, height: 2160 }, - '5K': { width: 5120, height: 2880 }, - '6K': { width: 6016, height: 3384 }, - '8K UHD': { width: 7680, height: 4320 } - } -}; diff --git a/js/utils/ResourceManager.js b/js/utils/ResourceManager.js index a652d36..e69de29 100644 --- a/js/utils/ResourceManager.js +++ b/js/utils/ResourceManager.js @@ -1,51 +0,0 @@ -// @ts-ignore -import { $el } from "../../../scripts/ui.js"; -import { createModuleLogger } from "./LoggerUtils.js"; -import { withErrorHandling, createValidationError, createNetworkError } from "../ErrorHandler.js"; -const log = createModuleLogger('ResourceManager'); -export const addStylesheet = withErrorHandling(function (url) { - if (!url) { - throw createValidationError("URL is required", { url }); - } - log.debug('Adding stylesheet:', { url }); - if (url.endsWith(".js")) { - url = url.substr(0, url.length - 2) + "css"; - } - $el("link", { - parent: document.head, - rel: "stylesheet", - type: "text/css", - href: url.startsWith("http") ? url : getUrl(url), - }); - log.debug('Stylesheet added successfully:', { finalUrl: url }); -}, 'addStylesheet'); -export function getUrl(path, baseUrl) { - if (!path) { - throw createValidationError("Path is required", { path }); - } - if (baseUrl) { - return new URL(path, baseUrl).toString(); - } - else { - // @ts-ignore - return new URL("../" + path, import.meta.url).toString(); - } -} -export const loadTemplate = withErrorHandling(async function (path, baseUrl) { - if (!path) { - throw createValidationError("Path is required", { path }); - } - const url = getUrl(path, baseUrl); - log.debug('Loading template:', { path, url }); - const response = await fetch(url); - if (!response.ok) { - throw createNetworkError(`Failed to load template: ${url}`, { - url, - status: response.status, - statusText: response.statusText - }); - } - const content = await response.text(); - log.debug('Template loaded successfully:', { path, contentLength: content.length }); - return content; -}, 'loadTemplate'); diff --git a/js/utils/preset-manager/DragDropHandler.js b/js/utils/preset-manager/DragDropHandler.js deleted file mode 100644 index 96161a3..0000000 --- a/js/utils/preset-manager/DragDropHandler.js +++ /dev/null @@ -1,99 +0,0 @@ -// DragDropHandler.js - Drag & Drop handling for PresetManagerDialog - -/** - * Helper class for handling drag & drop operations - */ -export class DragDropHandler { - /** - * Sets a drop indicator line at the top of an element - * @param {HTMLElement} element - The element to show the indicator on - * @param {string} color - Color of the indicator (default: #5af for reorder, #fa0 for move, #f00 for error, #0f0 for clone) - */ - static setDropIndicatorTop(element, color = '#5af') { - if (!element) return; - - // Remove all indicator classes first - element.classList.remove('resolution-master-drag-drop-indicator-top', 'resolution-master-drag-drop-indicator-top-move', 'resolution-master-drag-drop-indicator-top-error', 'resolution-master-drag-drop-indicator-top-clone'); - - // Add appropriate class based on color - if (color === '#fa0') { - element.classList.add('resolution-master-drag-drop-indicator-top-move'); - } else if (color === '#f00') { - element.classList.add('resolution-master-drag-drop-indicator-top-error'); - } else if (color === '#0f0') { - element.classList.add('resolution-master-drag-drop-indicator-top-clone'); - } else { - element.classList.add('resolution-master-drag-drop-indicator-top'); - } - } - - /** - * Sets a drop indicator line at the bottom of an element - * @param {HTMLElement} element - The element to show the indicator on - * @param {string} color - Color of the indicator (default: #5af for reorder, #fa0 for move, #f00 for error, #0f0 for clone) - */ - static setDropIndicatorBottom(element, color = '#5af') { - if (!element) return; - - // Remove all indicator classes first - element.classList.remove('resolution-master-drag-drop-indicator-bottom', 'resolution-master-drag-drop-indicator-bottom-move', 'resolution-master-drag-drop-indicator-bottom-error', 'resolution-master-drag-drop-indicator-bottom-clone'); - - // Add appropriate class based on color - if (color === '#fa0') { - element.classList.add('resolution-master-drag-drop-indicator-bottom-move'); - } else if (color === '#f00') { - element.classList.add('resolution-master-drag-drop-indicator-bottom-error'); - } else if (color === '#0f0') { - element.classList.add('resolution-master-drag-drop-indicator-bottom-clone'); - } else { - element.classList.add('resolution-master-drag-drop-indicator-bottom'); - } - } - - /** - * Clears the drop indicator from an element - * @param {HTMLElement} element - The element to clear the indicator from - */ - static clearDropIndicator(element) { - if (!element) return; - - // Remove all indicator classes - element.classList.remove( - 'resolution-master-drag-drop-indicator-top', - 'resolution-master-drag-drop-indicator-top-move', - 'resolution-master-drag-drop-indicator-top-error', - 'resolution-master-drag-drop-indicator-top-clone', - 'resolution-master-drag-drop-indicator-bottom', - 'resolution-master-drag-drop-indicator-bottom-move', - 'resolution-master-drag-drop-indicator-bottom-error', - 'resolution-master-drag-drop-indicator-bottom-clone' - ); - } - - /** - * Gets the first category header element - * @param {HTMLElement} container - The container to search in - * @returns {HTMLElement|null} The first category header or null - */ - static getFirstCategoryHeader(container) { - const firstCategorySection = container.querySelector('[data-category-index="0"]'); - if (firstCategorySection) { - return firstCategorySection.querySelector('div[draggable="true"]'); - } - return null; - } - - /** - * Gets the next category header element - * @param {HTMLElement} container - The container to search in - * @param {number} currentIndex - The current category index - * @returns {HTMLElement|null} The next category header or null - */ - static getNextCategoryHeader(container, currentIndex) { - const nextCategorySection = container.querySelector(`[data-category-index="${currentIndex + 1}"]`); - if (nextCategorySection) { - return nextCategorySection.querySelector('div[draggable="true"]'); - } - return null; - } -} diff --git a/js/utils/preset-manager/JSONEditorDialog.js b/js/utils/preset-manager/JSONEditorDialog.js deleted file mode 100644 index 9dd8c32..0000000 --- a/js/utils/preset-manager/JSONEditorDialog.js +++ /dev/null @@ -1,369 +0,0 @@ -// JSONEditorDialog.js - JSON editor using JSONEditor library - -import { TooltipManager } from './TooltipManager.js'; -import { presetManagerTooltips } from '../ResolutionMasterConfig.js'; - -/** - * JSON Editor Dialog with JSONEditor library - */ -export class JSONEditorDialog { - constructor(parentDialog) { - this.parentDialog = parentDialog; - this.editor = null; - } - - /** - * Shows JSON editor dialog for direct JSON editing - */ - async show() { - // Get current JSON - const currentJSON = this.parentDialog.manager.exportToJSON(); - - // Create overlay - const overlay = document.createElement('div'); - overlay.className = 'resolution-master-json-editor-overlay'; - - // Create dialog container - const dialog = document.createElement('div'); - dialog.className = 'resolution-master-json-editor-dialog'; - - // Create tooltip manager - const tooltipManager = new TooltipManager({ - delay: 500, - maxWidth: 300 - }); - - // Register tooltips - tooltipManager.registerTooltips(presetManagerTooltips); - - // Header - const header = this.createHeader(() => { - // Cleanup editor and tooltips before closing - if (this.editor) { - this.editor.destroy(); - this.editor = null; - } - tooltipManager.destroy(); - document.body.removeChild(overlay); - document.body.removeChild(dialog); - }); - dialog.appendChild(header); - - // Info message - const infoDiv = this.createInfoMessage(); - dialog.appendChild(infoDiv); - - // Content area with JSONEditor - const content = document.createElement('div'); - content.className = 'resolution-master-json-editor-content'; - - // Load JSONEditor CSS dynamically - if (!document.getElementById('jsoneditor-css-link')) { - const jsoneditorCSS = document.createElement('link'); - jsoneditorCSS.id = 'jsoneditor-css-link'; - jsoneditorCSS.rel = 'stylesheet'; - jsoneditorCSS.href = new URL('../../lib/jsoneditor.min.css', import.meta.url).href; - document.head.appendChild(jsoneditorCSS); - } - - // Load JSONEditor VS Dark+ Theme - if (!document.getElementById('jsoneditor-dark-theme-link')) { - const jsoneditorDarkTheme = document.createElement('link'); - jsoneditorDarkTheme.id = 'jsoneditor-dark-theme-link'; - jsoneditorDarkTheme.rel = 'stylesheet'; - jsoneditorDarkTheme.href = new URL('../../lib/jsoneditor.theme_twilight.css', import.meta.url).href; - document.head.appendChild(jsoneditorDarkTheme); - } - - // Load JSONEditor JS dynamically - if (!window.JSONEditor) { - await this.loadJSONEditorScript(); - } - - // Editor container - const editorContainer = document.createElement('div'); - editorContainer.id = 'json-editor-container'; - editorContainer.className = 'resolution-master-json-editor-container'; - // Height and width are controlled by CSS flex properties - - // Validation message - const validationMsg = document.createElement('div'); - validationMsg.className = 'resolution-master-json-editor-validation'; - validationMsg.style.color = '#5f5'; - validationMsg.textContent = '✓ Valid JSON'; - - // Initialize JSONEditor with full functionality - const options = { - mode: 'code', - modes: ['code', 'tree', 'form', 'text', 'view', 'preview'], // All available modes - enableSort: true, - enableTransform: true, - onError: (err) => { - validationMsg.style.color = '#f55'; - validationMsg.textContent = `❌ ${err.toString()}`; - }, - onChangeText: (jsonText) => { - try { - JSON.parse(jsonText); - validationMsg.style.color = '#5f5'; - validationMsg.textContent = '✓ Valid JSON'; - } catch (e) { - validationMsg.style.color = '#f55'; - validationMsg.textContent = `❌ ${e.message}`; - } - } - }; - - this.editor = new JSONEditor(editorContainer, options); - - // Set the initial JSON content - try { - this.editor.set(JSON.parse(currentJSON)); - } catch (e) { - // If parsing fails, set as text - this.editor.setText(currentJSON); - } - - // Add drag-and-drop functionality for JSON files - this.setupDragAndDrop(editorContainer, validationMsg); - - content.appendChild(editorContainer); - dialog.appendChild(content); - dialog.appendChild(validationMsg); - - // Footer with buttons - const footer = this.createFooter(validationMsg, () => { - // Cleanup editor and tooltips before closing - if (this.editor) { - this.editor.destroy(); - this.editor = null; - } - tooltipManager.destroy(); - document.body.removeChild(overlay); - document.body.removeChild(dialog); - }); - dialog.appendChild(footer); - - // Add to DOM - document.body.appendChild(overlay); - document.body.appendChild(dialog); - - // Attach tooltips to buttons (after adding to DOM) - const closeBtn = dialog.querySelector('#json-editor-close-btn'); - const cancelBtn = dialog.querySelector('#json-editor-cancel-btn'); - const applyBtn = dialog.querySelector('#json-editor-apply-btn'); - - if (closeBtn) tooltipManager.attach(closeBtn); - if (cancelBtn) tooltipManager.attach(cancelBtn); - if (applyBtn) tooltipManager.attach(applyBtn); - } - - /** - * Loads the JSONEditor script dynamically - * @returns {Promise} Promise that resolves when script is loaded - */ - loadJSONEditorScript() { - return new Promise((resolve, reject) => { - const script = document.createElement('script'); - script.src = new URL('../../lib/jsoneditor.min.js', import.meta.url).href; - script.onload = resolve; - script.onerror = reject; - document.head.appendChild(script); - }); - } - - /** - * Sets up drag-and-drop functionality for JSON files - * @param {HTMLElement} container - Container element to attach drag-and-drop - * @param {HTMLElement} validationMsg - Validation message element - */ - setupDragAndDrop(container, validationMsg) { - // Prevent default drag behaviors - ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { - container.addEventListener(eventName, (e) => { - e.preventDefault(); - e.stopPropagation(); - }, false); - }); - - // Highlight drop zone when item is dragged over it - ['dragenter', 'dragover'].forEach(eventName => { - container.addEventListener(eventName, () => { - container.classList.add('drag-over'); - }, false); - }); - - ['dragleave', 'drop'].forEach(eventName => { - container.addEventListener(eventName, () => { - container.classList.remove('drag-over'); - }, false); - }); - - // Handle dropped files - container.addEventListener('drop', (e) => { - const files = e.dataTransfer.files; - - if (files.length === 0) { - return; - } - - const file = files[0]; - - // Check if it's a JSON file - if (!file.name.endsWith('.json')) { - validationMsg.style.color = '#f55'; - validationMsg.textContent = '❌ Please drop a .json file'; - return; - } - - // Read the file - const reader = new FileReader(); - - reader.onload = (event) => { - try { - const jsonText = event.target.result; - const jsonObject = JSON.parse(jsonText); - - // Update editor with the new JSON - this.editor.set(jsonObject); - - validationMsg.style.color = '#5f5'; - validationMsg.textContent = `✓ Loaded: ${file.name}`; - } catch (error) { - validationMsg.style.color = '#f55'; - validationMsg.textContent = `❌ Invalid JSON in ${file.name}: ${error.message}`; - } - }; - - reader.onerror = () => { - validationMsg.style.color = '#f55'; - validationMsg.textContent = `❌ Error reading file: ${file.name}`; - }; - - reader.readAsText(file); - }, false); - } - - /** - * Creates the header with title and close button - * @param {Function} onClose - Close callback - * @returns {HTMLElement} Header element - */ - createHeader(onClose) { - const header = document.createElement('div'); - header.className = 'resolution-master-json-editor-header'; - - const title = document.createElement('div'); - title.className = 'resolution-master-json-editor-title'; - title.textContent = '{ } JSON Editor'; - - const closeBtn = document.createElement('button'); - closeBtn.id = 'json-editor-close-btn'; - closeBtn.className = 'resolution-master-json-editor-close-btn'; - closeBtn.textContent = '✕'; - closeBtn.addEventListener('click', onClose); - - header.appendChild(title); - header.appendChild(closeBtn); - - return header; - } - - /** - * Creates the info message - * @returns {HTMLElement} Info element - */ - createInfoMessage() { - const infoDiv = document.createElement('div'); - infoDiv.className = 'resolution-master-json-editor-info'; - infoDiv.innerHTML = ` - 💡 Direct JSON editing
- Edit the JSON below to modify custom presets and hidden built-in presets.
- You can also drag & drop a .json file onto the editor to load it.
- Changes will replace current configuration when you click "Apply Changes". - `; - - return infoDiv; - } - - /** - * Creates the footer with action buttons - * @param {HTMLElement} validationMsg - Validation message element - * @param {Function} onClose - Close callback - * @returns {HTMLElement} Footer element - */ - createFooter(validationMsg, onClose) { - const footer = document.createElement('div'); - footer.className = 'resolution-master-json-editor-footer'; - - const rightBtns = document.createElement('div'); - rightBtns.className = 'resolution-master-json-editor-footer-right'; - - // Cancel button - const cancelBtn = this.createCancelButton(onClose); - rightBtns.appendChild(cancelBtn); - - // Apply button - const applyBtn = this.createApplyButton(validationMsg, onClose); - rightBtns.appendChild(applyBtn); - - footer.appendChild(rightBtns); - - return footer; - } - - /** - * Creates the cancel button - * @param {Function} onClose - Close callback - * @returns {HTMLElement} Cancel button - */ - createCancelButton(onClose) { - const cancelBtn = document.createElement('button'); - cancelBtn.id = 'json-editor-cancel-btn'; - cancelBtn.className = 'resolution-master-json-editor-cancel-btn'; - cancelBtn.textContent = 'Cancel'; - cancelBtn.addEventListener('click', onClose); - - return cancelBtn; - } - - /** - * Creates the apply changes button - * @param {HTMLElement} validationMsg - Validation message element - * @param {Function} onClose - Close callback - * @returns {HTMLElement} Apply button - */ - createApplyButton(validationMsg, onClose) { - const applyBtn = document.createElement('button'); - applyBtn.id = 'json-editor-apply-btn'; - applyBtn.className = 'resolution-master-json-editor-apply-btn'; - applyBtn.textContent = 'Apply Changes'; - applyBtn.addEventListener('click', () => { - try { - // Get JSON from editor - const jsonObject = this.editor.get(); - const jsonString = JSON.stringify(jsonObject, null, 2); - - // Import with replace mode - const success = this.parentDialog.manager.importFromJSON(jsonString, false); - - if (success) { - validationMsg.style.color = '#5f5'; - validationMsg.textContent = '✓ Changes applied successfully!'; - - // Close dialog immediately and refresh main dialog - onClose(); - this.parentDialog.renderDialog(); - } else { - validationMsg.style.color = '#f55'; - validationMsg.textContent = '❌ Failed to apply changes. Check console for details.'; - } - } catch (error) { - validationMsg.style.color = '#f55'; - validationMsg.textContent = `❌ Invalid JSON: ${error.message}`; - } - }); - - return applyBtn; - } -} diff --git a/js/utils/preset-manager/PresetAddViewRenderer.js b/js/utils/preset-manager/PresetAddViewRenderer.js deleted file mode 100644 index 7f7d232..0000000 --- a/js/utils/preset-manager/PresetAddViewRenderer.js +++ /dev/null @@ -1,741 +0,0 @@ -// PresetAddViewRenderer.js - Renders the add/edit preset view - -import { AspectRatioUtils } from "../AspectRatioUtils.js"; -import { getIconHtml } from "../IconUtils.js"; - -/** - * Renderer for the add/edit preset view - */ -export class PresetAddViewRenderer { - constructor(parentDialog) { - this.parentDialog = parentDialog; - } - - /** - * Renders the add/edit preset view with category selection and preview - * @param {HTMLElement} container - Container to render into - */ - render(container) { - // Title - const titleDiv = document.createElement('div'); - titleDiv.className = 'resolution-master-preset-add-title'; - titleDiv.textContent = 'Add Presets to Category'; - container.appendChild(titleDiv); - - // Category selection section - const categorySection = this.createCategorySection(); - container.appendChild(categorySection); - - // Quick add form (shown only when category is selected) - if (this.parentDialog.selectedCategory) { - this.renderQuickAddForm(container); - - // Validation message - const validationMsg = document.createElement('div'); - validationMsg.id = 'validation-msg'; - validationMsg.style.cssText = ` - color: #f55; - font-size: 12px; - margin-top: 4px; - margin-bottom: 8px; - min-height: 14px; - `; - container.appendChild(validationMsg); - - // Preset preview section - this.renderPresetPreview(container); - } - } - - /** - * Creates the category selection section - * @returns {HTMLElement} Category section - */ - createCategorySection() { - const categorySection = document.createElement('div'); - categorySection.className = 'resolution-master-preset-add-category-section'; - - const categoryLabel = document.createElement('div'); - categoryLabel.className = 'resolution-master-preset-add-category-label'; - categoryLabel.textContent = 'Category'; - categorySection.appendChild(categoryLabel); - - // Container for category button and rename button - const categoryButtonContainer = document.createElement('div'); - categoryButtonContainer.className = 'resolution-master-preset-add-category-btn-container'; - - const categoryButton = this.createCategoryButton(); - categoryButtonContainer.appendChild(categoryButton); - - // Rename category button (only shown when category is selected) - if (this.parentDialog.selectedCategory) { - const renameCategoryBtn = this.createRenameCategoryButton(); - categoryButtonContainer.appendChild(renameCategoryBtn); - } - - categorySection.appendChild(categoryButtonContainer); - return categorySection; - } - - /** - * Creates the category selection button - * @returns {HTMLElement} Category button - */ - createCategoryButton() { - const categoryButton = document.createElement('button'); - categoryButton.id = 'category-select-btn'; - categoryButton.className = 'resolution-master-preset-add-category-btn'; - if (!this.parentDialog.selectedCategory) { - categoryButton.classList.add('placeholder'); - } - categoryButton.textContent = this.parentDialog.selectedCategory || 'Click to select category...'; - - categoryButton.addEventListener('click', (e) => { - this.parentDialog.showCategoryDropdown(e); - }); - - return categoryButton; - } - - /** - * Creates the rename category button - * @returns {HTMLElement} Rename button - */ - createRenameCategoryButton() { - const renameCategoryBtn = document.createElement('button'); - renameCategoryBtn.className = 'resolution-master-preset-add-rename-category-btn'; - renameCategoryBtn.innerHTML = getIconHtml(this.parentDialog.editIcon, '✏️'); - // Tooltip handled by TooltipManager - - renameCategoryBtn.addEventListener('click', () => { - this.parentDialog.renameDialogManager.showRenameCategoryDialog(this.parentDialog.selectedCategory); - }); - - return renameCategoryBtn; - } - - /** - * Renders the quick add form - * @param {HTMLElement} container - Container to render into - */ - renderQuickAddForm(container) { - const isEditMode = this.parentDialog.editingPresetData !== null; - const sectionBorderColor = isEditMode ? 'rgba(80, 255, 80, 0.5)' : 'rgba(90, 170, 255, 0.3)'; - const sectionBgColor = isEditMode ? 'rgba(80, 255, 80, 0.1)' : 'rgba(90, 170, 255, 0.1)'; - const titleColor = isEditMode ? '#5f5' : '#5af'; - - const quickAddSection = document.createElement('div'); - quickAddSection.style.cssText = ` - margin-bottom: 12px; - padding: 10px; - background: ${sectionBgColor}; - border: 1px solid ${sectionBorderColor}; - border-radius: 6px; - `; - - const quickAddTitle = document.createElement('div'); - quickAddTitle.id = 'quick-add-title'; - quickAddTitle.textContent = this.parentDialog.editingPresetData ? `Quick Edit Preset: ${this.parentDialog.editingPresetData.name}` : 'Quick Add Preset'; - quickAddTitle.style.cssText = `color: ${titleColor}; font-size: 13px; font-weight: bold; margin-bottom: 6px;`; - quickAddSection.appendChild(quickAddTitle); - - const quickAddForm = this.createQuickAddForm(); - quickAddSection.appendChild(quickAddForm); - - container.appendChild(quickAddSection); - } - - /** - * Creates the quick add form - * @returns {HTMLElement} Form element - */ - createQuickAddForm() { - const quickAddForm = document.createElement('div'); - quickAddForm.style.cssText = 'display: flex; flex-direction: column; gap: 4px;'; - - // Name input with error message - const nameGroup = this.createNameInput(); - quickAddForm.appendChild(nameGroup); - - // Width and Height in a row - const dimensionsRow = this.createDimensionsRow(); - quickAddForm.appendChild(dimensionsRow); - - // Preview container for shape visualization - const previewContainer = this.createPreviewContainer(); - quickAddForm.appendChild(previewContainer); - - // Button container for + and X buttons - const buttonContainer = this.createButtonContainer(); - quickAddForm.appendChild(buttonContainer); - - return quickAddForm; - } - - /** - * Creates the name input group - * @returns {HTMLElement} Name group - */ - createNameInput() { - const nameGroup = document.createElement('div'); - nameGroup.style.cssText = 'display: flex; flex-direction: column; gap: 4px;'; - - const nameLabel = document.createElement('label'); - nameLabel.textContent = 'Name'; - nameLabel.style.cssText = 'color: #ccc; font-size: 11px; font-weight: bold;'; - - const nameInput = document.createElement('input'); - nameInput.id = 'quick-name-input'; - nameInput.type = 'text'; - nameInput.placeholder = 'Preset name'; - nameInput.value = this.parentDialog.editingPresetData ? this.parentDialog.editingPresetData.name : ''; - nameInput.style.cssText = ` - padding: 6px; - border: 1px solid #555; - border-radius: 4px; - background: #333; - color: #fff; - font-size: 13px; - outline: none; - `; - - const nameErrorMsg = document.createElement('div'); - nameErrorMsg.id = 'name-error-msg'; - nameErrorMsg.style.cssText = ` - color: #f55; - font-size: 11px; - margin-top: 2px; - min-height: 14px; - `; - - nameGroup.appendChild(nameLabel); - nameGroup.appendChild(nameInput); - nameGroup.appendChild(nameErrorMsg); - - return nameGroup; - } - - /** - * Creates the dimensions row (width + height) - * @returns {HTMLElement} Dimensions row - */ - createDimensionsRow() { - const dimensionsRow = document.createElement('div'); - dimensionsRow.style.cssText = 'display: flex; gap: 6px;'; - - // Width input - const widthGroup = this.createDimensionInput('Width', 'quick-width-input', 'width-error-msg', - this.parentDialog.editingPresetData ? this.parentDialog.editingPresetData.width : ''); - dimensionsRow.appendChild(widthGroup); - - // Height input - const heightGroup = this.createDimensionInput('Height', 'quick-height-input', 'height-error-msg', - this.parentDialog.editingPresetData ? this.parentDialog.editingPresetData.height : ''); - dimensionsRow.appendChild(heightGroup); - - return dimensionsRow; - } - - /** - * Creates a dimension input group (width or height) - */ - createDimensionInput(label, inputId, errorId, value) { - const group = document.createElement('div'); - group.style.cssText = 'flex: 1; display: flex; flex-direction: column; gap: 4px;'; - - const labelEl = document.createElement('label'); - labelEl.textContent = label; - labelEl.style.cssText = 'color: #ccc; font-size: 11px; font-weight: bold;'; - - const input = document.createElement('input'); - input.id = inputId; - input.type = 'number'; - input.placeholder = '512'; - input.min = '64'; - input.step = '1'; - input.value = value; - input.style.cssText = ` - padding: 6px; - border: 1px solid #555; - border-radius: 4px; - background: #333; - color: #fff; - font-size: 13px; - outline: none; - `; - - const errorMsg = document.createElement('div'); - errorMsg.id = errorId; - errorMsg.style.cssText = ` - color: #f55; - font-size: 11px; - margin-top: 2px; - min-height: 14px; - `; - - group.appendChild(labelEl); - group.appendChild(input); - group.appendChild(errorMsg); - - return group; - } - - /** - * Creates the preview container - * @returns {HTMLElement} Preview container - */ - createPreviewContainer() { - const previewContainer = document.createElement('div'); - previewContainer.style.cssText = ` - margin-top: 6px; - padding: 8px; - background: rgba(0, 0, 0, 0.2); - border: 1px solid #444; - border-radius: 4px; - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - `; - - const previewLabel = document.createElement('div'); - previewLabel.textContent = 'Preview:'; - const labelColor = this.parentDialog.editingPresetData ? '#5f5' : '#5af'; - previewLabel.style.cssText = `color: ${labelColor}; font-size: 11px; font-weight: bold;`; - - const previewCanvas = document.createElement('div'); - previewCanvas.id = 'preview-canvas'; - previewCanvas.style.cssText = ` - width: 160px; - height: 120px; - display: flex; - align-items: center; - justify-content: center; - position: relative; - `; - - const previewShape = document.createElement('div'); - previewShape.id = 'preview-shape'; - previewShape.style.cssText = ` - border: 2px solid #5af; - background: rgba(90, 170, 255, 0.1); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - transition: all 0.2s; - `; - - const previewText = document.createElement('div'); - previewText.id = 'preview-text'; - previewText.style.cssText = ` - color: #5af; - font-size: 11px; - font-weight: bold; - text-align: center; - line-height: 1.4; - `; - - previewShape.appendChild(previewText); - previewCanvas.appendChild(previewShape); - previewContainer.appendChild(previewLabel); - previewContainer.appendChild(previewCanvas); - - // Set up event listeners after adding to DOM - setTimeout(() => this.setupFormValidation(), 0); - - return previewContainer; - } - - /** - * Sets up form validation and preview updates - */ - setupFormValidation() { - const nameInput = document.getElementById('quick-name-input'); - const widthInput = document.getElementById('quick-width-input'); - const heightInput = document.getElementById('quick-height-input'); - - if (!nameInput || !widthInput || !heightInput) return; - - // Function to update preview shape - const updatePreviewShape = () => { - const width = parseInt(widthInput.value) || 0; - const height = parseInt(heightInput.value) || 0; - const previewShape = document.getElementById('preview-shape'); - const previewText = document.getElementById('preview-text'); - const previewCanvas = document.getElementById('preview-canvas'); - - if (!previewShape || !previewText) return; - - if (width > 0 && height > 0) { - const maxWidth = 145; - const maxHeight = 105; - const scale = Math.min(maxWidth / width, maxHeight / height); - - const scaledWidth = width * scale; - const scaledHeight = height * scale; - - previewShape.style.width = `${scaledWidth}px`; - previewShape.style.height = `${scaledHeight}px`; - - const isEditMode = this.parentDialog.editingPresetData !== null; - const borderColor = isEditMode ? '#5f5' : '#5af'; - const bgColor = isEditMode ? 'rgba(80, 255, 80, 0.1)' : 'rgba(90, 170, 255, 0.1)'; - const textColor = isEditMode ? '#5f5' : '#5af'; - - previewShape.style.border = `2px solid ${borderColor}`; - previewShape.style.background = bgColor; - - const gcd = (a, b) => b === 0 ? a : gcd(b, a % b); - const divisor = gcd(width, height); - const ratioW = width / divisor; - const ratioH = height / divisor; - - previewText.innerHTML = ` -
${width}×${height}
-
${ratioW}:${ratioH}
- `; - - if (previewCanvas.parentElement) { - previewCanvas.parentElement.style.display = 'flex'; - } - } else { - if (previewCanvas.parentElement) { - previewCanvas.parentElement.style.display = 'none'; - } - } - }; - - // Validation function - const validateForm = () => { - const nameErrorMsg = document.getElementById('name-error-msg'); - const widthErrorMsg = document.getElementById('width-error-msg'); - const heightErrorMsg = document.getElementById('height-error-msg'); - const addButton = document.getElementById('quick-add-button'); - - if (!nameErrorMsg || !widthErrorMsg || !heightErrorMsg || !addButton) return; - - const enteredName = nameInput.value.trim(); - const width = parseInt(widthInput.value); - const height = parseInt(heightInput.value); - - let isValid = true; - - // Reset all borders and error messages - nameInput.style.borderColor = '#555'; - widthInput.style.borderColor = '#555'; - heightInput.style.borderColor = '#555'; - nameErrorMsg.textContent = ''; - widthErrorMsg.textContent = ''; - heightErrorMsg.textContent = ''; - - // Check if name is empty - if (!enteredName) { - nameInput.style.borderColor = '#f55'; - nameErrorMsg.textContent = '⚠️ Name is required'; - isValid = false; - } else { - const customPresets = this.parentDialog.manager.getCustomPresets(); - const categoryPresets = customPresets[this.parentDialog.selectedCategory] || {}; - const nameExists = Object.keys(categoryPresets).includes(enteredName); - - if (this.parentDialog.editingPresetName) { - if (enteredName !== this.parentDialog.editingPresetName && nameExists) { - nameInput.style.borderColor = '#f55'; - nameErrorMsg.textContent = `⚠️ Preset "${enteredName}" already exists`; - isValid = false; - } - } else { - if (nameExists) { - nameInput.style.borderColor = '#f55'; - nameErrorMsg.textContent = `⚠️ Preset "${enteredName}" already exists`; - isValid = false; - } - } - } - - // Check width - if (!width || width < 64) { - widthInput.style.borderColor = '#f55'; - widthErrorMsg.textContent = '⚠️ Width must be at least 64px'; - isValid = false; - } - - // Check height - if (!height || height < 64) { - heightInput.style.borderColor = '#f55'; - heightErrorMsg.textContent = '⚠️ Height must be at least 64px'; - isValid = false; - } - - // Update button state - if (isValid) { - addButton.disabled = false; - addButton.style.background = '#5af'; - addButton.style.color = '#000'; - addButton.style.cursor = 'pointer'; - addButton.style.opacity = '1'; - } else { - addButton.disabled = true; - addButton.style.background = '#666'; - addButton.style.color = '#999'; - addButton.style.cursor = 'not-allowed'; - addButton.style.opacity = '0.5'; - } - }; - - // Add event listeners - nameInput.addEventListener('input', validateForm); - widthInput.addEventListener('input', () => { - validateForm(); - updatePreviewShape(); - }); - heightInput.addEventListener('input', () => { - validateForm(); - updatePreviewShape(); - }); - - // Initial validation and preview - validateForm(); - updatePreviewShape(); - } - - /** - * Creates the button container (add/OK and cancel buttons) - * @returns {HTMLElement} Button container - */ - createButtonContainer() { - const buttonContainer = document.createElement('div'); - buttonContainer.style.cssText = 'display: flex; flex-direction: row; gap: 4px;'; - - const isEditMode = this.parentDialog.editingPresetData !== null; - - // Add/OK button - const addButton = document.createElement('button'); - addButton.id = 'quick-add-button'; - addButton.textContent = isEditMode ? 'OK' : '+'; - // Tooltip handled by TooltipManager - const buttonFontSize = isEditMode ? '14px' : '20px'; - - addButton.style.cssText = ` - padding: 6px 12px; - border: none; - border-radius: 4px; - background: #5af; - color: #000; - font-size: ${buttonFontSize}; - font-weight: bold; - cursor: pointer; - transition: all 0.2s; - `; - addButton.addEventListener('click', () => this.parentDialog.quickAddPreset()); - addButton.addEventListener('mouseenter', () => { - if (!addButton.disabled) { - addButton.style.background = '#7cf'; - } - }); - addButton.addEventListener('mouseleave', () => { - if (!addButton.disabled) { - addButton.style.background = '#5af'; - } - }); - buttonContainer.appendChild(addButton); - - // Cancel button (only visible when editing) - if (this.parentDialog.editingPresetName) { - const cancelButton = document.createElement('button'); - cancelButton.textContent = '✕'; - // Tooltip handled by TooltipManager - cancelButton.style.cssText = ` - padding: 4px 12px; - border: 1px solid #666; - border-radius: 4px; - background: rgba(255,255,255,0.1); - color: #ddd; - font-size: 14px; - font-weight: bold; - cursor: pointer; - transition: all 0.2s; - `; - cancelButton.addEventListener('click', () => this.parentDialog.cancelEdit()); - cancelButton.addEventListener('mouseenter', () => { - cancelButton.style.background = 'rgba(255,255,255,0.2)'; - cancelButton.style.borderColor = '#888'; - }); - cancelButton.addEventListener('mouseleave', () => { - cancelButton.style.background = 'rgba(255,255,255,0.1)'; - cancelButton.style.borderColor = '#666'; - }); - buttonContainer.appendChild(cancelButton); - } - - return buttonContainer; - } - - /** - * Renders the preset preview section - * @param {HTMLElement} container - Container to render into - */ - renderPresetPreview(container) { - this.parentDialog.presetPreviewContainer = document.createElement('div'); - this.parentDialog.presetPreviewContainer.id = 'preset-preview'; - this.parentDialog.presetPreviewContainer.style.cssText = ` - margin-top: 12px; - padding: 10px; - background: rgba(0, 0, 0, 0.2); - border: 1px solid #444; - border-radius: 6px; - max-height: 550px; - display: flex; - flex-direction: column; - `; - - const previewTitle = document.createElement('div'); - previewTitle.textContent = `Presets in "${this.parentDialog.selectedCategory}"`; - previewTitle.style.cssText = ` - color: #5af; - font-size: 13px; - font-weight: bold; - margin-bottom: 8px; - padding-bottom: 6px; - border-bottom: 1px solid #444; - `; - this.parentDialog.presetPreviewContainer.appendChild(previewTitle); - - const presetsGrid = document.createElement('div'); - presetsGrid.id = 'presets-grid'; - presetsGrid.style.cssText = ` - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 8px; - `; - this.parentDialog.presetPreviewContainer.appendChild(presetsGrid); - - // Store reference - this.parentDialog.presetsGrid = presetsGrid; - - container.appendChild(this.parentDialog.presetPreviewContainer); - - // Update preview - this.parentDialog.updatePresetPreview(); - } - - /** - * Creates a column for one aspect ratio (using unified AspectRatioUtils method) - * @param {string} ratio - Aspect ratio string - * @param {Array} presetList - List of presets for this ratio - * @returns {HTMLElement} Column element - */ - createRatioColumn(ratio, presetList) { - // Use unified method from AspectRatioUtils with custom preset item renderer - return AspectRatioUtils.createPresetColumn(ratio, presetList, { - renderPresetItem: (preset) => this.createPresetItemForColumn(preset) - }); - } - - /** - * Creates a preset item for the column view - */ - createPresetItemForColumn(preset) { - const presetItem = document.createElement('div'); - presetItem.className = 'resolution-master-aspect-ratio-preset-item resolution-master-aspect-ratio-preset-item-column' + (preset.isHidden ? ' resolution-master-preset-is-hidden' : ''); - // Preset name with custom icon if applicable - const nameDiv = document.createElement('div'); - nameDiv.className = 'resolution-master-aspect-ratio-preset-item-name'; - const customIcon = preset.isCustom && this.parentDialog.customPresetIcon ? - `` : ''; - nameDiv.innerHTML = `${preset.name}${customIcon}`; - - // Dimensions below name - const dimensionsDiv = document.createElement('div'); - dimensionsDiv.className = 'resolution-master-aspect-ratio-preset-item-dims'; - dimensionsDiv.textContent = `${preset.width}×${preset.height}`; - - // Action button - if (preset.isCustom) { - const deleteBtn = this.createDeleteButton(preset); - presetItem.appendChild(deleteBtn); - } else { - const toggleBtn = this.createToggleButton(preset); - presetItem.appendChild(toggleBtn); - } - - presetItem.appendChild(nameDiv); - presetItem.appendChild(dimensionsDiv); - - // Click to load preset values into quick add form - presetItem.addEventListener('click', () => { - if (preset.isCustom) { - this.parentDialog.editingPresetName = preset.name; - this.parentDialog.editingPresetData = { - name: preset.name, - width: preset.width, - height: preset.height - }; - } else { - this.parentDialog.editingPresetName = null; - this.parentDialog.editingPresetData = { - name: preset.name, - width: preset.width, - height: preset.height - }; - } - - this.parentDialog.renderDialog(); - }); - - return presetItem; - } - - /** - * Creates delete button for custom preset - */ - createDeleteButton(preset) { - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'resolution-master-aspect-ratio-preset-action-btn delete'; - // Tooltip handled by TooltipManager - - if (this.parentDialog.deleteIcon) { - deleteBtn.innerHTML = ``; - } else { - deleteBtn.textContent = '🗑️'; - } - - deleteBtn.addEventListener('click', (e) => { - e.stopPropagation(); - if (confirm(`Delete custom preset "${preset.name}"?`)) { - this.parentDialog.manager.deletePreset(this.parentDialog.selectedCategory, preset.name); - this.parentDialog.updatePresetPreview(); - this.parentDialog.attachTooltips(); // Re-attach tooltips to new DOM elements - } - }); - - return deleteBtn; - } - - /** - * Creates toggle button for built-in preset - */ - createToggleButton(preset) { - const isHidden = this.parentDialog.manager.isBuiltInPresetHidden(this.parentDialog.selectedCategory, preset.name); - - const toggleBtn = document.createElement('button'); - toggleBtn.className = 'resolution-master-aspect-ratio-preset-action-btn' + (isHidden ? ' unhide' : ' hide'); - // Tooltip handled by TooltipManager - - if (this.parentDialog.deleteIcon) { - toggleBtn.innerHTML = ``; - } else { - toggleBtn.textContent = '🗑️'; - } - - toggleBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.parentDialog.manager.toggleBuiltInPresetVisibility(this.parentDialog.selectedCategory, preset.name); - this.parentDialog.updatePresetPreview(); - this.parentDialog.attachTooltips(); // Re-attach tooltips to new DOM elements - }); - - return toggleBtn; - } -} diff --git a/js/utils/preset-manager/PresetListRenderer.js b/js/utils/preset-manager/PresetListRenderer.js deleted file mode 100644 index a4cc2e3..0000000 --- a/js/utils/preset-manager/PresetListRenderer.js +++ /dev/null @@ -1,662 +0,0 @@ -// PresetListRenderer.js - Renders the preset list view - -import { AspectRatioUtils } from "../AspectRatioUtils.js"; -import { getIconHtml } from "../IconUtils.js"; -import { DragDropHandler } from "./DragDropHandler.js"; -import { PresetUIComponents } from "./PresetUIComponents.js"; - -/** - * Renderer for the preset list view - */ -export class PresetListRenderer { - constructor(parentDialog) { - this.parentDialog = parentDialog; - } - - /** - * Renders the list view showing all custom presets - * @param {HTMLElement} container - Container to render into - */ - render(container) { - const customPresets = this.parentDialog.manager.getCustomPresets(); - const stats = this.parentDialog.manager.getStatistics(); - - // Stats header - const statsDiv = this.createStatsHeader(stats, container); - container.appendChild(statsDiv); - - // If no presets, show empty state - if (stats.presets === 0) { - const emptyState = this.createEmptyState(); - container.appendChild(emptyState); - return; - } - - // List presets grouped by category - Object.entries(customPresets).forEach(([category, presets], categoryIndex) => { - const categorySection = this.createCategorySection(category, presets, categoryIndex, container); - container.appendChild(categorySection); - }); - } - - /** - * Creates the stats header - * @param {Object} stats - Statistics object - * @param {HTMLElement} container - Parent container - * @returns {HTMLElement} Stats div - */ - createStatsHeader(stats, container) { - const statsDiv = document.createElement('div'); - statsDiv.className = 'resolution-master-preset-list-stats'; - statsDiv.innerHTML = ` - 📊 ${stats.categories} categories, - ${stats.presets} custom presets total - `; - - // Add drag & drop handlers to statsDiv to allow dropping at top (index 0) - statsDiv.addEventListener('dragover', (e) => { - if (this.parentDialog.draggedCategoryName) { - e.preventDefault(); - e.dataTransfer.dropEffect = 'move'; - - // Find first category header and show indicator above it - const firstCategoryHeader = DragDropHandler.getFirstCategoryHeader(container); - DragDropHandler.setDropIndicatorTop(firstCategoryHeader); - } - }); - - statsDiv.addEventListener('dragleave', (e) => { - if (this.parentDialog.draggedCategoryName && !statsDiv.contains(e.relatedTarget)) { - // Reset indicator only if leaving the entire statsDiv - const firstCategoryHeader = DragDropHandler.getFirstCategoryHeader(container); - DragDropHandler.clearDropIndicator(firstCategoryHeader); - } - }); - - statsDiv.addEventListener('drop', (e) => { - e.preventDefault(); - - // Save draggedCategoryName before dragend might reset it - const draggedCategory = this.parentDialog.draggedCategoryName; - - if (draggedCategory) { - // Move to top (index 0) - this.parentDialog.manager.reorderCategories(draggedCategory, 0); - this.parentDialog.renderDialog(); - } - }); - - return statsDiv; - } - - /** - * Creates the empty state message - * @returns {HTMLElement} Empty state div - */ - createEmptyState() { - const emptyState = document.createElement('div'); - emptyState.className = 'resolution-master-preset-list-empty'; - emptyState.innerHTML = ` -
📦
-
No custom presets yet
-
Click "Add Preset" to create your first custom preset
- `; - return emptyState; - } - - /** - * Creates a category section - * @param {string} category - Category name - * @param {Object} presets - Presets in category - * @param {number} categoryIndex - Category index - * @param {HTMLElement} container - Parent container - * @returns {HTMLElement} Category section - */ - createCategorySection(category, presets, categoryIndex, container) { - const categorySection = document.createElement('div'); - categorySection.className = 'resolution-master-preset-list-category-section'; - categorySection.dataset.categoryName = category; - categorySection.dataset.categoryIndex = categoryIndex; - - // Category header with edit button - const categoryHeader = this.createCategoryHeader(category, presets, categoryIndex, container, categorySection); - categorySection.appendChild(categoryHeader); - - // Presets list - Object.entries(presets).forEach(([name, dims], presetIndex) => { - const presetItem = this.createPresetItem(category, name, dims, presetIndex); - categorySection.appendChild(presetItem); - }); - - return categorySection; - } - - /** - * Creates a category header - * @param {string} category - Category name - * @param {Object} presets - Presets in category - * @param {number} categoryIndex - Category index - * @param {HTMLElement} container - Parent container - * @param {HTMLElement} categorySection - Category section element - * @returns {HTMLElement} Category header - */ - createCategoryHeader(category, presets, categoryIndex, container, categorySection) { - const categoryHeader = document.createElement('div'); - categoryHeader.draggable = true; - categoryHeader.className = 'resolution-master-preset-list-category-header'; - - // Drag & drop handlers for category reordering - this.attachCategoryDragHandlers(categoryHeader, category, categoryIndex, container, categorySection); - - // Category title with custom icon (only for truly custom categories) - const categoryTitle = this.createCategoryTitle(category, presets); - - // Edit category button - const editCategoryBtn = this.createEditCategoryButton(category); - - categoryHeader.appendChild(categoryTitle); - categoryHeader.appendChild(editCategoryBtn); - - return categoryHeader; - } - - /** - * Attaches drag & drop handlers to category header - */ - attachCategoryDragHandlers(categoryHeader, category, categoryIndex, container, categorySection) { - categoryHeader.addEventListener('dragstart', (e) => { - this.parentDialog.draggedCategoryName = category; - categoryHeader.style.opacity = '0.5'; - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/html', categoryHeader.innerHTML); - }); - - categoryHeader.addEventListener('dragend', () => { - categoryHeader.style.opacity = '1'; - DragDropHandler.clearDropIndicator(categoryHeader); - this.parentDialog.draggedCategoryName = null; - }); - - categoryHeader.addEventListener('dragover', (e) => { - if (this.parentDialog.draggedCategoryName && this.parentDialog.draggedCategoryName !== category) { - e.preventDefault(); - e.stopPropagation(); - e.dataTransfer.dropEffect = 'move'; - - const nextCategoryHeader = DragDropHandler.getNextCategoryHeader(container, categoryIndex); - DragDropHandler.clearDropIndicator(nextCategoryHeader); - - DragDropHandler.setDropIndicatorTop(categoryHeader, '#5af'); - } - }); - - categoryHeader.addEventListener('dragleave', () => { - DragDropHandler.clearDropIndicator(categoryHeader); - }); - - categoryHeader.addEventListener('drop', (e) => { - e.preventDefault(); - e.stopPropagation(); - - if (this.parentDialog.draggedCategoryName && this.parentDialog.draggedCategoryName !== category) { - const targetIndex = categoryIndex; - this.parentDialog.manager.reorderCategories(this.parentDialog.draggedCategoryName, targetIndex); - this.parentDialog.renderDialog(); - } - - DragDropHandler.clearDropIndicator(categoryHeader); - }); - - // Category section drag handlers - this.attachCategorySectionDragHandlers(categorySection, category, categoryIndex, container, categoryHeader); - } - - /** - * Attaches drag & drop handlers to category section - */ - attachCategorySectionDragHandlers(categorySection, category, categoryIndex, container, categoryHeader) { - categorySection.addEventListener('dragover', (e) => { - if (this.parentDialog.draggedCategoryName && this.parentDialog.draggedCategoryName !== category) { - const draggableElement = e.target.closest('div[draggable="true"]'); - const isOverCategoryHeader = draggableElement && !draggableElement.dataset.presetName; - - if (!isOverCategoryHeader) { - e.preventDefault(); - - DragDropHandler.clearDropIndicator(categoryHeader); - - const nextCategorySection = container.querySelector(`[data-category-index="${categoryIndex + 1}"]`); - if (nextCategorySection) { - const nextCategoryName = nextCategorySection.dataset.categoryName; - if (nextCategoryName !== this.parentDialog.draggedCategoryName) { - const nextCategoryHeader = nextCategorySection.querySelector('div[draggable="true"]'); - if (nextCategoryHeader) { - const headerRect = nextCategoryHeader.getBoundingClientRect(); - const distanceToHeader = headerRect.top - e.clientY; - - if (distanceToHeader > 10) { - DragDropHandler.setDropIndicatorTop(nextCategoryHeader, '#5af'); - } - } - } - } else { - DragDropHandler.setDropIndicatorBottom(categorySection, '#5af'); - } - } - } - }); - - categorySection.addEventListener('dragleave', (e) => { - if (this.parentDialog.draggedCategoryName && !categorySection.contains(e.relatedTarget)) { - DragDropHandler.clearDropIndicator(categoryHeader); - DragDropHandler.clearDropIndicator(categorySection); - const nextCategoryHeader = DragDropHandler.getNextCategoryHeader(container, categoryIndex); - DragDropHandler.clearDropIndicator(nextCategoryHeader); - } - }); - - categorySection.addEventListener('drop', (e) => { - const draggedCategory = this.parentDialog.draggedCategoryName; - - DragDropHandler.clearDropIndicator(categoryHeader); - DragDropHandler.clearDropIndicator(categorySection); - const nextCategoryHeader = DragDropHandler.getNextCategoryHeader(container, categoryIndex); - DragDropHandler.clearDropIndicator(nextCategoryHeader); - - if (draggedCategory && draggedCategory !== category) { - if (e.target !== categoryHeader && !categoryHeader.contains(e.target)) { - e.preventDefault(); - e.stopPropagation(); - - const targetIndex = categoryIndex + 1; - this.parentDialog.manager.reorderCategories(draggedCategory, targetIndex); - this.parentDialog.renderDialog(); - } - } - }); - } - - /** - * Creates category title element - */ - createCategoryTitle(category, presets) { - const categoryTitle = document.createElement('div'); - categoryTitle.className = 'resolution-master-preset-list-category-title'; - - // Create clickable name element (like preset names) - const nameElement = document.createElement('strong'); - nameElement.className = 'resolution-master-preset-list-category-name'; - nameElement.textContent = category; - nameElement.style.cursor = 'pointer'; - - // Add double-click handler only to the name - nameElement.addEventListener('dblclick', (e) => { - e.stopPropagation(); - this.parentDialog.renameDialogManager.startRenamingCategory(nameElement, category); - }); - - categoryTitle.appendChild(nameElement); - - // Add preset count - const countSpan = document.createElement('span'); - countSpan.textContent = ` (${Object.keys(presets).length})`; - categoryTitle.appendChild(countSpan); - - // Add custom icon if needed - const builtInPresets = this.parentDialog.manager.rm.presetCategories; - const isTrulyCustomCategory = !builtInPresets.hasOwnProperty(category); - if (isTrulyCustomCategory) { - const customIcon = getIconHtml(this.parentDialog.customPresetIcon, '', 14, 'margin-left: 6px; vertical-align: middle;'); - if (customIcon) { - const iconSpan = document.createElement('span'); - iconSpan.innerHTML = customIcon; - categoryTitle.appendChild(iconSpan); - } - } - - return categoryTitle; - } - - /** - * Creates edit category button - */ - createEditCategoryButton(category) { - const editCategoryBtn = document.createElement('button'); - editCategoryBtn.className = 'resolution-master-preset-list-edit-category-btn'; - editCategoryBtn.innerHTML = getIconHtml(this.parentDialog.editIcon, '✏️'); - // Tooltip handled by TooltipManager - - editCategoryBtn.addEventListener('click', () => { - this.parentDialog.currentView = 'add'; - this.parentDialog.selectedCategory = category; - this.parentDialog.editingPreset = null; - this.parentDialog.editingPresetName = null; - this.parentDialog.editingPresetData = null; - this.parentDialog.renderDialog(); - }); - - return editCategoryBtn; - } - - /** - * Creates a preset item element for the list - * @param {string} category - Category name - * @param {string} name - Preset name - * @param {Object} dims - Dimensions {width, height} - * @param {number} presetIndex - Preset index - * @returns {HTMLElement} Preset item - */ - createPresetItem(category, name, dims, presetIndex) { - const item = document.createElement('div'); - item.draggable = true; - item.className = 'resolution-master-preset-list-item'; - item.dataset.presetName = name; - item.dataset.presetIndex = presetIndex; - item.dataset.category = category; - - // Drag & drop handlers for MOVE mode - this.attachPresetDragHandlers(item, category, name, presetIndex); - - // Checkbox for bulk deletion - const checkbox = this.createBulkDeleteCheckbox(category, name); - - // Preset info - const info = this.createPresetInfo(category, name, dims); - - // Action buttons (includes clone handle, edit, and delete) - const actions = this.createActionButtons(category, name, dims, presetIndex); - - item.appendChild(checkbox); - item.appendChild(info); - item.appendChild(actions); - - return item; - } - - /** - * Attaches drag & drop handlers to preset item - */ - attachPresetDragHandlers(item, category, name, presetIndex) { - item.addEventListener('dragstart', (e) => { - this.parentDialog.draggedPresetName = name; - this.parentDialog.draggedPresetCategory = category; - item.style.opacity = '0.5'; - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/html', item.innerHTML); - }); - - item.addEventListener('dragend', () => { - item.style.opacity = '1'; - DragDropHandler.clearDropIndicator(item); - this.parentDialog.draggedPresetName = null; - this.parentDialog.draggedPresetCategory = null; - }); - - item.addEventListener('dragover', (e) => { - if (this.parentDialog.draggedPresetName && this.parentDialog.draggedPresetName !== name) { - e.preventDefault(); - - // Set drop effect based on mode - if (this.parentDialog.isDuplicateMode) { - e.dataTransfer.dropEffect = 'copy'; - } else { - e.dataTransfer.dropEffect = 'move'; - } - - const rect = item.getBoundingClientRect(); - const midpoint = rect.top + rect.height / 2; - - let color; - if (this.parentDialog.isDuplicateMode) { - // Clone mode - use green/cyan to indicate copy - color = '#0f0'; // Green for clone - } else if (this.parentDialog.draggedPresetCategory === category) { - // Same category reorder - blue - color = '#5af'; - } else { - // Different category move - orange or red if name exists - const customPresets = this.parentDialog.manager.getCustomPresets(); - const targetCategoryPresets = customPresets[category] || {}; - const nameExists = Object.keys(targetCategoryPresets).includes(this.parentDialog.draggedPresetName); - color = nameExists ? '#f00' : '#fa0'; - } - - if (e.clientY < midpoint) { - DragDropHandler.setDropIndicatorTop(item, color); - } else { - DragDropHandler.setDropIndicatorBottom(item, color); - } - } - }); - - item.addEventListener('dragleave', () => { - DragDropHandler.clearDropIndicator(item); - }); - - item.addEventListener('drop', (e) => { - DragDropHandler.clearDropIndicator(item); - - if (this.parentDialog.draggedPresetName && this.parentDialog.draggedPresetName !== name) { - e.preventDefault(); - e.stopPropagation(); - - const rect = item.getBoundingClientRect(); - const midpoint = rect.top + rect.height / 2; - let targetIndex = presetIndex; - - if (e.clientY >= midpoint) { - targetIndex = presetIndex + 1; - } - - // Check if in duplicate mode - if (this.parentDialog.isDuplicateMode) { - // CLONE MODE: Duplicate the preset - const sourceName = this.parentDialog.draggedPresetName; - const sourceCategory = this.parentDialog.draggedPresetCategory; - - // Generate unique name for the duplicate - let targetName = sourceName; - const customPresets = this.parentDialog.manager.getCustomPresets(); - const targetCategoryPresets = customPresets[category] || {}; - - // If name exists in target category, add a suffix - if (Object.keys(targetCategoryPresets).includes(targetName)) { - let counter = 1; - while (Object.keys(targetCategoryPresets).includes(`${sourceName} (${counter})`)) { - counter++; - } - targetName = `${sourceName} (${counter})`; - } - - // Get built-in presets for duplicatePreset method - const builtInPresets = this.parentDialog.manager.rm.presetCategories; - - // Duplicate the preset - const success = this.parentDialog.manager.duplicatePreset( - sourceCategory, - sourceName, - category, - targetName, - builtInPresets - ); - - if (success) { - // If duplicated to same category, reorder it to the target position - if (sourceCategory === category) { - this.parentDialog.manager.reorderPresets(category, targetName, targetIndex); - } - } - } else { - // MOVE MODE: Move or reorder the preset - if (this.parentDialog.draggedPresetCategory === category) { - this.parentDialog.manager.reorderPresets(category, this.parentDialog.draggedPresetName, targetIndex); - } else { - this.parentDialog.manager.movePreset(this.parentDialog.draggedPresetCategory, this.parentDialog.draggedPresetName, category, targetIndex); - } - } - - this.parentDialog.renderDialog(); - } - }); - } - - /** - * Creates clone handle for duplicate drag & drop - */ - createCloneHandle(category, name, dims, presetIndex) { - const cloneHandle = document.createElement('div'); - cloneHandle.className = 'resolution-master-preset-list-clone-handle'; - cloneHandle.draggable = true; - cloneHandle.innerHTML = getIconHtml(this.parentDialog.dragAndDuplicateIcon, '⊕', 16); - - // Prevent the clone handle from being selected as text - cloneHandle.style.userSelect = 'none'; - cloneHandle.style.cursor = 'grab'; - - // Clone-specific drag handlers - cloneHandle.addEventListener('dragstart', (e) => { - // Set clone/duplicate mode - this.parentDialog.isDuplicateMode = true; - this.parentDialog.draggedPresetName = name; - this.parentDialog.draggedPresetCategory = category; - this.parentDialog.draggedPresetDims = dims; - - // Visual feedback - const item = cloneHandle.closest('.resolution-master-preset-list-item'); - if (item) { - item.style.opacity = '0.5'; - } - - e.dataTransfer.effectAllowed = 'copy'; - e.dataTransfer.setData('text/html', cloneHandle.innerHTML); - e.stopPropagation(); // Prevent parent item drag - }); - - cloneHandle.addEventListener('dragend', (e) => { - // Reset clone mode - this.parentDialog.isDuplicateMode = false; - - // Reset visual feedback - const item = cloneHandle.closest('.resolution-master-preset-list-item'); - if (item) { - item.style.opacity = '1'; - } - - DragDropHandler.clearDropIndicator(item); - this.parentDialog.draggedPresetName = null; - this.parentDialog.draggedPresetCategory = null; - this.parentDialog.draggedPresetDims = null; - e.stopPropagation(); - }); - - // Prevent click from bubbling - cloneHandle.addEventListener('click', (e) => { - e.stopPropagation(); - }); - - // Prevent double-click from bubbling - cloneHandle.addEventListener('dblclick', (e) => { - e.stopPropagation(); - }); - - return cloneHandle; - } - - /** - * Creates checkbox for bulk deletion - */ - createBulkDeleteCheckbox(category, name) { - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.className = 'resolution-master-preset-list-checkbox'; - const presetKey = `${category}|${name}`; - checkbox.dataset.presetKey = presetKey; - checkbox.checked = this.parentDialog.selectedPresetsForDeletion.has(presetKey); - checkbox.addEventListener('click', (e) => { - if (e.shiftKey && this.parentDialog.lastClickedPresetKey) { - this.parentDialog.handleShiftClickSelection(presetKey, checkbox.checked); - } else { - if (checkbox.checked) { - this.parentDialog.selectedPresetsForDeletion.add(presetKey); - } else { - this.parentDialog.selectedPresetsForDeletion.delete(presetKey); - } - this.parentDialog.lastClickedPresetKey = presetKey; - } - - this.parentDialog.updateDeleteSelectedButton(); - e.stopPropagation(); - }); - - return checkbox; - } - - /** - * Creates preset info section - */ - createPresetInfo(category, name, dims) { - const info = document.createElement('div'); - info.className = 'resolution-master-preset-list-info'; - - const nameContainer = document.createElement('span'); - nameContainer.className = 'resolution-master-preset-list-name-container'; - - const nameElement = document.createElement('strong'); - nameElement.className = 'resolution-master-preset-list-name'; - nameElement.textContent = name; - // Tooltip handled by TooltipManager - - nameElement.addEventListener('dblclick', (e) => { - e.stopPropagation(); - this.parentDialog.renameDialogManager.startRenamingPreset(nameElement, category, name, dims); - }); - - nameContainer.appendChild(nameElement); - - const customIcon = getIconHtml(this.parentDialog.customPresetIcon, '', 14, 'margin-left: 6px; vertical-align: middle;'); - if (customIcon) { - const iconSpan = document.createElement('span'); - iconSpan.innerHTML = customIcon; - nameContainer.appendChild(iconSpan); - } - - const dimsSpan = document.createElement('span'); - dimsSpan.className = 'resolution-master-preset-list-dims'; - dimsSpan.textContent = `(${dims.width}×${dims.height})`; - - info.appendChild(nameContainer); - info.appendChild(dimsSpan); - - return info; - } - - /** - * Creates action buttons for preset item - */ - createActionButtons(category, name, dims, presetIndex) { - const actions = document.createElement('div'); - actions.className = 'resolution-master-preset-list-actions'; - - // Clone handle (first button) - const cloneHandle = this.createCloneHandle(category, name, dims, presetIndex); - actions.appendChild(cloneHandle); - - // Edit button - const editIconHtml = getIconHtml(this.parentDialog.editIcon, '✏️'); - const editBtn = PresetUIComponents.createActionButton(editIconHtml, 'Edit', () => { - this.parentDialog.editPreset(category, name, dims); - }); - editBtn.classList.add('resolution-master-preset-list-edit-btn'); - actions.appendChild(editBtn); - - // Delete button - const deleteIcon = getIconHtml(this.parentDialog.deleteIcon, '🗑️'); - const deleteBtn = PresetUIComponents.createActionButton(deleteIcon, 'Delete', () => { - this.parentDialog.deletePreset(category, name); - }); - deleteBtn.classList.add('resolution-master-preset-list-delete-btn'); - actions.appendChild(deleteBtn); - - return actions; - } -} diff --git a/js/utils/preset-manager/PresetUIComponents.js b/js/utils/preset-manager/PresetUIComponents.js deleted file mode 100644 index c5d729c..0000000 --- a/js/utils/preset-manager/PresetUIComponents.js +++ /dev/null @@ -1,157 +0,0 @@ -// PresetUIComponents.js - UI component creation helpers for PresetManagerDialog - -/** - * Helper class for creating UI components used in PresetManagerDialog - */ -export class PresetUIComponents { - /** - * Adds hover effects to a button element - * @param {HTMLElement} button - The button element - * @param {string} hoverBg - Background color on hover - * @param {string} normalBg - Normal background color - * @param {string} hoverBorder - Border color on hover (optional) - * @param {string} normalBorder - Normal border color (optional) - */ - static addButtonHoverEffects(button, hoverBg, normalBg, hoverBorder = null, normalBorder = null) { - button.addEventListener('mouseenter', () => { - button.style.background = hoverBg; - if (hoverBorder) button.style.borderColor = hoverBorder; - }); - button.addEventListener('mouseleave', () => { - button.style.background = normalBg; - if (normalBorder) button.style.borderColor = normalBorder; - }); - } - - /** - * Creates a form group (label + input) - * @param {string} label - Label text - * @param {string} id - Input ID - * @param {string} type - Input type - * @param {string} placeholder - Placeholder text - * @param {string} value - Initial value - * @returns {HTMLElement} The form group element - */ - static createFormGroup(label, id, type, placeholder, value = '') { - const group = document.createElement('div'); - group.className = 'resolution-master-preset-ui-form-group'; - - const labelEl = document.createElement('label'); - labelEl.htmlFor = id; - labelEl.textContent = label; - labelEl.className = 'resolution-master-preset-ui-form-label'; - - const input = document.createElement('input'); - input.id = id; - input.type = type; - input.placeholder = placeholder; - input.value = value; - input.className = 'resolution-master-preset-ui-form-input'; - - if (type === 'number') { - input.min = '64'; - input.step = '1'; - } - - group.appendChild(labelEl); - group.appendChild(input); - - return group; - } - - /** - * Creates an action button for preset items - * @param {string} icon - Icon (text or HTML) - * @param {string} tooltip - Tooltip text - * @param {Function} onClick - Click handler - * @returns {HTMLElement} The button element - */ - static createActionButton(icon, tooltip, onClick) { - const btn = document.createElement('button'); - btn.className = 'resolution-master-preset-ui-action-btn'; - - // Support both text icons and HTML (for SVG icons) - if (icon.includes(' { - e.stopPropagation(); - onClick(); - }); - - return btn; - } - - /** - * Creates a footer button - * @param {string} text - Button text (can include HTML for icons) - * @param {string} style - Button style ('primary' or 'secondary') - * @param {Function} onClick - Click handler - * @returns {HTMLElement} The button element - */ - static createFooterButton(text, style, onClick) { - const btn = document.createElement('button'); - btn.className = `resolution-master-preset-ui-footer-btn resolution-master-preset-ui-footer-btn-${style}`; - - // Support both text and HTML (for SVG icons) - if (text.includes('`; - } else { - deleteBtn.textContent = '🗑️'; - } - - deleteBtn.addEventListener('click', (e) => { - e.stopPropagation(); - onDelete(); - }); - - card.appendChild(nameDiv); - card.appendChild(dimsDiv); - card.appendChild(deleteBtn); - - return card; - } -} diff --git a/js/utils/preset-manager/RenameDialogManager.js b/js/utils/preset-manager/RenameDialogManager.js deleted file mode 100644 index d011a53..0000000 --- a/js/utils/preset-manager/RenameDialogManager.js +++ /dev/null @@ -1,367 +0,0 @@ -// RenameDialogManager.js - Rename dialogs for categories and presets - -import { getIconHtml } from "../IconUtils.js"; - -/** - * Manager for rename dialogs (categories and presets) - */ -export class RenameDialogManager { - constructor(parentDialog) { - this.parentDialog = parentDialog; - } - - /** - * Shows a dialog to rename the category - * @param {string} currentCategoryName - Current category name - */ - showRenameCategoryDialog(currentCategoryName) { - // Create overlay - const overlay = document.createElement('div'); - overlay.className = 'resolution-master-rename-dialog-overlay'; - overlay.addEventListener('mousedown', () => { - document.body.removeChild(overlay); - document.body.removeChild(dialog); - }); - document.body.appendChild(overlay); - - // Create dialog container - const dialog = document.createElement('div'); - dialog.className = 'resolution-master-rename-dialog'; - dialog.addEventListener('mousedown', (e) => e.stopPropagation()); // Prevent clicks inside from closing - - // Create dialog content - dialog.innerHTML = ` -
Rename Category
-
- - -
-
-
- - -
- `; - - document.body.appendChild(dialog); - - // Get elements - const input = dialog.querySelector('#renameCategoryInput'); - const validationMsg = dialog.querySelector('#renameValidationMessage'); - const cancelBtn = dialog.querySelector('#renameCancelBtn'); - const applyBtn = dialog.querySelector('#renameApplyBtn'); - - // Focus and select input - setTimeout(() => { - input.focus(); - input.select(); - }, 50); - - // Real-time validation - const validateInput = () => { - const newName = input.value.trim(); - - if (!newName) { - validationMsg.textContent = 'Category name cannot be empty'; - applyBtn.disabled = true; - applyBtn.style.opacity = '0.5'; - applyBtn.style.cursor = 'not-allowed'; - return false; - } - - if (newName === currentCategoryName) { - validationMsg.textContent = ''; - applyBtn.disabled = true; - applyBtn.style.opacity = '0.5'; - applyBtn.style.cursor = 'not-allowed'; - return false; - } - - // Check if category already exists - const customPresets = this.parentDialog.manager.getCustomPresets(); - if (customPresets[newName]) { - validationMsg.textContent = `Category "${newName}" already exists`; - applyBtn.disabled = true; - applyBtn.style.opacity = '0.5'; - applyBtn.style.cursor = 'not-allowed'; - return false; - } - - validationMsg.textContent = ''; - applyBtn.disabled = false; - applyBtn.style.opacity = '1'; - applyBtn.style.cursor = 'pointer'; - return true; - }; - - // Apply rename function - const applyRename = () => { - if (!validateInput()) return; - - const trimmedNewName = input.value.trim(); - - // Try to rename - const success = this.parentDialog.manager.renameCategory(currentCategoryName, trimmedNewName); - - if (success) { - // Success - update selected category and refresh dialog - this.parentDialog.selectedCategory = trimmedNewName; - document.body.removeChild(overlay); - document.body.removeChild(dialog); - this.parentDialog.renderDialog(); - } else { - // Failed - show error in validation message - validationMsg.textContent = `Failed to rename category. Check browser console for details.`; - } - }; - - // Event listeners - input.addEventListener('input', validateInput); - input.addEventListener('keydown', (e) => { - if (e.key === 'Enter' && validateInput()) { - applyRename(); - } else if (e.key === 'Escape') { - document.body.removeChild(overlay); - document.body.removeChild(dialog); - } - }); - - cancelBtn.addEventListener('click', () => { - document.body.removeChild(overlay); - document.body.removeChild(dialog); - }); - - applyBtn.addEventListener('click', applyRename); - - // Initial validation - validateInput(); - } - - /** - * Starts renaming a category by converting title to input - * @param {HTMLElement} titleElement - The category title element - * @param {string} categoryName - Current category name - */ - startRenamingCategory(titleElement, categoryName) { - const originalText = titleElement.textContent; - const categoryNameOnly = categoryName; // Without the count - - // Create input field - const input = document.createElement('input'); - input.type = 'text'; - input.value = categoryNameOnly; - input.className = 'resolution-master-rename-inline-category-input'; - - // Replace title with input - titleElement.replaceWith(input); - input.focus(); - input.select(); - - // Flag to prevent multiple saves - let isSaving = false; - - // Handle Enter key - save - const handleSave = () => { - // Prevent multiple calls - if (isSaving) return; - isSaving = true; - - const newName = input.value.trim(); - - if (!newName) { - // Empty name - restore original - const newTitle = this.recreateCategoryTitleElement(originalText, categoryNameOnly); - input.replaceWith(newTitle); - return; - } - - if (newName === categoryNameOnly) { - // No change - restore original - const newTitle = this.recreateCategoryTitleElement(originalText, categoryNameOnly); - input.replaceWith(newTitle); - return; - } - - // Generate unique name if needed (auto-add suffix like "(1)", "(2)", etc.) - let finalNewName = newName; - const customPresets = this.parentDialog.manager.getCustomPresets(); - - // If category name already exists (and it's not the same category), add suffix - if (customPresets[finalNewName] && finalNewName !== categoryNameOnly) { - let counter = 1; - while (customPresets[`${newName} (${counter})`]) { - counter++; - } - finalNewName = `${newName} (${counter})`; - } - - // Try to rename with the final unique name - const success = this.parentDialog.manager.renameCategory(categoryNameOnly, finalNewName); - - if (success) { - // Success - refresh dialog - this.parentDialog.renderDialog(); - } else { - // Failed - show error and keep editing - alert(`Cannot rename category to "${finalNewName}".\n\nOld name: "${categoryNameOnly}"\nCheck browser console for details.`); - input.focus(); - input.select(); - } - }; - - // Handle Escape key - cancel - const handleCancel = () => { - const newTitle = this.recreateCategoryTitleElement(originalText, categoryNameOnly); - input.replaceWith(newTitle); - }; - - // Event listeners - input.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - handleSave(); - } else if (e.key === 'Escape') { - e.preventDefault(); - handleCancel(); - } - }); - - input.addEventListener('blur', () => { - // Save on blur - handleSave(); - }); - } - - /** - * Recreates category title element - * @param {string} text - Title text - * @param {string} categoryName - Category name - * @returns {HTMLElement} The title element - */ - recreateCategoryTitleElement(text, categoryName) { - const newTitle = document.createElement('div'); - newTitle.className = 'resolution-master-rename-category-title'; - newTitle.textContent = text; - // Tooltip handled by TooltipManager - newTitle.addEventListener('dblclick', () => { - this.startRenamingCategory(newTitle, categoryName); - }); - return newTitle; - } - - /** - * Starts renaming a preset by converting name to input - * @param {HTMLElement} nameElement - The preset name element - * @param {string} category - Category name - * @param {string} presetName - Current preset name - * @param {Object} dims - Dimensions {width, height} - */ - startRenamingPreset(nameElement, category, presetName, dims) { - const originalText = nameElement.textContent; - - // Create input field - const input = document.createElement('input'); - input.type = 'text'; - input.value = presetName; - input.className = 'resolution-master-rename-inline-preset-input'; - - // Replace name with input - nameElement.replaceWith(input); - input.focus(); - input.select(); - - // Flag to prevent multiple saves - let isSaving = false; - - // Handle Enter key - save - const handleSave = () => { - // Prevent multiple calls - if (isSaving) return; - isSaving = true; - - const newName = input.value.trim(); - - if (!newName) { - // Empty name - restore original - const newNameElement = this.recreatePresetNameElement(originalText, category, presetName, dims); - input.replaceWith(newNameElement); - return; - } - - if (newName === presetName) { - // No change - restore original - const newNameElement = this.recreatePresetNameElement(originalText, category, presetName, dims); - input.replaceWith(newNameElement); - return; - } - - // Check if new name already exists in category - const customPresets = this.parentDialog.manager.getCustomPresets(); - const categoryPresets = customPresets[category] || {}; - if (Object.keys(categoryPresets).includes(newName)) { - // Name exists - show error and keep editing - alert(`Preset "${newName}" already exists in category "${category}".\n\nPlease choose a different name.`); - isSaving = false; // Reset flag to allow retry - input.focus(); - input.select(); - return; - } - - // Try to rename using updatePreset - const success = this.parentDialog.manager.updatePreset(category, presetName, newName, dims.width, dims.height); - - if (success) { - // Success - refresh dialog - this.parentDialog.renderDialog(); - } else { - // Failed - show error and keep editing - alert(`Cannot rename preset to "${newName}".\n\nOld name: "${presetName}"\nCheck browser console for details.`); - isSaving = false; // Reset flag to allow retry - input.focus(); - input.select(); - } - }; - - // Handle Escape key - cancel - const handleCancel = () => { - const newNameElement = this.recreatePresetNameElement(originalText, category, presetName, dims); - input.replaceWith(newNameElement); - }; - - // Event listeners - input.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - handleSave(); - } else if (e.key === 'Escape') { - e.preventDefault(); - handleCancel(); - } - }); - - input.addEventListener('blur', () => { - // Save on blur - handleSave(); - }); - } - - /** - * Recreates preset name element - * @param {string} text - Name text - * @param {string} category - Category name - * @param {string} presetName - Preset name - * @param {Object} dims - Dimensions - * @returns {HTMLElement} The name element - */ - recreatePresetNameElement(text, category, presetName, dims) { - const newNameElement = document.createElement('strong'); - newNameElement.className = 'resolution-master-rename-preset-name'; - newNameElement.textContent = text; - // Tooltip handled by TooltipManager - newNameElement.addEventListener('dblclick', (e) => { - e.stopPropagation(); - this.startRenamingPreset(newNameElement, category, presetName, dims); - }); - return newNameElement; - } -} diff --git a/js/utils/preset-manager/TooltipManager.js b/js/utils/preset-manager/TooltipManager.js deleted file mode 100644 index f097e6b..0000000 --- a/js/utils/preset-manager/TooltipManager.js +++ /dev/null @@ -1,312 +0,0 @@ -// TooltipManager.js - DOM-based tooltip system adapted from ResolutionMaster.js - -/** - * Manager for displaying tooltips on DOM elements - * Adapted from the canvas-based tooltip system in ResolutionMaster.js - */ -export class TooltipManager { - constructor(options = {}) { - this.tooltipDelay = options.delay || 500; // ms - delay before showing tooltip - this.maxWidth = options.maxWidth || 250; // Maximum tooltip width - this.tooltipElement = null; // The DOM element for the tooltip - this.tooltipTimer = null; // Timer for tooltip delay - this.currentTarget = null; // Currently hovered element - this.tooltips = options.tooltips || {}; // Map of element IDs/classes to tooltip text - - // Create tooltip DOM element - this.createTooltipElement(); - - // Bind methods - this.handleMouseEnter = this.handleMouseEnter.bind(this); - this.handleMouseLeave = this.handleMouseLeave.bind(this); - this.handleMouseMove = this.handleMouseMove.bind(this); - this.handleClick = this.handleClick.bind(this); - } - - /** - * Creates the tooltip DOM element - */ - createTooltipElement() { - this.tooltipElement = document.createElement('div'); - this.tooltipElement.className = 'tooltip-manager-tooltip'; - this.tooltipElement.style.cssText = ` - position: fixed; - z-index: 100000; - pointer-events: none; - opacity: 0; - transition: opacity 0.2s; - max-width: ${this.maxWidth}px; - padding: 8px; - background: linear-gradient(to bottom, rgba(45, 45, 45, 0.95), rgba(35, 35, 35, 0.95)); - border: 1px solid rgba(200, 200, 200, 0.3); - border-radius: 6px; - box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); - color: #ffffff; - font-family: Arial, sans-serif; - font-size: 12px; - line-height: 16px; - word-wrap: break-word; - white-space: normal; - `; - document.body.appendChild(this.tooltipElement); - } - - /** - * Registers tooltip text for an element - * @param {string} elementId - Element identifier (ID, class, or data attribute) - * @param {string} tooltipText - Tooltip text to display - */ - registerTooltip(elementId, tooltipText) { - this.tooltips[elementId] = tooltipText; - } - - /** - * Registers multiple tooltips at once - * @param {Object} tooltips - Map of element IDs to tooltip texts - */ - registerTooltips(tooltips) { - Object.assign(this.tooltips, tooltips); - } - - /** - * Attaches tooltip handlers to an element - * @param {HTMLElement} element - Element to attach tooltip to - * @param {string} tooltipText - Tooltip text (optional if already registered) - */ - attach(element, tooltipText = null) { - if (!element) return; - - // Register tooltip text if provided - if (tooltipText) { - const elementId = element.id || element.className || element.dataset.tooltipId; - if (elementId) { - this.registerTooltip(elementId, tooltipText); - } - // Store directly on element as fallback - element.dataset.tooltipText = tooltipText; - } - - element.addEventListener('mouseenter', this.handleMouseEnter); - element.addEventListener('mouseleave', this.handleMouseLeave); - element.addEventListener('mousemove', this.handleMouseMove); - element.addEventListener('click', this.handleClick); // Hide tooltip on click without clearing state - } - - /** - * Detaches tooltip handlers from an element - * @param {HTMLElement} element - Element to detach from - */ - detach(element) { - if (!element) return; - - element.removeEventListener('mouseenter', this.handleMouseEnter); - element.removeEventListener('mouseleave', this.handleMouseLeave); - element.removeEventListener('mousemove', this.handleMouseMove); - element.removeEventListener('click', this.handleClick); - } - - /** - * Handles mouse enter event - * @param {MouseEvent} e - Mouse event - */ - handleMouseEnter(e) { - const element = e.currentTarget; - this.currentTarget = element; - - // Get tooltip text - const tooltipText = this.getTooltipText(element); - if (!tooltipText) return; - - // Clear any existing timer - if (this.tooltipTimer) { - clearTimeout(this.tooltipTimer); - } - - // Start new timer - this.tooltipTimer = setTimeout(() => { - this.showTooltip(element, tooltipText, e); - }, this.tooltipDelay); - } - - /** - * Handles mouse leave event - * @param {MouseEvent} e - Mouse event - */ - handleMouseLeave(e) { - // Clear timer - if (this.tooltipTimer) { - clearTimeout(this.tooltipTimer); - this.tooltipTimer = null; - } - - // Hide tooltip - this.hideTooltip(); - this.currentTarget = null; - } - - /** - * Handles mouse move event - * @param {MouseEvent} e - Mouse event - */ - handleMouseMove(e) { - // Update tooltip position if visible - if (this.tooltipElement.style.opacity === '1') { - this.positionTooltip(e); - } - } - - /** - * Handles click event - hides tooltip without clearing currentTarget - * @param {MouseEvent} e - Mouse event - */ - handleClick(e) { - // Clear timer - if (this.tooltipTimer) { - clearTimeout(this.tooltipTimer); - this.tooltipTimer = null; - } - - // Hide tooltip but don't clear currentTarget - // This allows tooltips to work on subsequent elements - this.hideTooltip(); - } - - /** - * Gets tooltip text for an element - * @param {HTMLElement} element - Element to get tooltip for - * @returns {string|null} Tooltip text - */ - getTooltipText(element) { - // Try data attribute first - if (element.dataset.tooltipText) { - return element.dataset.tooltipText; - } - - // Try registered tooltips by ID - if (element.id && this.tooltips[element.id]) { - const tooltip = this.tooltips[element.id]; - // Check if it's a nested object (for action-specific tooltips) - if (typeof tooltip === 'object' && tooltip !== null && !Array.isArray(tooltip)) { - return this.resolveNestedTooltip(element, tooltip); - } - return tooltip; - } - - // Try registered tooltips by individual classes - if (element.classList && element.classList.length > 0) { - for (const cls of element.classList) { - if (this.tooltips[cls]) { - const tooltip = this.tooltips[cls]; - // Check if it's a nested object (for action-specific tooltips) - if (typeof tooltip === 'object' && tooltip !== null && !Array.isArray(tooltip)) { - return this.resolveNestedTooltip(element, tooltip); - } - return tooltip; - } - } - } - - // Try data-tooltip-id attribute - if (element.dataset.tooltipId && this.tooltips[element.dataset.tooltipId]) { - const tooltip = this.tooltips[element.dataset.tooltipId]; - // Check if it's a nested object (for action-specific tooltips) - if (typeof tooltip === 'object' && tooltip !== null && !Array.isArray(tooltip)) { - return this.resolveNestedTooltip(element, tooltip); - } - return tooltip; - } - - return null; - } - - /** - * Resolves a nested tooltip object by checking element classes - * @param {HTMLElement} element - Element to check - * @param {Object} tooltipObj - Nested tooltip object - * @returns {string|null} Resolved tooltip text - */ - resolveNestedTooltip(element, tooltipObj) { - // Check other classes for action type (delete, hide, unhide, etc.) - if (element.classList && element.classList.length > 0) { - for (const actionCls of element.classList) { - if (tooltipObj[actionCls]) { - return tooltipObj[actionCls]; - } - } - } - return null; - } - - /** - * Shows the tooltip - * @param {HTMLElement} element - Element being hovered - * @param {string} text - Tooltip text - * @param {MouseEvent} e - Mouse event - */ - showTooltip(element, text, e) { - this.tooltipElement.textContent = text; - this.positionTooltip(e); - this.tooltipElement.style.opacity = '1'; - } - - /** - * Hides the tooltip - */ - hideTooltip() { - if (!this.tooltipElement) return; - this.tooltipElement.style.opacity = '0'; - } - - /** - * Positions the tooltip relative to mouse - * @param {MouseEvent} e - Mouse event - */ - positionTooltip(e) { - const tooltip = this.tooltipElement; - const mouseX = e.clientX; - const mouseY = e.clientY; - - // Get tooltip dimensions - const rect = tooltip.getBoundingClientRect(); - const tooltipWidth = rect.width; - const tooltipHeight = rect.height; - - // Default position: right and above mouse - let tooltipX = mouseX + 15; - let tooltipY = mouseY - tooltipHeight - 10; - - // Adjust if tooltip would go off screen - if (tooltipX + tooltipWidth > window.innerWidth) { - tooltipX = mouseX - tooltipWidth - 15; - } - - if (tooltipY < 0) { - tooltipY = mouseY + 20; - } - - // Ensure tooltip stays within viewport - tooltipX = Math.max(5, Math.min(tooltipX, window.innerWidth - tooltipWidth - 5)); - tooltipY = Math.max(5, Math.min(tooltipY, window.innerHeight - tooltipHeight - 5)); - - tooltip.style.left = `${tooltipX}px`; - tooltip.style.top = `${tooltipY}px`; - } - - /** - * Cleans up the tooltip manager - */ - destroy() { - if (this.tooltipTimer) { - clearTimeout(this.tooltipTimer); - this.tooltipTimer = null; - } - - if (this.tooltipElement && this.tooltipElement.parentNode) { - this.tooltipElement.parentNode.removeChild(this.tooltipElement); - } - - this.tooltipElement = null; - this.currentTarget = null; - this.tooltips = {}; - } -} diff --git a/pyproject.toml b/pyproject.toml index 761af56..2a11a07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,36 @@ [project] -name = "Comfyui-Resolution-Master" -description = "ResolutionMaster is for total control over resolution and aspect ratio. It provides an intuitive interface with an interactive canvas, advanced scaling options, extensive presets (SDXL, Flux, WAN), and model-specific optimizations for high-quality AI image generation." -version = "1.5.7" -license = { text = "MIT License" } +name = "resolution-master" +description = " Precise resolution and aspect ratio control for ComfyUI" +version = "1.0.0" +license = {file = "LICENSE"} +# classifiers = [ +# # For OS-independent nodes (works on all operating systems) +# "Operating System :: OS Independent", +# +# # OR for OS-specific nodes, specify the supported systems: +# "Operating System :: Microsoft :: Windows", # Windows specific +# "Operating System :: POSIX :: Linux", # Linux specific +# "Operating System :: MacOS", # macOS specific +# +# # GPU Accelerator support. Pick the ones that are supported by your extension. +# "Environment :: GPU :: NVIDIA CUDA", # NVIDIA CUDA support +# "Environment :: GPU :: AMD ROCm", # AMD ROCm support +# "Environment :: GPU :: Intel Arc", # Intel Arc support +# "Environment :: NPU :: Huawei Ascend", # Huawei Ascend support +# "Environment :: GPU :: Apple Metal", # Apple Metal support +# ] + [project.urls] Repository = "https://github.com/Azornes/Comfyui-Resolution-Master" +# Used by Comfy Registry https://registry.comfy.org +Documentation = "https://github.com/Azornes/Comfyui-Resolution-Master/wiki" +"Bug Tracker" = "https://github.com/Azornes/Comfyui-Resolution-Master/issues" [tool.comfy] -PublisherId = "azornes" +PublisherId = "" DisplayName = "Comfyui-Resolution-Master" Icon = "" +includes = [] +# "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility +