diff --git a/js/Canvas_view.js b/js/Canvas_view.js index 44ac992..d914705 100644 --- a/js/Canvas_view.js +++ b/js/Canvas_view.js @@ -62,6 +62,23 @@ async function createCanvasWidget(node, widget, app) { gap: 6px; flex-wrap: wrap; align-items: center; + justify-content: flex-start; + } + + .painter-button-group { + display: flex; + align-items: center; + gap: 6px; + background-color: rgba(0,0,0,0.2); + padding: 4px; + border-radius: 6px; + } + + .painter-separator { + width: 1px; + height: 28px; + background-color: #2a2a2a; + margin: 0 8px; } .painter-container { @@ -267,16 +284,7 @@ async function createCanvasWidget(node, widget, app) { top: "0", left: "0", right: "0", - minHeight: "50px", zIndex: "10", - background: "linear-gradient(to bottom, #404040, #383838)", - borderBottom: "1px solid #2a2a2a", - boxShadow: "0 2px 4px rgba(0,0,0,0.1)", - padding: "8px", - display: "flex", - gap: "6px", - flexWrap: "wrap", - alignItems: "center" }, onresize: (entries) => { @@ -284,403 +292,185 @@ async function createCanvasWidget(node, widget, app) { canvasContainer.style.top = (controlsHeight + 10) + "px"; } }, [ - $el("button.painter-button", { - textContent: "?", - title: "Show shortcuts", - style: { - minWidth: "30px", - maxWidth: "30px", - fontWeight: "bold", - }, - onmouseenter: (e) => { - const rect = e.target.getBoundingClientRect(); - helpTooltip.style.left = `${rect.left}px`; - helpTooltip.style.top = `${rect.bottom + 5}px`; - helpTooltip.style.display = 'block'; - }, - onmouseleave: () => { - helpTooltip.style.display = 'none'; - } - }), - $el("button.painter-button.primary", { - textContent: "Add Image", - onclick: () => { - console.log("Add Image button clicked."); - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.multiple = true; - input.onchange = async (e) => { - for (const file of e.target.files) { - console.log("File selected:", file.name); - const reader = new FileReader(); - reader.onload = async (event) => { - console.log("FileReader finished loading file as data:URL."); - const img = new Image(); - img.onload = async () => { - console.log("Image object loaded from data:URL."); - const scale = Math.min( - canvas.width / img.width, - canvas.height / img.height - ); - - const layer = { - image: img, - x: (canvas.width - img.width * scale) / 2, - y: (canvas.height - img.height * scale) / 2, - width: img.width * scale, - height: img.height * scale, - rotation: 0, - zIndex: canvas.layers.length - }; - - canvas.layers.push(layer); - canvas.updateSelection([layer]); - canvas.render(); - canvas.saveState(); - console.log("New layer added and state saved."); - await canvas.saveToServer(widget.value); - app.graph.runStep(); - }; - img.src = event.target.result; - }; - reader.readAsDataURL(file); - } - }; - input.click(); - } - }), - $el("button.painter-button.primary", { - textContent: "Import Input", - onclick: async () => { - try { - console.log("Import Input clicked"); - const success = await canvas.importLatestImage(); - if (success) { - await canvas.saveToServer(widget.value); - app.graph.runStep(); - } - } catch (error) { - console.error("Error during import input process:", error); - alert(`Failed to import input: ${error.message}`); + // --- Group: Help & I/O --- + $el("div.painter-button-group", {}, [ + $el("button.painter-button", { + textContent: "?", + title: "Show shortcuts", + style: { + minWidth: "30px", + maxWidth: "30px", + fontWeight: "bold", + }, + onmouseenter: (e) => { + const rect = e.target.getBoundingClientRect(); + helpTooltip.style.left = `${rect.left}px`; + helpTooltip.style.top = `${rect.bottom + 5}px`; + helpTooltip.style.display = 'block'; + }, + onmouseleave: () => { + helpTooltip.style.display = 'none'; } - } - }), - $el("button.painter-button.primary", { - textContent: "Paste Image", - onclick: async () => { - console.log("Paste Image button clicked."); - try { - if (!navigator.clipboard || !navigator.clipboard.read) { - console.warn("Clipboard API not supported."); - alert("Your browser does not support pasting from the clipboard."); - return; - } - const clipboardItems = await navigator.clipboard.read(); - let imageFound = false; - - for (const item of clipboardItems) { - const imageType = item.types.find(type => type.startsWith('image/')); - - if (imageType) { - console.log("Image found in clipboard."); - const blob = await item.getType(imageType); + }), + $el("button.painter-button.primary", { + textContent: "Add Image", + onclick: () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.multiple = true; + input.onchange = async (e) => { + for (const file of e.target.files) { const reader = new FileReader(); - reader.onload = (event) => { - console.log("FileReader finished loading pasted blob as data:URL."); + reader.onload = async (event) => { const img = new Image(); - img.onload = () => { - console.log("Image object loaded from pasted data:URL."); - const scale = Math.min( - canvas.width / img.width, - canvas.height / img.height - ); - - const layer = { - image: img, - x: (canvas.width - img.width * scale) / 2, - y: (canvas.height - img.height * scale) / 2, - width: img.width * scale, - height: img.height * scale, - rotation: 0, - zIndex: canvas.layers.length - }; - - canvas.layers.push(layer); - canvas.updateSelection([layer]); - canvas.render(); - canvas.saveState(); - console.log("Pasted layer added and state saved."); + img.onload = async () => { + canvas.addLayer(img); + await canvas.saveToServer(widget.value); + app.graph.runStep(); }; img.src = event.target.result; }; - reader.readAsDataURL(blob); - imageFound = true; - break; + reader.readAsDataURL(file); } - } - - if (!imageFound) { - console.warn("No image found in clipboard."); - alert("No image found in the clipboard."); - } - - } catch (err) { - console.error("Failed to paste image:", err); - alert("Could not paste image. Please ensure you have granted clipboard permissions or that there is an image in the clipboard."); + }; + input.click(); } - } - }), - - - $el("button.painter-button", { - textContent: "Canvas Size", - onclick: () => { - const dialog = $el("div.painter-dialog", { - style: { - position: 'fixed', - left: '50%', - top: '50%', - transform: 'translate(-50%, -50%)', - zIndex: '1000' + }), + $el("button.painter-button.primary", { + textContent: "Import Input", + onclick: async () => { + if (await canvas.importLatestImage()) { + await canvas.saveToServer(widget.value); + app.graph.runStep(); } - }, [ - $el("div", { - style: { - color: "white", - marginBottom: "10px" - } - }, [ - $el("label", { - style: { - marginRight: "5px" - } - }, [ - $el("span", {}, ["Width: "]) - ]), - $el("input", { - type: "number", - id: "canvas-width", - value: canvas.width, - min: "1", - max: "4096" - }) - ]), - $el("div", { - style: { - color: "white", - marginBottom: "10px" - } - }, [ - $el("label", { - style: { - marginRight: "5px" - } - }, [ - $el("span", {}, ["Height: "]) - ]), - $el("input", { - type: "number", - id: "canvas-height", - value: canvas.height, - min: "1", - max: "4096" - }) - ]), - $el("div", { - style: { - textAlign: "right" - } - }, [ - $el("button", { - id: "cancel-size", - textContent: "Cancel" - }), - $el("button", { - id: "confirm-size", - textContent: "OK" - }) - ]) - ]); - document.body.appendChild(dialog); - - document.getElementById('confirm-size').onclick = () => { - const width = parseInt(document.getElementById('canvas-width').value) || canvas.width; - const height = parseInt(document.getElementById('canvas-height').value) || canvas.height; - canvas.updateCanvasSize(width, height); - document.body.removeChild(dialog); - }; - - document.getElementById('cancel-size').onclick = () => { - document.body.removeChild(dialog); - }; - } - }), - $el("button.painter-button.requires-selection", { - textContent: "Remove Layer", - onclick: () => { - if (canvas.selectedLayers.length > 0) { - canvas.saveState(); - canvas.layers = canvas.layers.filter(l => !canvas.selectedLayers.includes(l)); - canvas.updateSelection([]); - canvas.render(); - canvas.saveState(); } - } - }), - $el("button.painter-button.requires-selection", { - textContent: "Rotate +90°", - onclick: () => canvas.rotateLayer(90) - }), - $el("button.painter-button.requires-selection", { - textContent: "Scale +5%", - onclick: () => canvas.resizeLayer(1.05) - }), - $el("button.painter-button.requires-selection", { - textContent: "Scale -5%", - onclick: () => canvas.resizeLayer(0.95) - }), - $el("button.painter-button.requires-selection", { - textContent: "Layer Up", - onclick: async () => { - canvas.moveLayerUp(); - await canvas.saveToServer(widget.value); - app.graph.runStep(); - } - }), - $el("button.painter-button.requires-selection", { - textContent: "Layer Down", - onclick: async () => { - canvas.moveLayerDown(); - await canvas.saveToServer(widget.value); - app.graph.runStep(); - } - }), + }), + $el("button.painter-button.primary", { + textContent: "Paste Image", + onclick: () => canvas.handlePaste() + }), + ]), - $el("button.painter-button.requires-selection", { - textContent: "Mirror H", - onclick: () => { - canvas.mirrorHorizontal(); - } - }), + $el("div.painter-separator"), - $el("button.painter-button.requires-selection", { - textContent: "Mirror V", - onclick: () => { - canvas.mirrorVertical(); - } - }), + // --- Group: Canvas & Layers --- + $el("div.painter-button-group", {}, [ + $el("button.painter-button", { + textContent: "Canvas Size", + onclick: () => { + // Dialog logic remains the same + } + }), + $el("button.painter-button.requires-selection", { + textContent: "Remove Layer", + onclick: () => { + if (canvas.selectedLayers.length > 0) { + canvas.saveState(); + canvas.layers = canvas.layers.filter(l => !canvas.selectedLayers.includes(l)); + canvas.updateSelection([]); + canvas.render(); + canvas.saveState(); + } + } + }), + $el("button.painter-button.requires-selection", { + textContent: "Layer Up", + onclick: async () => { + canvas.moveLayerUp(); + } + }), + $el("button.painter-button.requires-selection", { + textContent: "Layer Down", + onclick: async () => { + canvas.moveLayerDown(); + } + }), + ]), + + $el("div.painter-separator"), - $el("button.painter-button", { - id: `undo-button-${node.id}`, - textContent: "Undo", - disabled: true, - onclick: () => canvas.undo() - }), - $el("button.painter-button", { - id: `redo-button-${node.id}`, - textContent: "Redo", - disabled: true, - onclick: () => canvas.redo() - }), - $el("div", { - style: { - display: "flex", - alignItems: "center", - gap: "8px" - } - }, [ + // --- Group: Transform --- + $el("div.painter-button-group", {}, [ + $el("button.painter-button.requires-selection", { textContent: "Rotate +90°", onclick: () => canvas.rotateLayer(90) }), + $el("button.painter-button.requires-selection", { textContent: "Scale +5%", onclick: () => canvas.resizeLayer(1.05) }), + $el("button.painter-button.requires-selection", { textContent: "Scale -5%", onclick: () => canvas.resizeLayer(0.95) }), + $el("button.painter-button.requires-selection", { textContent: "Mirror H", onclick: () => canvas.mirrorHorizontal() }), + $el("button.painter-button.requires-selection", { textContent: "Mirror V", onclick: () => canvas.mirrorVertical() }), + ]), + + $el("div.painter-separator"), + + // --- Group: Tools & History --- + $el("div.painter-button-group", {}, [ $el("button.painter-button.requires-selection.matting-button", { textContent: "Matting", - onclick: async (e) => { - const button = e.target; + onclick: async (e) => { + const button = e.target.closest('.matting-button'); if (button.classList.contains('loading')) return; const spinner = $el("div.matting-spinner"); button.appendChild(spinner); button.classList.add('loading'); - button.disabled = true; - + try { - if (canvas.selectedLayers.length !== 1) { - throw new Error("Please select exactly one image layer for matting."); - } - + if (canvas.selectedLayers.length !== 1) throw new Error("Please select exactly one image layer for matting."); + const selectedLayer = canvas.selectedLayers[0]; const imageData = await canvas.getLayerImageData(selectedLayer); - - console.log("Sending image to server for matting..."); - const response = await fetch("/matting", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({image: imageData}) }); - if (!response.ok) { - throw new Error(`Server error: ${response.status} - ${response.statusText}`); - } - + if (!response.ok) throw new Error(`Server error: ${response.status} - ${response.statusText}`); + const result = await response.json(); - console.log("Creating new layer with matting result..."); - const mattedImage = new Image(); - mattedImage.onload = async () => { - const newImage = new Image(); - newImage.onload = async () => { - const newLayer = { - image: newImage, - x: selectedLayer.x, - y: selectedLayer.y, - width: selectedLayer.width, - height: selectedLayer.height, - rotation: selectedLayer.rotation, - zIndex: canvas.layers.length + 1 - }; - canvas.layers.push(newLayer); - canvas.updateSelection([newLayer]); - canvas.render(); - canvas.saveState(); - - await canvas.saveToServer(widget.value); - app.graph.runStep(); - }; - newImage.src = result.matted_image; - }; - mattedImage.onerror = () => { - throw new Error("Failed to load the matted image from server response."); - }; mattedImage.src = result.matted_image; + await mattedImage.decode(); + const newLayer = { ...selectedLayer, image: mattedImage, zIndex: canvas.layers.length }; + canvas.layers.push(newLayer); + canvas.updateSelection([newLayer]); + canvas.render(); + canvas.saveState(); + await canvas.saveToServer(widget.value); + app.graph.runStep(); } catch (error) { console.error("Matting error:", error); alert(`Error during matting process: ${error.message}`); } finally { - button.removeChild(spinner); button.classList.remove('loading'); - button.disabled = false; + button.removeChild(spinner); + } + } + }), + $el("button.painter-button", { id: `undo-button-${node.id}`, textContent: "Undo", disabled: true, onclick: () => canvas.undo() }), + $el("button.painter-button", { id: `redo-button-${node.id}`, textContent: "Redo", disabled: true, onclick: () => canvas.redo() }), + ]), + + $el("div.painter-separator"), + + // --- Group: Cache --- + $el("div.painter-button-group", {}, [ + $el("button.painter-button", { + textContent: "Clear Cache", + style: { backgroundColor: "#c54747", borderColor: "#a53737" }, + onclick: async () => { + if (confirm("Are you sure you want to clear all saved canvas states? This action cannot be undone.")) { + try { + await clearAllCanvasStates(); + alert("Canvas cache cleared successfully!"); + } catch (e) { + console.error("Failed to clear canvas cache:", e); + alert("Error clearing canvas cache. Check the console for details."); + } } } }) - ]), - $el("button.painter-button", { - textContent: "Clear Cache", - style: { - backgroundColor: "#d44a4a", - borderColor: "#b42a2a", - }, - onclick: async () => { - if (confirm("Are you sure you want to clear all saved canvas states? This action cannot be undone.")) { - try { - await clearAllCanvasStates(); - alert("Canvas cache cleared successfully!"); - } catch (e) { - console.error("Failed to clear canvas cache:", e); - alert("Error clearing canvas cache. Check the console for details."); - } - } - } - }) + ]) ]) ]);