From c4318d492383e0d31d1b12e65ec93a470061534d Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 3 Aug 2025 14:18:21 +0200 Subject: [PATCH] Refactor: Move blend mode menu styles to CSS file Moved all blend mode menu styles from CanvasLayers.ts to a dedicated CSS file. Replaced inline styles with CSS classes and preserved all functionality. --- js/CanvasIO.js | 3 +- js/CanvasLayers.js | 92 +++++-------------- js/css/blend_mode_menu.css | 170 ++++++++++++++++++++++++++++++++++++ src/CanvasIO.ts | 5 +- src/CanvasLayers.ts | 95 +++++--------------- src/css/blend_mode_menu.css | 170 ++++++++++++++++++++++++++++++++++++ 6 files changed, 389 insertions(+), 146 deletions(-) create mode 100644 js/css/blend_mode_menu.css create mode 100644 src/css/blend_mode_menu.css diff --git a/js/CanvasIO.js b/js/CanvasIO.js index 303666a..c252e13 100644 --- a/js/CanvasIO.js +++ b/js/CanvasIO.js @@ -238,7 +238,8 @@ export class CanvasIO { } catch (error) { log.error(`Failed to send data for node ${nodeId}:`, error); - throw new Error(`Failed to get confirmation from server for node ${nodeId}. The workflow might not have the latest canvas data.`); + throw new Error(`Failed to get confirmation from server for node ${nodeId}. ` + + `Make sure that the nodeId: (${nodeId}) matches the "node_id" value in the node options. If they don't match, you may need to manually set the node_id to ${nodeId}.`); } } async addInputToCanvas(inputImage, inputMask) { diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index 3cb89a3..dac95e6 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -3,6 +3,7 @@ import { createModuleLogger } from "./utils/LoggerUtils.js"; import { generateUUID, generateUniqueFileName, createCanvas } from "./utils/CommonUtils.js"; import { withErrorHandling, createValidationError } from "./ErrorHandler.js"; import { showErrorNotification } from "./utils/NotificationUtils.js"; +import { addStylesheet, getUrl } from "./utils/ResourceManager.js"; // @ts-ignore import { app } from "../../scripts/app.js"; // @ts-ignore @@ -122,6 +123,8 @@ export class CanvasLayers { this.isAdjustingOpacity = false; this.internalClipboard = []; this.clipboardPreference = 'system'; + // Load CSS for blend mode menu + addStylesheet(getUrl('./css/blend_mode_menu.css')); } async copySelectedLayers() { if (this.canvas.canvasSelection.selectedLayers.length === 0) @@ -785,65 +788,14 @@ export class CanvasLayers { const menu = document.createElement('div'); this.blendMenuElement = menu; menu.id = 'blend-mode-menu'; - menu.style.cssText = ` - position: absolute; - top: 0; - left: 0; - background: #2a2a2a; - border: 1px solid #3a3a3a; - border-radius: 4px; - z-index: 10000; - box-shadow: 0 2px 10px rgba(0,0,0,0.3); - min-width: 200px; - `; const titleBar = document.createElement('div'); - titleBar.style.cssText = ` - background: #3a3a3a; - color: white; - padding: 8px 10px; - cursor: move; - user-select: none; - border-radius: 3px 3px 0 0; - font-size: 12px; - font-weight: bold; - border-bottom: 1px solid #4a4a4a; - display: flex; - justify-content: space-between; - align-items: center; - `; + titleBar.className = 'blend-menu-title-bar'; const titleText = document.createElement('span'); titleText.textContent = `Blend Mode: ${selectedLayer.name}`; - titleText.style.cssText = ` - flex: 1; - cursor: move; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - `; + titleText.className = 'blend-menu-title-text'; const closeButton = document.createElement('button'); closeButton.textContent = '×'; - closeButton.style.cssText = ` - background: none; - border: none; - color: white; - font-size: 18px; - cursor: pointer; - padding: 0; - margin: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 3px; - transition: background-color 0.2s; - `; - closeButton.onmouseover = () => { - closeButton.style.backgroundColor = '#4a4a4a'; - }; - closeButton.onmouseout = () => { - closeButton.style.backgroundColor = 'transparent'; - }; + closeButton.className = 'blend-menu-close-button'; closeButton.onclick = (e) => { e.stopPropagation(); this.closeBlendModeMenu(); @@ -851,18 +803,19 @@ export class CanvasLayers { titleBar.appendChild(titleText); titleBar.appendChild(closeButton); const content = document.createElement('div'); - content.style.cssText = `padding: 5px;`; + content.className = 'blend-menu-content'; menu.appendChild(titleBar); menu.appendChild(content); const blendAreaContainer = document.createElement('div'); - blendAreaContainer.style.cssText = `padding: 5px 10px; border-bottom: 1px solid #4a4a4a;`; + blendAreaContainer.className = 'blend-area-container'; const blendAreaLabel = document.createElement('label'); blendAreaLabel.textContent = 'Blend Area'; - blendAreaLabel.style.color = 'white'; + blendAreaLabel.className = 'blend-area-label'; const blendAreaSlider = document.createElement('input'); blendAreaSlider.type = 'range'; blendAreaSlider.min = '0'; blendAreaSlider.max = '100'; + blendAreaSlider.className = 'blend-area-slider'; blendAreaSlider.value = selectedLayer?.blendArea?.toString() ?? '0'; blendAreaSlider.oninput = () => { if (selectedLayer) { @@ -912,20 +865,19 @@ export class CanvasLayers { this.blendModes.forEach((mode) => { const container = document.createElement('div'); container.className = 'blend-mode-container'; - container.style.cssText = `margin-bottom: 5px;`; const option = document.createElement('div'); - option.style.cssText = `padding: 5px 10px; color: white; cursor: pointer; transition: background-color 0.2s;`; + option.className = 'blend-mode-option'; option.textContent = `${mode.label} (${mode.name})`; const slider = document.createElement('input'); slider.type = 'range'; slider.min = '0'; slider.max = '100'; + slider.className = 'blend-opacity-slider'; const selectedLayer = this.canvas.canvasSelection.selectedLayers[0]; slider.value = selectedLayer ? String(Math.round(selectedLayer.opacity * 100)) : '100'; - slider.style.cssText = `width: 100%; margin: 5px 0; display: none;`; if (selectedLayer && selectedLayer.blendMode === mode.name) { - slider.style.display = 'block'; - option.style.backgroundColor = '#3a3a3a'; + container.classList.add('active'); + option.classList.add('active'); } option.onclick = () => { // Re-check selected layer at the time of click @@ -933,19 +885,17 @@ export class CanvasLayers { if (!currentSelectedLayer) { return; } - // Hide only the opacity sliders within other blend mode containers + // Remove active class from all containers and options content.querySelectorAll('.blend-mode-container').forEach(c => { - const opacitySlider = c.querySelector('input[type="range"]'); - if (opacitySlider) { - opacitySlider.style.display = 'none'; - } - const optionDiv = c.querySelector('div'); + c.classList.remove('active'); + const optionDiv = c.querySelector('.blend-mode-option'); if (optionDiv) { - optionDiv.style.backgroundColor = ''; + optionDiv.classList.remove('active'); } }); - slider.style.display = 'block'; - option.style.backgroundColor = '#3a3a3a'; + // Add active class to current container and option + container.classList.add('active'); + option.classList.add('active'); currentSelectedLayer.blendMode = mode.name; this.canvas.render(); }; diff --git a/js/css/blend_mode_menu.css b/js/css/blend_mode_menu.css new file mode 100644 index 0000000..a98eafb --- /dev/null +++ b/js/css/blend_mode_menu.css @@ -0,0 +1,170 @@ +/* Blend Mode Menu Styles */ +#blend-mode-menu { + position: absolute; + top: 0; + left: 0; + background: #2a2a2a; + border: 1px solid #3a3a3a; + border-radius: 4px; + z-index: 10000; + box-shadow: 0 2px 10px rgba(0,0,0,0.3); + min-width: 200px; +} + +#blend-mode-menu .blend-menu-title-bar { + background: #3a3a3a; + color: white; + padding: 8px 10px; + cursor: move; + user-select: none; + border-radius: 3px 3px 0 0; + font-size: 12px; + font-weight: bold; + border-bottom: 1px solid #4a4a4a; + display: flex; + justify-content: space-between; + align-items: center; +} + +#blend-mode-menu .blend-menu-title-text { + flex: 1; + cursor: move; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#blend-mode-menu .blend-menu-close-button { + background: none; + border: none; + color: white; + font-size: 18px; + cursor: pointer; + padding: 0; + margin: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + transition: background-color 0.2s; +} + +#blend-mode-menu .blend-menu-close-button:hover { + background-color: #4a4a4a; +} + +#blend-mode-menu .blend-menu-close-button:focus { + background-color: transparent; +} + +#blend-mode-menu .blend-menu-content { + padding: 5px; +} + +#blend-mode-menu .blend-area-container { + padding: 5px 10px; + border-bottom: 1px solid #4a4a4a; +} + +#blend-mode-menu .blend-area-label { + color: white; + display: block; + margin-bottom: 5px; + font-size: 12px; +} + +#blend-mode-menu .blend-area-slider { + width: 100%; + margin: 5px 0; + -webkit-appearance: none; + height: 4px; + background: #555; + border-radius: 2px; + outline: none; +} + +#blend-mode-menu .blend-area-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; + transition: background 0.2s; +} + +#blend-mode-menu .blend-area-slider::-webkit-slider-thumb:hover { + background: #fff; +} + +#blend-mode-menu .blend-area-slider::-moz-range-thumb { + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; +} + +#blend-mode-menu .blend-mode-container { + margin-bottom: 5px; +} + +#blend-mode-menu .blend-mode-option { + padding: 5px 10px; + color: white; + cursor: pointer; + transition: background-color 0.2s; +} + +#blend-mode-menu .blend-mode-option:hover { + background-color: #3a3a3a; +} + +#blend-mode-menu .blend-mode-option.active { + background-color: #3a3a3a; +} + +#blend-mode-menu .blend-opacity-slider { + width: 100%; + margin: 5px 0; + display: none; + -webkit-appearance: none; + height: 4px; + background: #555; + border-radius: 2px; + outline: none; +} + +#blend-mode-menu .blend-mode-container.active .blend-opacity-slider { + display: block; +} + +#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; + transition: background 0.2s; +} + +#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb:hover { + background: #fff; +} + +#blend-mode-menu .blend-opacity-slider::-moz-range-thumb { + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; +} diff --git a/src/CanvasIO.ts b/src/CanvasIO.ts index 00fe5d4..a837862 100644 --- a/src/CanvasIO.ts +++ b/src/CanvasIO.ts @@ -269,7 +269,10 @@ export class CanvasIO { log.error(`Failed to send data for node ${nodeId}:`, error); - throw new Error(`Failed to get confirmation from server for node ${nodeId}. The workflow might not have the latest canvas data.`); + throw new Error( + `Failed to get confirmation from server for node ${nodeId}. ` + + `Make sure that the nodeId: (${nodeId}) matches the "node_id" value in the node options. If they don't match, you may need to manually set the node_id to ${nodeId}.` + ); } } diff --git a/src/CanvasLayers.ts b/src/CanvasLayers.ts index 761f2ef..af34f3b 100644 --- a/src/CanvasLayers.ts +++ b/src/CanvasLayers.ts @@ -3,6 +3,7 @@ import {createModuleLogger} from "./utils/LoggerUtils.js"; import {generateUUID, generateUniqueFileName, createCanvas} from "./utils/CommonUtils.js"; import {withErrorHandling, createValidationError} from "./ErrorHandler.js"; import {showErrorNotification, showSuccessNotification} from "./utils/NotificationUtils.js"; +import { addStylesheet, getUrl } from "./utils/ResourceManager.js"; // @ts-ignore import {app} from "../../scripts/app.js"; // @ts-ignore @@ -57,6 +58,9 @@ export class CanvasLayers { this.isAdjustingOpacity = false; this.internalClipboard = []; this.clipboardPreference = 'system'; + + // Load CSS for blend mode menu + addStylesheet(getUrl('./css/blend_mode_menu.css')); } async copySelectedLayers(): Promise { @@ -916,70 +920,17 @@ export class CanvasLayers { const menu = document.createElement('div'); this.blendMenuElement = menu; menu.id = 'blend-mode-menu'; - menu.style.cssText = ` - position: absolute; - top: 0; - left: 0; - background: #2a2a2a; - border: 1px solid #3a3a3a; - border-radius: 4px; - z-index: 10000; - box-shadow: 0 2px 10px rgba(0,0,0,0.3); - min-width: 200px; - `; const titleBar = document.createElement('div'); - titleBar.style.cssText = ` - background: #3a3a3a; - color: white; - padding: 8px 10px; - cursor: move; - user-select: none; - border-radius: 3px 3px 0 0; - font-size: 12px; - font-weight: bold; - border-bottom: 1px solid #4a4a4a; - display: flex; - justify-content: space-between; - align-items: center; - `; + titleBar.className = 'blend-menu-title-bar'; const titleText = document.createElement('span'); titleText.textContent = `Blend Mode: ${selectedLayer.name}`; - titleText.style.cssText = ` - flex: 1; - cursor: move; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - `; + titleText.className = 'blend-menu-title-text'; const closeButton = document.createElement('button'); closeButton.textContent = '×'; - closeButton.style.cssText = ` - background: none; - border: none; - color: white; - font-size: 18px; - cursor: pointer; - padding: 0; - margin: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 3px; - transition: background-color 0.2s; - `; - - closeButton.onmouseover = () => { - closeButton.style.backgroundColor = '#4a4a4a'; - }; - - closeButton.onmouseout = () => { - closeButton.style.backgroundColor = 'transparent'; - }; + closeButton.className = 'blend-menu-close-button'; closeButton.onclick = (e: MouseEvent) => { e.stopPropagation(); @@ -990,22 +941,23 @@ export class CanvasLayers { titleBar.appendChild(closeButton); const content = document.createElement('div'); - content.style.cssText = `padding: 5px;`; + content.className = 'blend-menu-content'; menu.appendChild(titleBar); menu.appendChild(content); const blendAreaContainer = document.createElement('div'); - blendAreaContainer.style.cssText = `padding: 5px 10px; border-bottom: 1px solid #4a4a4a;`; + blendAreaContainer.className = 'blend-area-container'; const blendAreaLabel = document.createElement('label'); blendAreaLabel.textContent = 'Blend Area'; - blendAreaLabel.style.color = 'white'; + blendAreaLabel.className = 'blend-area-label'; const blendAreaSlider = document.createElement('input'); blendAreaSlider.type = 'range'; blendAreaSlider.min = '0'; blendAreaSlider.max = '100'; + blendAreaSlider.className = 'blend-area-slider'; blendAreaSlider.value = selectedLayer?.blendArea?.toString() ?? '0'; @@ -1064,23 +1016,22 @@ export class CanvasLayers { this.blendModes.forEach((mode: BlendMode) => { const container = document.createElement('div'); container.className = 'blend-mode-container'; - container.style.cssText = `margin-bottom: 5px;`; const option = document.createElement('div'); - option.style.cssText = `padding: 5px 10px; color: white; cursor: pointer; transition: background-color 0.2s;`; + option.className = 'blend-mode-option'; option.textContent = `${mode.label} (${mode.name})`; const slider = document.createElement('input'); slider.type = 'range'; slider.min = '0'; slider.max = '100'; + slider.className = 'blend-opacity-slider'; const selectedLayer = this.canvas.canvasSelection.selectedLayers[0]; slider.value = selectedLayer ? String(Math.round(selectedLayer.opacity * 100)) : '100'; - slider.style.cssText = `width: 100%; margin: 5px 0; display: none;`; if (selectedLayer && selectedLayer.blendMode === mode.name) { - slider.style.display = 'block'; - option.style.backgroundColor = '#3a3a3a'; + container.classList.add('active'); + option.classList.add('active'); } option.onclick = () => { @@ -1090,20 +1041,18 @@ export class CanvasLayers { return; } - // Hide only the opacity sliders within other blend mode containers + // Remove active class from all containers and options content.querySelectorAll('.blend-mode-container').forEach(c => { - const opacitySlider = c.querySelector('input[type="range"]'); - if (opacitySlider) { - opacitySlider.style.display = 'none'; - } - const optionDiv = c.querySelector('div'); + c.classList.remove('active'); + const optionDiv = c.querySelector('.blend-mode-option'); if (optionDiv) { - optionDiv.style.backgroundColor = ''; + optionDiv.classList.remove('active'); } }); - slider.style.display = 'block'; - option.style.backgroundColor = '#3a3a3a'; + // Add active class to current container and option + container.classList.add('active'); + option.classList.add('active'); currentSelectedLayer.blendMode = mode.name; this.canvas.render(); diff --git a/src/css/blend_mode_menu.css b/src/css/blend_mode_menu.css new file mode 100644 index 0000000..a98eafb --- /dev/null +++ b/src/css/blend_mode_menu.css @@ -0,0 +1,170 @@ +/* Blend Mode Menu Styles */ +#blend-mode-menu { + position: absolute; + top: 0; + left: 0; + background: #2a2a2a; + border: 1px solid #3a3a3a; + border-radius: 4px; + z-index: 10000; + box-shadow: 0 2px 10px rgba(0,0,0,0.3); + min-width: 200px; +} + +#blend-mode-menu .blend-menu-title-bar { + background: #3a3a3a; + color: white; + padding: 8px 10px; + cursor: move; + user-select: none; + border-radius: 3px 3px 0 0; + font-size: 12px; + font-weight: bold; + border-bottom: 1px solid #4a4a4a; + display: flex; + justify-content: space-between; + align-items: center; +} + +#blend-mode-menu .blend-menu-title-text { + flex: 1; + cursor: move; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#blend-mode-menu .blend-menu-close-button { + background: none; + border: none; + color: white; + font-size: 18px; + cursor: pointer; + padding: 0; + margin: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + transition: background-color 0.2s; +} + +#blend-mode-menu .blend-menu-close-button:hover { + background-color: #4a4a4a; +} + +#blend-mode-menu .blend-menu-close-button:focus { + background-color: transparent; +} + +#blend-mode-menu .blend-menu-content { + padding: 5px; +} + +#blend-mode-menu .blend-area-container { + padding: 5px 10px; + border-bottom: 1px solid #4a4a4a; +} + +#blend-mode-menu .blend-area-label { + color: white; + display: block; + margin-bottom: 5px; + font-size: 12px; +} + +#blend-mode-menu .blend-area-slider { + width: 100%; + margin: 5px 0; + -webkit-appearance: none; + height: 4px; + background: #555; + border-radius: 2px; + outline: none; +} + +#blend-mode-menu .blend-area-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; + transition: background 0.2s; +} + +#blend-mode-menu .blend-area-slider::-webkit-slider-thumb:hover { + background: #fff; +} + +#blend-mode-menu .blend-area-slider::-moz-range-thumb { + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; +} + +#blend-mode-menu .blend-mode-container { + margin-bottom: 5px; +} + +#blend-mode-menu .blend-mode-option { + padding: 5px 10px; + color: white; + cursor: pointer; + transition: background-color 0.2s; +} + +#blend-mode-menu .blend-mode-option:hover { + background-color: #3a3a3a; +} + +#blend-mode-menu .blend-mode-option.active { + background-color: #3a3a3a; +} + +#blend-mode-menu .blend-opacity-slider { + width: 100%; + margin: 5px 0; + display: none; + -webkit-appearance: none; + height: 4px; + background: #555; + border-radius: 2px; + outline: none; +} + +#blend-mode-menu .blend-mode-container.active .blend-opacity-slider { + display: block; +} + +#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; + transition: background 0.2s; +} + +#blend-mode-menu .blend-opacity-slider::-webkit-slider-thumb:hover { + background: #fff; +} + +#blend-mode-menu .blend-opacity-slider::-moz-range-thumb { + width: 14px; + height: 14px; + background: #e0e0e0; + border-radius: 50%; + cursor: pointer; + border: 2px solid #555; +}