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 = `
-