From 6ace9fc662ff1d9e67e8ea4bcd919ffc430b6ff2 Mon Sep 17 00:00:00 2001 From: adachi Date: Thu, 12 Nov 2026 22:35:30 +0800 Subject: [PATCH] Streamline workbench controls --- tests/test_color_paint.mjs | 4 ++-- web/color_paint.js | 5 ++--- web/compare.js | 2 +- web/compare_node.js | 2 +- web/editor.css | 3 ++- web/inline_inpaint.js | 6 +++--- web/inpaint_node.js | 2 +- web/inpaint_panel.js | 2 +- web/workspace.css | 1 - web/workspace_ui.js | 4 ++-- 10 files changed, 15 insertions(+), 16 deletions(-) diff --git a/tests/test_color_paint.mjs b/tests/test_color_paint.mjs index b30031f..a026059 100644 --- a/tests/test_color_paint.mjs +++ b/tests/test_color_paint.mjs @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; class Element { constructor(tag){this.tag=tag;this.style={};this.dataset={};this.classList={toggle(){}};this.children=[];this.width=0;this.height=0;this.value='';} - append(...children){this.children.push(...children)} setAttribute(){} hasPointerCapture(){return false} setPointerCapture(){} + append(...children){this.children.push(...children)} prepend(...children){this.children.unshift(...children)} setAttribute(){} hasPointerCapture(){return false} setPointerCapture(){} getContext(){return {save(){},restore(){},beginPath(){},rect(){},clip(){},arc(){},fill(){},moveTo(){},lineTo(){},stroke(){},translate(){},clearRect(){},drawImage(){},getImageData(){return {data:[12,34,56,255]}}};} toDataURL(){return 'data:image/png;base64,paint';} } @@ -10,7 +10,7 @@ globalThis.Image=class {constructor(){this.width=16;this.height=12;} async decod const {ColorPaint}=await import('../web/color_paint.js'); const editor={image:{width:16,height:12},ready:true,view:'overlay',brushSize:8,brushOpacity:100,stage:new Element('div'),root:new Element('div'),canvas:new Element('canvas'),options:{drawSource:()=>false},point:e=>({x:e.x,y:e.y}),pointerDown(){},pointerMove(){},pointerUp(){},schedule(){},status(){}}; const crop={mode:'mask',bar:new Element('div'),region:{locked:false}},widget={value:''};let changes=0; -const paint=new ColorPaint(editor,crop,widget,()=>changes++);paint.button.onclick({preventDefault(){},stopPropagation(){}}); +const paint=new ColorPaint(editor,crop,widget,()=>changes++);assert.equal(crop.bar.children[0],paint.controls);paint.button.onclick({preventDefault(){},stopPropagation(){}}); const event={button:0,pointerId:1,x:2,y:3,preventDefault(){},stopPropagation(){}}; editor.pointerDown(event);assert.equal(editor.pointerId,1);editor.pointerUp(event);assert.equal(editor.pointerId,null);assert.match(widget.value,/data:image\/png/);assert.equal(changes,1); const saved=widget.value;editor.pointerDown({...event,altKey:true});assert.equal(paint.color,'#0c2238');assert.equal(widget.value,saved); diff --git a/web/color_paint.js b/web/color_paint.js index fd4ae99..08da872 100644 --- a/web/color_paint.js +++ b/web/color_paint.js @@ -12,8 +12,7 @@ export class ColorPaint { this.picker=document.createElement('input');this.picker.type='color';this.picker.value=this.color;this.picker.title=T('Brush color');this.picker.setAttribute('aria-label',this.picker.title);this.picker.oninput=()=>{this.color=this.picker.value;}; this.undoButton=document.createElement('button');this.undoButton.type='button';this.undoButton.textContent='↶';this.undoButton.title=T('Undo color painting');this.undoButton.onclick=()=>this.step(this.undo,this.redo); this.redoButton=document.createElement('button');this.redoButton.type='button';this.redoButton.textContent='↷';this.redoButton.title=T('Redo color painting');this.redoButton.onclick=()=>this.step(this.redo,this.undo); - controls.append(this.button,this.picker,this.undoButton,this.redoButton);editor.stage.append(controls);this.controls=controls; - this.barObserver=new ResizeObserver(()=>editor.root.style.setProperty('--dd-color-offset',`${Math.ceil(crop.bar.getBoundingClientRect().height)+14}px`));this.barObserver.observe(crop.bar); + controls.append(this.button,this.picker,this.undoButton,this.redoButton);crop.bar.prepend(controls);this.controls=controls; const draw=editor.options.drawSource,revision=editor.options.sceneRevision; editor.options.drawSource=ctx=>{const done=draw?.(ctx);if(!done)ctx.drawImage(editor.image,0,0);if(this.layer.width&&!crop.previewOriginal)ctx.drawImage(this.layer,0,0);return true;}; editor.options.sceneRevision=()=>`${revision?.()||''}:paint:${this.revision}`; @@ -35,7 +34,7 @@ export class ColorPaint { remember(){this.undo.push(this.layer.toDataURL('image/png'));let bytes=this.undo.reduce((n,s)=>n+s.length*2,0);while(this.undo.length>1&&(this.undo.length>20||bytes>64*1024*1024))bytes-=this.undo.shift().length*2;} async step(from,to){if(this.drawing||this.restoring||!from.length)return;this.restoring=true;const current=this.layer.toDataURL('image/png'),data=from.pop(),image=new Image();image.src=data;try{await image.decode();to.push(current);const ctx=this.layer.getContext('2d');ctx.clearRect(0,0,this.layer.width,this.layer.height);ctx.drawImage(image,0,0);this.revision++;this.flush();this.editor.schedule();this.sync();}catch(e){from.push(data);this.editor.status(e.message,true);}finally{this.restoring=false;}} clear(){this.undo=[];this.redo=[];this.layer.width=this.layer.height=0;if(this.widget)this.widget.value='';this.revision++;this.sync();} - destroy(){this.barObserver.disconnect();this.layer.width=this.layer.height=0;this.controls.remove();} + destroy(){this.layer.width=this.layer.height=0;this.controls.remove();} clearRegion(r){if(!this.layer.width)return;this.remember();this.redo=[];this.layer.getContext('2d').clearRect(r.x,r.y,r.width,r.height);this.revision++;this.flush();this.editor.schedule();this.sync();} sync(){this.editor.stage.classList.toggle('dd-color-active',this.active);this.button.classList.toggle('dd-active',this.active);this.button.setAttribute('aria-pressed',String(this.active));this.picker.hidden=this.undoButton.hidden=this.redoButton.hidden=!this.active;this.undoButton.disabled=!this.undo.length;this.redoButton.disabled=!this.redo.length;this.editor.updateButtons?.();} } diff --git a/web/compare.js b/web/compare.js index fe4a71f..a99f35c 100644 --- a/web/compare.js +++ b/web/compare.js @@ -34,7 +34,7 @@ export class ImageCompareView { this.downloadButton.setAttribute("aria-label", this.downloadButton.title); this.downloadButton.onclick = event => { event.preventDefault(); event.stopPropagation(); this.downloadLatest(); }; toolbar.append(this.downloadButton); - const fit = element("button", "", T("Fit")); fit.type = "button"; + const fit = element("button", "dd-compare-fit", T("Fit")); fit.type = "button"; fit.onclick = event => { event.preventDefault(); event.stopPropagation(); this.fitView(); }; toolbar.append(fit); this.stage = element("div", "dd-stage dd-compare-stage"); this.canvas = document.createElement("canvas"); this.canvas.tabIndex = 0; diff --git a/web/compare_node.js b/web/compare_node.js index bff31e9..d11fc1e 100644 --- a/web/compare_node.js +++ b/web/compare_node.js @@ -1,6 +1,6 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; -import { ImageCompareView } from "./compare.js?v=2.1.0"; +import { ImageCompareView } from "./compare.js?v=toolbar-layout-20261112"; import { readPreview } from "./preview_transport.js"; import { installComparisonRecovery } from "./result_recovery.js"; import {executionId,executionNode,graphNodeEntries} from "./workspace_flow.js"; diff --git a/web/editor.css b/web/editor.css index a5ceaf9..85cdb80 100644 --- a/web/editor.css +++ b/web/editor.css @@ -26,7 +26,7 @@ .dd-inpaint input[type="range"] { display: block; width: 100%; min-width: 0; height: 18px; margin: 3px 0 0; accent-color: #f3b06a; cursor: pointer; } .dd-inpaint .dd-stage { position: relative; flex: 1 1 290px; min-height: 170px; overflow: hidden; border-top: 1px solid var(--dd-line); border-bottom: 1px solid var(--dd-line); background-color: #161a20; background-image: linear-gradient(45deg, #222831 25%, transparent 25%), linear-gradient(-45deg, #222831 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #222831 75%), linear-gradient(-45deg, transparent 75%, #222831 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0; } .dd-inpaint .dd-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; cursor: crosshair; } -.dd-inpaint .dd-color-paint {position:absolute;left:8px;bottom:var(--dd-color-offset,45px);z-index:5;display:flex;gap:4px;align-items:center;padding:3px;border-radius:5px;background:#1b222bdd;} +.dd-inpaint .dd-color-paint {display:flex;flex:none;gap:3px;align-items:center;} .dd-inpaint .dd-color-paint button {min-width:26px;padding:3px 6px;} .dd-inpaint .dd-color-paint input[type=color] {width:26px;height:25px;padding:1px;border:0;background:transparent;cursor:pointer;} .dd-inpaint .dd-color-paint [hidden] {display:none;} @@ -102,6 +102,7 @@ .dd-comparer .dd-compare-stage { border: 0; } .dd-comparer .dd-compare-toolbar { padding-top: 5px; padding-bottom: 2px; } .dd-comparer .dd-compare-toolbar .dd-dimensions { margin-left: 5px; } +.adachi-workspace .dd-comparer .dd-compare-fit { display: none; } .dd-comparer .dd-adoption { padding-top: 3px; padding-bottom: 5px; } .dd-comparer .dd-compare-captions { position: absolute; top: 8px; left: 8px; right: 8px; display: flex; justify-content: space-between; pointer-events: none; } .dd-comparer .dd-compare-captions span { background: #171d27cc; color: #f1e3cf; padding: 3px 7px; border-radius: 3px; font-size: 11px; } diff --git a/web/inline_inpaint.js b/web/inline_inpaint.js index d159376..b648e73 100644 --- a/web/inline_inpaint.js +++ b/web/inline_inpaint.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; import { InlineMaskEditor } from "./editor.js?v=2.1.0"; -import { installInpaintWorkbench } from "./inpaint_node.js?v=workspace-zoom-20261112"; +import { installInpaintWorkbench } from "./inpaint_node.js?v=toolbar-layout-20261112"; import { bindWidgetToNodeWidth, createWidgetHost } from "./widget_layout.js?v=stable-host-20261112"; import { routeWheelOutsideImage } from "./wheel_routing.js"; import { InlineCropController } from "./inline_crop.js?v=2.1.0"; @@ -9,13 +9,13 @@ import { canStoreDraft, prepareInlineDraft, syncInlineWidgets } from "./draft_gu import { T } from "./i18n.js"; import { removeLegacyFeedback } from "./legacy_feedback.js?v=0.6.4"; import { uploadImageWithProgress } from "./upload_transport.js?v=stability1"; -import { ColorPaint } from "./color_paint.js"; +import { ColorPaint } from "./color_paint.js?v=toolbar-layout-20261112"; import { createPaintDraftProtection } from "./paint_draft.js"; const styleId = "adachi-inpaint-style"; const stylesheet = document.getElementById(styleId) || document.createElement("link"); stylesheet.id = styleId; stylesheet.rel = "stylesheet"; -stylesheet.href = new URL("./editor.css?v=color-paint-20261112", import.meta.url).href; +stylesheet.href = new URL("./editor.css?v=toolbar-layout-20261112", import.meta.url).href; if (!stylesheet.isConnected) document.head.append(stylesheet); function hide(widget) { diff --git a/web/inpaint_node.js b/web/inpaint_node.js index ffb7c09..185e76f 100644 --- a/web/inpaint_node.js +++ b/web/inpaint_node.js @@ -1,5 +1,5 @@ import { app } from "../../scripts/app.js"; -import { InpaintParameterPanel } from "./inpaint_panel.js?v=workspace-zoom-20261112"; +import { InpaintParameterPanel } from "./inpaint_panel.js?v=toolbar-layout-20261112"; import { connectedEditor, connectedSamplers, relocateWidgets, syncCurrentDimensions } from "./parameter_bridge.js?v=0.8.2"; import { routeWheelOutsideImage } from "./wheel_routing.js"; import { T } from "./i18n.js"; diff --git a/web/inpaint_panel.js b/web/inpaint_panel.js index 39271a5..7fe7e89 100644 --- a/web/inpaint_panel.js +++ b/web/inpaint_panel.js @@ -58,7 +58,7 @@ export class InpaintParameterPanel { enum(parent, name, label, pills = false) { const widget = this.widget(name); if (!widget) return; label = T(label); - const wrapper = el("div", "dd-param-field"); wrapper.append(el("span", "dd-param-label", label)); + const wrapper = el("div", "dd-param-field"); wrapper.dataset.adachiParam = name; wrapper.append(el("span", "dd-param-label", label)); let values = widget.options?.values; if (typeof values === "function") values = values(); if (!Array.isArray(values)) values = Object.keys(choices[name] || {}); if (!values.includes(widget.value)) values = [widget.value, ...values]; diff --git a/web/workspace.css b/web/workspace.css index 6bfcf82..9f49b3a 100644 --- a/web/workspace.css +++ b/web/workspace.css @@ -98,7 +98,6 @@ body.adachi-workspace-open > :not(.adachi-workspace):not(script):not(style):not( .adachi-workspace .aw-canvas .dd-crop-bar button,.adachi-workspace .aw-canvas .dd-crop-bar select,.adachi-workspace .aw-canvas .dd-crop-bar input { height:28px;min-height:28px;font-size:12px;color:var(--aw-text); } -.adachi-workspace .aw-canvas .dd-color-paint {bottom:calc(var(--aw-history-height,40px) + var(--aw-crop-height,32px) + 24px);background:var(--aw-panel);border:1px solid var(--aw-line);box-shadow:0 2px 8px #0002;} .adachi-workspace .aw-canvas .dd-files button {opacity:1} .adachi-workspace .aw-canvas .dd-adoption {display:none} .adachi-workspace .aw-canvas .dd-compare-stage {position:absolute;inset:0;min-height:0} diff --git a/web/workspace_ui.js b/web/workspace_ui.js index 7543437..e1ea20f 100644 --- a/web/workspace_ui.js +++ b/web/workspace_ui.js @@ -72,7 +72,7 @@ export class Workspace { // Move the existing controls, rather than copying widgets: linked inputs, // source-size synchronization and sampler updates keep their native wiring. this.sampler.adachiParameters.refresh(); - for(const name of ['resolution','width','height','denoise']){ + for(const name of ['area','resolution','width','height','denoise']){ const field=this.sampler.adachiParameters.body.querySelector(`:scope > [data-adachi-param="${name}"]`); if(field)this.mount(field,this.quickParams); } @@ -219,6 +219,6 @@ export class Workspace { close(){if(this.closed)return;this.closed=true;this.sessionController.abort();this.checkController?.abort();++this.checkVersion;clearInterval(this.timer);clearInterval(this.pollTimer);clearTimeout(this.checkTimer);this.stopSplitter?.();for(const dispose of this.disposers.reverse())dispose();if(this.history){this.history.onChange=null;this.history.onRestore=null;}if(this.taskProgress)this.taskProgress.onChange=()=>{};this.bindings?.destroy();for(const {element,marker,style}of this.mounts.reverse()){element.classList.remove('aw-embedded');const removed=(element===this.editor?.root&&(this.editor.destroyed||!this.editorNode?.graph))||(element===this.compare?.root&&!this.compareNode?.graph)||(element===this.sampler?.adachiWorkbench?.root&&!this.sampler?.graph);if(removed){marker.remove();element.remove();continue;}element.hidden=false;element.inert=false;if(marker.parentNode)marker.replaceWith(element);if(style===null)element.removeAttribute('style');else element.setAttribute('style',style);}this.root.remove();document.body.classList.remove('adachi-workspace-open');this.editor?.schedule();this.compare?.schedule();this.compare?.updateButton();currentWorkspace=null;} } app.registerExtension({name:'Adachi.InpaintWorkspace',setup(){ - const css=document.createElement('link');css.rel='stylesheet';css.href=new URL('./workspace.css?v=2.1.0',import.meta.url).href;document.head.append(css); + const css=document.createElement('link');css.rel='stylesheet';css.href=new URL('./workspace.css?v=toolbar-layout-20261112',import.meta.url).href;document.head.append(css); document.addEventListener('adachi-open-workspace',event=>{if(!currentWorkspace)currentWorkspace=new Workspace(event.detail?.sampler||null);}); }});