From fca7e22d2b153ae8ade53cf44217f7760786521f Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 22 Nov 2024 11:25:27 +1100 Subject: [PATCH 01/19] docs for 1.4.1 --- README.md | 32 ++++++++++++++++++++++----- images/controllertop.png | Bin 1240 -> 1480 bytes images/nodeblock_icons.png | Bin 0 -> 2617 bytes images/other_icons.png | Bin 3014 -> 3979 bytes images/top.png | Bin 1925 -> 1946 bytes js/constants.js | 1 + js/controller_panel.js | 44 ++++++++++++++++++------------------- js/utilities.js | 16 ++++++++++++-- 8 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 images/nodeblock_icons.png diff --git a/README.md b/README.md index c6fe9b1..95d4008 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,14 @@ doesn't bring up the menu any more). If you want to get rid of a tab, switch to ## The other icons +### On the controller + ![other icons](images/other_icons.png) The play button indicates that all of the nodes in this group are currently active (not bypassed or muted). -You can use it to bypass or mute a group. (Mute is refered to as 'never' in some places) +You can use it to bypass or mute a group. (Mute is refered to as 'never' in some places). + +Clicking the play button will bypass or unbypass the group, ctrl-clicking will mute or unmute. |Icon|Meaning|Click will...|Ctrl-click will...| |-|-|-|-| @@ -79,12 +83,26 @@ You can use it to bypass or mute a group. (Mute is refered to as 'never' in some The little lightning bolt icon is used to show/hide any nodes that were added as advanced controls. If there are no advanced control, this icon won't be shown. +### On each nodeblock + +![other icons](images/nodeblock_icons.png) + +Each node can be independantly bypassed or muted using the arrow on the nodeblock. + Nodes which include an image also have a pin in the top right corner: ![pin](images/pin.png) When this is active (blue, the default) the image will be shown at the full width of the controller. If you unpin the image, you will find a handle at the bottom right of the image that can be used to resize it (vertically). ## Other things to explore +### Hover and zoom + +If the magnifying glass in the top control is active ![image](images/top.png) then when you move you mouse over a node in +the controller it will be highlighted in the workflow, and when you move your mouse over a group tab, the group will +be highlighted. + +Control-click a tab and the workflow will zoom to show the group. + ### Multiple controllers Remember, you can add more controllers any time by right clicking on the canvas. You can also close controllers with the 'X', @@ -124,6 +142,10 @@ There's also a debug setting that I might ask you to use if you report a problem There is a little drag box at the bottom right of the controller that you can use to resize it. You can also move it around by dragging the header. +## Snapping + +Controllers will snap to each other and move around together. To break them apart, move the controller on the right or bottom. + ## Known Limitations ### Custom widgets @@ -134,11 +156,11 @@ in future releases, probably starting with rgthree's Power Lora Loader. # The road ahead... -Future features will depend on community feedback, but some of the top contenders right now are... +Future features will depend on community feedback, but the aim for the 1.5 release is to include -- Visual links between the controller and workflow (highlight node on hover) -- Selecting which widgets on a node to show -- 'Snapping' controller windows to each other and the edges of the display +- Support for some popular custom node widgets +- Support for batches of images +- Open the mask editor directly from the controller on an image load node For more details of what's under consideration, take a look at the [issues list](https://github.com/chrisgoringe/cg-controller/issues), and feel free to add your ideas there, or diff --git a/images/controllertop.png b/images/controllertop.png index 271f124fba18d105314134e4f554f2a288fe3b02..2a6d339c29c6d7672951c79c6e7b4a59d22740c8 100644 GIT binary patch delta 1447 zcmV;Y1z7sn3CIf}iBL{Q4GJ0x0000DNk~Le000160000_2nGNE0EBV56pgp|GudlBoCntxG_Qs6&J_-3meeZU1 z`Kv+#N^FVjXd@1X1Dd9#UbFpsDfTC$vPoc<%Z0qWJSDWr_W@|BmD zTe@2^Y$GIeadB}qLnWE~qTO<7Pb%1ODciXpMdqscmC zLpdq8RmoVcX9G=!F8ANA>*{61fa#C)qlCfOR z2KrI5dT*y6bojQLuj8Jc*zH6VhJNN5#|AP z@OSbHQaO$j&sM=(eVmVv4uN6^&{zW`!XX5ErxAe@bxnt@ZEGV=f9z|Gs6_&ElR-A< z!!)xJr`LaIE3q=d#11snva+5-XfQqQOIe8z=XRVZ?SUAvJBM+GSxDse1yEh5=pDgS zf=!eS0Uod)Ld5QBs$&k#gl$i^ed(hQJ_^<}GB>v;hVG5TVlj9;9v;~a_0~+-r5mVe z>EkgDtN59BeOMf=f5GWq3Q(VY9^hl@v-`Yr%z`%kBet<~Zr0vieg|!yB0k=VBi{l1 zt3%g$U<*mifAawXV-NUP9OhScs<&nq!UWG?FoZd>5)*oED`I+O2X04kz9xbjHK$n* zl1Zw1p9iMiZ@`5?vnqQpe0Y1H8z+A4$7`QV@z~K|Xt};Sf9c*D(diw9CW4c5tRb{# z&n>{S#bW8o=C23B*gcBFLpig(fAb__NNlQZ+Ch;G<=ogQvLc4WraeL0s*(-mq}*1; zkl3{ON_&FK%ga0>9?C^I55DnxAM?N5L-@D*e0+4M4PS{E7JCErC`0xfT5Lu3n7>?C z?1~awA|@}_e-(RiV9!<20PZV;X!p?OFuzPZ3ghe7uVaD_?S!e_h<2s}{wf`_V_#sF zjVHdniHXS&LU-u+0rLq598ZC{xIHnk8>z=DEnaBxKk?Arj*gBFw0m7x4v%7JXb3~G zV{n)0e~66t<7l6_J337CY&S{@b?Ajvc;S>Eema_((f(o^yH<;fXIXc0Zvw%wmGs+f zh`^nilj!-Z2i{meKK#24mzy29`|(>CnBkXgh`{A~B}$5brDZo>^;aVK6}ak~QI(v<=;wBI>c@x^+j9)X&`|O;ed|s}`thB8t@?k2iLJRynbg3_&o|M3 zM=4DR6Wd+rV5^x3Zhd+SO6oM~xe_!yZQP28GMA+tmA`@jJ5>aAHM2RgC^?bHO zRAoy7X>pupO-cX&002ovPDHLkV1m2J BwBP^$ delta 1206 zcmV;n1WEhI3)l%EiBL{Q4GJ0x0000DNk~Le0000{0000%2nGNE0I69>t&t%#e*|qw zL_t(|UhSGqNMlzN$NzpVqDIjuSfK%*i%Awkrf#yCRdMJdvnYZCN+B?tf!Vmxf{X-F zgvQNm28NbEpk!c#(nVdg6S}FJxNwvVB(v}dDM-OYm`2Sa_|bcBuGEjbvy}iBcdRz4M_2KO7j7=F71qK z6^vVtBbLjJo;fCj)8}~eJ9@h^|iF6gNJ_iOd z<^>T1c*m|=)DL}*h*iH{e^gvpsuG@rA1S=q&cf-R)XcO-rzVMEpMrcUj_B+*R4DM- z)M5=1!|*A2q~cNX`z+oh@=$DQ(Y(a44~iPc6m>|5##6{EK3kTw=80u@cNfFM!z{i@ z5V5U!_?I2h=QGo#(wsEk3}Mm#og?z%R-plC$M0|2(pdf0>)Zw~B+uS1Iit ziezOk#aAgn{dF13Yya^0_Ar-;!7+H9Dv_cXsZ6AM!}T>o$L?%|kkD1rc$&~U6&sGwAtUj2?#4sjF9jn`AbVTDw<(=?P+G}Ri%*0G4gQ1}z zVNHih(5}wj-X7d;exG$qoe0Bu6t1+@7<8?;tMQzHGNXs_?! z;DC?Z4)p%CXKCdQcg2N;cARNfn>J?)g#w$qL_qID@3+6d&th|(?F`Z%Gx(U!@VI`| z^*a`teO4~x3v@}8RK~UdoB@QC^Ll|FoXRw*r!6!cYKRp=n zkHY=$pP2nVirfFj_w&G@#OhxT`Db3^xuW9LvlZ0$>!Z6vheQXy3QXWWYd-hD^KcZd z+$Pr3H}jCvf8%+GQCAL|>*-Ra=8{-ldyhsgZXFT>2t}S?q{1{=k>U>CK3%{X4;vBv zj)4Y*f{D;H z9*^|!xH6x1e#6U!RoEdp4M>z|Ay;PsNQnZF5(OY73P4H}K-twr3;+NC07*qoM6N<$f^|Yo(f|Me diff --git a/images/nodeblock_icons.png b/images/nodeblock_icons.png new file mode 100644 index 0000000000000000000000000000000000000000..18b2af2db156ac0d8ee84f359190a7f6ee0406af GIT binary patch literal 2617 zcmV-93dZ$`P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D3C>AGK~#8N?VW3E zPFotspSGx?TDMZAXtgfKU5Y4iYm+hvAu`4-lOsYhlBVH9zsQL%m5>ROBXLAVM&x87 zK`|pDq;aXt;keW#?#(DI#;w$?v`l}?UbXw)`nKvl)A9V0<=t!Twf0(j?|1#zv!1oy z3QJ2%`*{EUeNhxeQJS9k_;~XfZvIsiMNz&gDz=KEC?UY78^M~rilQh=!|YA-TD6ku>S~FIh>&;h-pSRgS7rbH{qpqb zQzy5gC`vOUB2%KWR!UNeNck`4me3pqejm4VN!pmTlX%nPWxyO-TD$zDpOmxA711 z+)4UOt|Vqp7b#4ak~2<{5B%wAy*^tk+PQP*%Inv!Wz(ija^%PnBecLLaXLRP1@e*K z7YOaRapPp~-o28Yoozx{S(z+bwoJNp>*nNAf(7BV;u|X7zkgrn{Sh8Md}xH&{SGC+PmhZ`vC#7Y}md0{c>W4wi)2jWgIeq%HL`6l_ zd52@hj4|)ExVYE|C>E|S5aKs)-k9$M)-T_$K;o<@&6$XVgoGauOE=Nc(dLsyu&Gn0 z%FUZMjW7aM%ZL#pq-)o%(xXQYxpwWE@!OV{mz#5RmMd4Tm`>y-F)`7c!`iuX=Z;Bx z{P?jH6cp6Pv1(SXTq)D0O*7Nt!Gj0OsZ*zn1(%+lZjNof<;$0wDJ*4~iY{Kf*pwMH zYLw}`p7#W_n|nKZ_N-)NWXO^wOUx8@{`~o-Z0pvo%~TneiH&oA+*eLcj?A7t+oV%} zX=$lxKhQo~hBk8zdp?-9k%vLb-k<0F5+68lpvglz$9?sIai0$=Ie|D#8bIL3y!h8{`80obGhSlGq>9}At2{4rl6>G#PXk)EH9~B@ z{GL7t2n!37)YMd2v}jSSg-bph{^PK@{f*6ErxYmA_=Sc7{4QHBm3 zIv4=~YZci}&$){iFPbuRWID9X!?e~4Rec~5)?&vBC2p_d?)ZqC6}8yd*gAqr{Y-x; zM|)_OCD^tx2mt~~;5pJBj>(I!)QXuu$iwx4>k)4#wq3Tp_Sx8U-iIA5ZD62bVbm_R zkr#K7T@-TRXr zAH5_?)kNlJi1f4lIH`j zs*C<8H-6eJ&!v|nWaK!h!4Xhw`N0ArfKSd7Iv!h%jvhVgq=?B!e$O8|&8>uzs!E#0H)L+s*hMxbZbe zsVhMVJ#!=`zDCoE4@-r&qt=_sKg)lLD8%-DX{IL%?t__>L*Tp*0QrSq-}@{*NKUVF>U|?!Uy%a5Sj{Wq1yIYYt*KD!l1!9 zW+9Zp5@)dW%+GV+UOac5*k{Lg!Vb2iDN1OGmhgxg{ptTF`7XOKsF2dXMrI=-0}H4J z0e2$+Rw^sb)}mzEc;UhYDJdy&a)^u_JJzgTb8g?hee&_6w|n{AxpUH{O&hbjfkhVr z)9LNox0{u5R4|$K zo}CngqM{;W(IFP@xXsHCI1qi?E{-cIDvWrrYEJ#+ojiH65n>jt5KOLv&okJzv0~5t zLt$Z|ynXvtMvfe5MECgd<0hTedZ6C8xH!`$1|@`=pHSN!0a)Yg0-!CP!be+s_3CAW zls4JG&I{Vh?tsk9Ow(p}d;RT3VE;9BvHO8~@mYJ;Y445q?cQm?b$C7qEx$};P+?}H zD52$pR91z_oBPF5QCc^AXrC;tD=$mwg(9zW761NKUX~SkEvEU?)1ZDq#)8+vg$qsR zMu_N4?4e9bN-`p4JK4^iJH4#hZw;o@Jg4URD>X$?J`?l%Lg^v@kSl-Bb5iQ3(Z3)A z1a!xa9kO}zW+NcOhYvSOF4g<#q9;|S|t z4fWr9P!#3s(Ug|0U$H&&y8wS_p%JA0GtD_t6h&zsw4GHTJ!Q9C%m2|ysh=-$BPfcZ ze2cuwpVT0s4TQQ86h%>*kOsLC6h%>#ra|2ZilQhXM8#H76eR?x*eZ&mga8#=MNyQm bj>vxjMGT|S--j6x00000NkvXXu0mjfjp7#< literal 0 HcmV?d00001 diff --git a/images/other_icons.png b/images/other_icons.png index b4f5677e65d2bfa9eb833478133eda958a258c2e..2f69a9bb924f1dd47f84957a8a9b64c9a3f7bd54 100644 GIT binary patch delta 3966 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 diff --git a/js/constants.js b/js/constants.js index c036d69..adb3be1 100644 --- a/js/constants.js +++ b/js/constants.js @@ -71,6 +71,7 @@ export class Timings { // ms static ON_CHANGE_GAP = 200 static ON_WINDOW_RESIZE_GAP = 27 static GROUP_CHANGE_DELAY = 10 + static ALLOW_LAYOUT = 1000 } export class Colors { diff --git a/js/controller_panel.js b/js/controller_panel.js index 51dd53d..744fcdc 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -1,6 +1,6 @@ import { app } from "../../scripts/app.js"; -import { create, get_node, add_tooltip, clamp, classSet, defineProperty, find_controller_parent, createBounds } from "./utilities.js"; +import { create, get_node, add_tooltip, clamp, classSet, defineProperty, find_controller_parent, createBounds, title_if_overflowing } from "./utilities.js"; import { family_names, GroupManager } from "./groups.js"; import { UpdateController } from "./update_controller.js"; @@ -512,16 +512,14 @@ export class ControllerPanel extends HTMLDivElement { header_mouse_down(e) { if (app.canvas.read_only) return - //if (e.target==this.header_tabs || e.target.parentElement==this.header_tabs) { - this.being_dragged = true - this.drag_threshold = false - this.classList.add('grabbed') - this.offset_x = e.x - this.settings.position.x - this.offset_y = e.y - this.settings.position.y - //e.preventDefault() - //e.stopPropagation() - //} + + this.being_dragged = true + this.drag_threshold = false + this.classList.add('grabbed') + this.offset_x = e.x - this.settings.position.x + this.offset_y = e.y - this.settings.position.y } + mouse_move(e) { if (app.canvas.read_only) return this.style.cursor = '' @@ -703,19 +701,19 @@ export class ControllerPanel extends HTMLDivElement { } this.mouse_down_on = null }) - //if (this.settings.groups.length==1) { - //tab.only_tab = true - tab.addEventListener('click', (e)=>{ - e.preventDefault() - e.stopPropagation() - if (e.ctrlKey) { - //app.canvas.fitViewToSelectionAnimated() - app.canvas.animateToBounds(createBounds(app.graph._groups.filter((g)=>(g.title==nm)))) - } else { - if (this.settings.groups.length==1) this.show_group_select(e, true) - } - }) - //} + + tab.addEventListener('click', (e)=>{ + e.preventDefault() + e.stopPropagation() + if (e.ctrlKey) { + app.canvas.animateToBounds(createBounds(app.graph._groups.filter((g)=>(g.title==nm)))) + } else { + if (this.settings.groups.length==1) this.show_group_select(e, true) + } + }) + + title_if_overflowing(tab, nm) + }) } diff --git a/js/utilities.js b/js/utilities.js index 4fec8c0..aa0dbd3 100644 --- a/js/utilities.js +++ b/js/utilities.js @@ -1,5 +1,5 @@ import { app } from "../../scripts/app.js" -import { SettingIds } from "./constants.js"; +import { SettingIds, Timings } from "./constants.js"; import { getSettingValue } from "./settings.js"; export var mouse_is_down @@ -161,4 +161,16 @@ export function createBounds(objects, padding = 10) { bounds[2] - bounds[0] + 2 * padding, bounds[3] - bounds[1] + 2 * padding ]; - } \ No newline at end of file + } + +export function title_if_overflowing(element, title) { + setTimeout(_title_if_overflowing, Timings.ALLOW_LAYOUT, element, title) +} + +function _title_if_overflowing(element, title) { + if (element.clientWidth < element.scrollWidth) { + element.title = title + } else { + if (element.title) delete element.title + } +} \ No newline at end of file From 6168f5d4e94e87847521f7d733dd6124ce41810f Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 22 Nov 2024 12:59:48 +1100 Subject: [PATCH 02/19] 289 --- js/controller.css | 30 ++++++++++++++++++++++- js/image_manager.js | 44 +++++++++++++++++---------------- js/nodeblock.js | 60 +++++++++++++++++++++++++++++++-------------- 3 files changed, 94 insertions(+), 40 deletions(-) diff --git a/js/controller.css b/js/controller.css index 8a426b7..47dc32d 100644 --- a/js/controller.css +++ b/js/controller.css @@ -33,7 +33,7 @@ --second-back-color: #222222; --third-back-color: #353535; - --overlay-background: #ffffff66; + --overlay-background: #ffffff99; --overlay-foreground: #353535; --mute-button-color:#ff6e6e; @@ -576,6 +576,34 @@ i { font-size: 80%; } +.overlay_paging { + display:flex; + padding-bottom: 1px; + bottom: unset; + top: 2.5em; +} + +.overlay_paging_icon { + font-family: 'primeicons'; + min-width: 1em; +} +.overlay_paging_text { + text-align: center; + min-width: 2.5em; + padding: 0px 4px; + position: relative; + top: -1px; +} +.overlay_paging_icon:before { + +} +.overlay_paging_icon.prev:before { + content: "\e928"; +} +.overlay_paging_icon.next:before { + content: "\e92a"; +} + .tooltip { position: relative; display: inline-block; diff --git a/js/image_manager.js b/js/image_manager.js index d372b41..090736f 100644 --- a/js/image_manager.js +++ b/js/image_manager.js @@ -31,27 +31,27 @@ export class ImageManager { static init() { ImageManager.node_listener_map = {} - ImageManager.node_src_map = {} + ImageManager.node_urls_map = {} ImageManager.executing_node = null } static node_listener_map = {} // map to Set - static node_src_map = {} // map to url + static node_urls_map = {} // map to url static executing_node = null static add_listener(node_id, listener) { if (!ImageManager.node_listener_map[node_id]) ImageManager.node_listener_map[node_id] = new Set() ImageManager.node_listener_map[node_id].add(listener) - const src = ImageManager.node_src_map[node_id] - if (src) listener.manage_image(src) + const urls = ImageManager.node_urls_map[node_id] + if (urls) listener.manage_image(urls) } static _send(node_id) { - const src = ImageManager.node_src_map[node_id] - if (src) { + const urls = ImageManager.node_urls_map[node_id] + if (urls) { if (ImageManager.node_listener_map[node_id]) { Array.from(ImageManager.node_listener_map[node_id]).forEach((l)=>{ - if (!l.manage_image(src, (ImageManager.executing_node!=null))) ImageManager.node_listener_map[node_id].delete(node_id) + if (!l.manage_image(urls, (ImageManager.executing_node!=null))) ImageManager.node_listener_map[node_id].delete(node_id) }) } } @@ -65,8 +65,8 @@ export class ImageManager { } } - static _set_source(node_id, src) { - ImageManager.node_src_map[node_id] = src + static _set_sources(node_id, srcs) { + ImageManager.node_urls_map[node_id] = srcs ImageManager._send(node_id) } @@ -74,17 +74,18 @@ export class ImageManager { static node_img_change(node) { Debug.trivia(`node_img_change called for node ${node.id} which now has ${node.imgs.length} image(s)`) if (node.imgs.length == 0) return - const v = node.imgs[0] + if (ImageManager.executing_node==null || is_image_upload_node(node)) { - var src = v.src ?? api.apiURL( - `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}` - ) - ImageManager._set_source(node.id, src) + const srcs = [] + node.imgs.forEach((v)=>{ + srcs.push(v.src ?? api.apiURL( `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}`) ) + }) + ImageManager._set_sources(node.id, srcs) } } static on_execution_start() { - ImageManager.node_src_map = {} + ImageManager.node_urls_map = {} } static on_executing(e) { @@ -93,16 +94,17 @@ export class ImageManager { static on_b_preview(e) { Debug.trivia(`ImageManager on_b_preview ${ImageManager.executing_node}`) - ImageManager._set_source( ImageManager.executing_node, window.URL.createObjectURL(e.detail) ) + ImageManager._set_sources( ImageManager.executing_node, [window.URL.createObjectURL(e.detail),] ) } static on_executed(e) { Debug.trivia(`ImageManager on_executed ${e.detail.node}`) - const v = e.detail?.output?.images?.[0] - if (v) { - ImageManager._set_source( e.detail.node, api.apiURL( - `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}` - ) ) + const srcs = [] + e.detail?.output?.images?.forEach((v)=>{ + srcs.push(api.apiURL( `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}`) ) + }) + if (srcs.length>0) { + ImageManager._set_sources( e.detail.node, srcs ) } } diff --git a/js/nodeblock.js b/js/nodeblock.js index 9586269..08a6b26 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -50,6 +50,7 @@ export class NodeBlock extends HTMLSpanElement { this.parent_controller = parent_controller this.node = node this.mode = this.node.mode + this.image_index = null Debug.trivia(`creating nodeblock for ${this.node?.id} on controller ${this.parent_controller?.index}`) if (!this.node.properties.controller_details) { @@ -180,7 +181,7 @@ export class NodeBlock extends HTMLSpanElement { } image_progress_update(value, max) { this.on_progress(value, max) } - + on_progress(value, max) { if (value) { this.title_bar.appendChild(this.progress) @@ -266,6 +267,13 @@ export class NodeBlock extends HTMLSpanElement { this.image_image = create('img', 'nodeblock_image', this.image_panel) this.image_image.addEventListener('load', () => {this.rescale_image()}) + this.image_paging = create('span', 'overlay overlay_paging', this.image_panel) + + this.image_prev = create('span', 'overlay_paging_icon', this.image_paging) + this.image_xofy = create('span', 'overlay_paging_text', this.image_paging) + this.image_next = create('span', 'overlay_paging_icon', this.image_paging) + this.image_prev.addEventListener('click', ()=>{this.previousImage()}) + this.image_next.addEventListener('click', ()=>{this.nextImage()}) if (!app.canvas.read_only) { make_resizable( this.image_panel, this.node.id, this.image_panel_id, this.node.properties.controller_widgets[this.image_panel_id] ) @@ -295,19 +303,20 @@ export class NodeBlock extends HTMLSpanElement { this.main = new_main if (this.node.imgs && this.node.imgs.length>0) { - this.show_image(this.node.imgs[0].src) - //ImageManager.node_img_change(this.node) + const urls = [] + this.node.imgs.forEach((i)=>{urls.push(i.src)}) + this.show_images(urls) } this.valid_nodeblock = true if (!(isImageNode(this.node) || this.widget_count || (this.node.imgs && this.node.imgs.length>0))) this.minimised = true } - manage_image(url, running) { + manage_image(urls, running) { if (!this.parentElement) return false if (!(this.bypassed || this.hidden)) { /* take anything when running, or if we have nothing, or if we have a blob; otherwise reject blobs */ - if (running || !this.image_image.src || image_is_blob(this.image_image.src) || !image_is_blob(url)) this.show_image(url) + if (running || !this.image_image.src || image_is_blob(this.image_image.src) || !image_is_blob(urls[0])) this.show_images(urls) } return true } @@ -343,16 +352,9 @@ export class NodeBlock extends HTMLSpanElement { this.image_image.style.width = `${w}px` } else { const scaled_height_fraction = (im_h * w) / (im_w * box.height) - //if (scaled_height_fraction<=1) { - // this.image_panel.style.height = `${(im_h * w) / (im_w)}px` - // this.image_panel.style.maxHeight = `${(im_h * w) / (im_w)}px` - // this.image_image.style.height = `100%` - // this.image_image.style.width = `${w}px` - //} else { - this.image_panel.style.maxHeight = `unset` - this.image_image.style.height = `100%` - this.image_image.style.width = `${w/scaled_height_fraction}px` - //} + this.image_panel.style.maxHeight = `unset` + this.image_image.style.height = `100%` + this.image_image.style.width = `${w/scaled_height_fraction}px` } } } @@ -360,15 +362,37 @@ export class NodeBlock extends HTMLSpanElement { this.rescaling = false } - show_image(url) { - classSet(this.image_panel, 'nodeblock_image_empty', !url) - classSet(this.image_pin, 'hidden', !url) + show_images(urls) { + const nothing = !(urls && urls.length>0) + classSet(this.image_panel, 'nodeblock_image_empty', nothing) + classSet(this.image_pin, 'hidden', nothing) + + if (this.image_index===null || this.image_index>=urls.length) this.image_index = 0 + + this.urls = urls + const url = urls[this.image_index] if (this.image_image.src != url) { this.image_image.src = url this.image_panel.style.maxHeight = '' } + + classSet(this.image_paging, 'hidden', urls.length<2) + classSet(this.image_prev, 'prev', this.image_index>0) + this.image_xofy.innerHTML = `${this.image_index+1}/${urls.length}` + classSet(this.image_next, 'next', this.image_index Date: Fri, 22 Nov 2024 21:26:49 +1100 Subject: [PATCH 03/19] Update nodeblock.js --- js/nodeblock.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/nodeblock.js b/js/nodeblock.js index 08a6b26..f5540fd 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -384,11 +384,13 @@ export class NodeBlock extends HTMLSpanElement { } previousImage() { + if (this.image_index==0) return this.image_index -= 1 this.show_images(this.urls) } nextImage() { + if (this.image_index+1 == this.urls.length) return this.image_index += 1 this.show_images(this.urls) } From d4d27d07f5d6e5f15437e7ae302543e1559a8dae Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 22 Nov 2024 23:18:45 +1100 Subject: [PATCH 04/19] loop images --- js/nodeblock.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/js/nodeblock.js b/js/nodeblock.js index f5540fd..f7b5a47 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -378,20 +378,18 @@ export class NodeBlock extends HTMLSpanElement { } classSet(this.image_paging, 'hidden', urls.length<2) - classSet(this.image_prev, 'prev', this.image_index>0) + classSet(this.image_prev, 'prev', true) this.image_xofy.innerHTML = `${this.image_index+1}/${urls.length}` - classSet(this.image_next, 'next', this.image_index Date: Sun, 24 Nov 2024 14:30:23 +1100 Subject: [PATCH 05/19] 214 --- js/context_menu.js | 9 +++++++++ js/nodeblock.js | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 js/context_menu.js diff --git a/js/context_menu.js b/js/context_menu.js new file mode 100644 index 0000000..bdbbc26 --- /dev/null +++ b/js/context_menu.js @@ -0,0 +1,9 @@ +import { app, ComfyApp } from "../../scripts/app.js" + +export function new_context_menu(e, title, values) { + const options = { + "title":title, + "event":e + } + new LiteGraph.ContextMenu(values, options, app.canvas.getCanvasWindow()) +} diff --git a/js/nodeblock.js b/js/nodeblock.js index f7b5a47..05c31dd 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -1,4 +1,4 @@ -import { app } from "../../scripts/app.js"; +import { app, ComfyApp } from "../../scripts/app.js"; import { ComfyWidgets } from "../../scripts/widgets.js"; @@ -9,6 +9,7 @@ import { image_is_blob, ImageManager, is_image_upload_node, isImageNode } from " import { UpdateController } from "./update_controller.js"; import { Debug } from "./debug.js"; import { Highlighter } from "./highlighter.js"; +import { new_context_menu } from "./context_menu.js"; function is_single_image(data) { return (data && data.items && data.items.length==1 && data.items[0].type.includes("image")) } @@ -267,6 +268,20 @@ export class NodeBlock extends HTMLSpanElement { this.image_image = create('img', 'nodeblock_image', this.image_panel) this.image_image.addEventListener('load', () => {this.rescale_image()}) + this.image_image.addEventListener('click', (e)=>{ + if (e.ctrlKey) { + new_context_menu(e, "Image", [ + { + "title":"Open in Mask Editor", + "callback":()=>{ + ComfyApp.copyToClipspace(this.node) + ComfyApp.clipspace_return_node = this.node + ComfyApp.open_maskeditor() + } + }, + ]) + } + }) this.image_paging = create('span', 'overlay overlay_paging', this.image_panel) this.image_prev = create('span', 'overlay_paging_icon', this.image_paging) From dcfb940180442b4283d623e97e1d44bddfddbf5c Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 25 Nov 2024 09:55:13 +1100 Subject: [PATCH 06/19] mask edit --- js/constants.js | 1 + js/context_menu.js | 16 +++++++++++++--- js/image_manager.js | 19 +++++++------------ js/nodeblock.js | 16 +++++++++++++--- js/panel_entry.js | 25 +++++++++++++++++-------- 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/js/constants.js b/js/constants.js index adb3be1..e9c5ed2 100644 --- a/js/constants.js +++ b/js/constants.js @@ -63,6 +63,7 @@ export class InclusionOptions { } export class Timings { // ms + static GENERIC_SHORT_DELAY = 20 static DRAG_PAUSE_OVER_BACKGROUND = 500 static SLIDER_ACTIVE_DELAY = 300 static UPDATE_EXCEPTION_WAITTIME = 10000 diff --git a/js/context_menu.js b/js/context_menu.js index bdbbc26..026c0ca 100644 --- a/js/context_menu.js +++ b/js/context_menu.js @@ -1,9 +1,19 @@ import { app, ComfyApp } from "../../scripts/app.js" -export function new_context_menu(e, title, values) { +var context_menu + +function close_context_menu() { + if (context_menu) context_menu.root.remove() + context_menu = null +} + +function _open_context_menu(e, title, values) { const options = { "title":title, - "event":e + "event":e, } - new LiteGraph.ContextMenu(values, options, app.canvas.getCanvasWindow()) + context_menu = LiteGraph.ContextMenu(values, options, app.canvas.getCanvasWindow()) } +export function open_context_menu(e, title, values) { setTimeout(_open_context_menu, 10, e, title, values) } + +window.addEventListener('click',close_context_menu) diff --git a/js/image_manager.js b/js/image_manager.js index 090736f..3453246 100644 --- a/js/image_manager.js +++ b/js/image_manager.js @@ -16,11 +16,12 @@ export function image_is_blob(url) { export function clean_image_manager() { ImageManager.node_listener_map = {} - //Object.keys(ImageManager.node_listener_map).forEach((k)=>{ - // ImageManager.node_listener_map[k] = Set.from(ImageManager.node_listener_map[k].filter((v)=>v.parentElement)) - //}) } +export function get_image_url(v) { + if (v.src) return v.src + return api.apiURL( `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}`) +} export class ImageManager { /* @@ -77,9 +78,7 @@ export class ImageManager { if (ImageManager.executing_node==null || is_image_upload_node(node)) { const srcs = [] - node.imgs.forEach((v)=>{ - srcs.push(v.src ?? api.apiURL( `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}`) ) - }) + node.imgs.forEach((v)=>{ srcs.push(get_image_url(v)) }) ImageManager._set_sources(node.id, srcs) } } @@ -100,12 +99,8 @@ export class ImageManager { static on_executed(e) { Debug.trivia(`ImageManager on_executed ${e.detail.node}`) const srcs = [] - e.detail?.output?.images?.forEach((v)=>{ - srcs.push(api.apiURL( `/view?filename=${encodeURIComponent(v.filename ?? v)}&type=${v.type ?? "input"}&subfolder=${v.subfolder ?? ""}`) ) - }) - if (srcs.length>0) { - ImageManager._set_sources( e.detail.node, srcs ) - } + e.detail?.output?.images?.forEach((v)=>{ srcs.push(get_image_url(v)) }) + if (srcs.length>0) { ImageManager._set_sources( e.detail.node, srcs ) } } diff --git a/js/nodeblock.js b/js/nodeblock.js index 05c31dd..a2dfd32 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -5,11 +5,12 @@ import { ComfyWidgets } from "../../scripts/widgets.js"; import { create, darken, classSet, mode_change, focus_mode } from "./utilities.js"; import { Entry } from "./panel_entry.js" import { make_resizable } from "./resize_manager.js"; -import { image_is_blob, ImageManager, is_image_upload_node, isImageNode } from "./image_manager.js"; +import { get_image_url, image_is_blob, ImageManager, is_image_upload_node, isImageNode } from "./image_manager.js"; import { UpdateController } from "./update_controller.js"; import { Debug } from "./debug.js"; import { Highlighter } from "./highlighter.js"; -import { new_context_menu } from "./context_menu.js"; +import { open_context_menu } from "./context_menu.js"; +import { Timings } from "./constants.js"; function is_single_image(data) { return (data && data.items && data.items.length==1 && data.items[0].type.includes("image")) } @@ -241,11 +242,13 @@ export class NodeBlock extends HTMLSpanElement { this.image_panel = create("div", "nodeblock_image_panel nodeblock_image_empty", new_main) this.widget_count = 0 + this.entry_controlling_image = null this.node.widgets?.forEach(w => { if (!this.node.properties.controller_widgets[w.name]) this.node.properties.controller_widgets[w.name] = {} const properties = this.node.properties.controller_widgets[w.name] const e = new Entry(this.parent_controller, this, this.node, w, properties) if (e.valid()) { + if (e.combo_for_image) this.entry_controlling_image = e new_main.appendChild(e) this[w.name] = e this.widget_count += 1 @@ -270,7 +273,7 @@ export class NodeBlock extends HTMLSpanElement { this.image_image.addEventListener('load', () => {this.rescale_image()}) this.image_image.addEventListener('click', (e)=>{ if (e.ctrlKey) { - new_context_menu(e, "Image", [ + open_context_menu(e, "Image", [ { "title":"Open in Mask Editor", "callback":()=>{ @@ -377,7 +380,14 @@ export class NodeBlock extends HTMLSpanElement { this.rescaling = false } + select_image(nm) { + this.show_images([get_image_url(nm),]) + } + show_images(urls) { + if (this.entry_controlling_image) setTimeout(()=>{ + this.entry_controlling_image.update_combo_selection() + }, Timings.GENERIC_SHORT_DELAY) const nothing = !(urls && urls.length>0) classSet(this.image_panel, 'nodeblock_image_empty', nothing) classSet(this.image_pin, 'hidden', nothing) diff --git a/js/panel_entry.js b/js/panel_entry.js index d63570d..4a44f3f 100644 --- a/js/panel_entry.js +++ b/js/panel_entry.js @@ -84,6 +84,8 @@ export class Entry extends HTMLDivElement { } if (!this.input_element) return + + this.combo_for_image = (this.target_widget.name=='image' && this.target_widget._real_value && this.target_widget.type=="combo") switch (target_widget.type) { case 'button': @@ -107,23 +109,30 @@ export class Entry extends HTMLDivElement { } } - //const onRemove = target_widget.onRemove - //target_widget.onRemove = function () { - // onRemove?.apply(target_widget, arguments) - // UpdateController.make_request('widget removed') - //} - WidgetChangeManager.add_listener(target_widget, this) this.render() } + update_combo_selection() { + if (this.input_element) { + this.input_element.value = this.target_widget.value + this.entry_value.innerText = this.target_widget.value + } else { + Debug.important("update_combo with no input_element") + } + } + wcm_manager_callback() { if (!this.parent_controller?.contains(this)) return false; this.input_element.value = this.target_widget.value; if (this.input_element.wcm_manager_callback) this.input_element.wcm_manager_callback() if (this.input_element.redraw) this.input_element.redraw(true) - if (this.target_widget.name=='image' && this.target_widget._real_value && this.target_widget.type=="combo") { - this.parent_nodeblock.show_image(this.target_widget.value) + if (this.combo_for_image) { + try { + this.parent_nodeblock.select_image(this.target_widget.value) + } catch (e) { + Debug.error('wcm_manager_callback',e) + } } return true; } From 481d63c690982442eddf0281eb1889ccc610b902 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 25 Nov 2024 11:17:21 +1100 Subject: [PATCH 07/19] 283 --- js/controller.js | 25 ++++++++++++++++++------- js/controller_panel.js | 3 +++ js/panel_entry.js | 12 ++++++------ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/js/controller.js b/js/controller.js index 6fe9825..734d4d5 100644 --- a/js/controller.js +++ b/js/controller.js @@ -113,19 +113,18 @@ app.registerExtension({ } catch (e) { Debug.error("add controls", e) } try { - const on_change = app.graph.on_change - app.graph.on_change = function () { + const onAfterChange = app.graph.onAfterChange + app.graph.onAfterChange = function () { try { - on_change?.apply(this,arguments) + onAfterChange?.apply(this,arguments) OnChangeController.on_change() } catch (e) { - Debug.error("on==_change", e) + Debug.error("onAfterChange", e) } } - Debug.trivia("*** in setup, ADDED on_change") } catch (e) { - Debug.error("ADDING on_change", e) - console.error(e) + Debug.error("ADDING onAfterChange", e) + } const draw = app.canvas.onDrawForeground; @@ -205,7 +204,19 @@ app.registerExtension({ nodeType.prototype.onInputAdded = function () { onInputAdded?.apply(this,arguments) ControllerPanel.node_change(this.id) + app.graph.afterChange() + } + const onOutputRemoved = nodeType.prototype.onOutputRemoved + nodeType.prototype.onOutputRemoved = function () { + onOutputRemoved?.apply(this,arguments) + ControllerPanel.node_change(this.id) } + const onOutputAdded = nodeType.prototype.onOutputAdded + nodeType.prototype.onOutputAdded = function () { + onOutputAdded?.apply(this,arguments) + ControllerPanel.node_change(this.id) + } + const onModeChange = nodeType.prototype.onModeChange nodeType.prototype.onModeChange = function () { onModeChange?.apply(this,arguments) diff --git a/js/controller_panel.js b/js/controller_panel.js index 744fcdc..ddd22d3 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -340,6 +340,9 @@ export class ControllerPanel extends HTMLDivElement { } static node_change(node_id, moreinfo) { + setTimeout(ControllerPanel._node_change, Timings.GENERIC_SHORT_DELAY, node_id, moreinfo) + } + static _node_change(node_id, moreinfo) { Object.values(ControllerPanel.instances).forEach((cp)=>{cp._node_change(node_id, moreinfo)}) } diff --git a/js/panel_entry.js b/js/panel_entry.js index 4a44f3f..dd8b82d 100644 --- a/js/panel_entry.js +++ b/js/panel_entry.js @@ -64,7 +64,8 @@ export class Entry extends HTMLDivElement { this.entry_label = create('span','entry_label', this, {'innerText':widget_label, 'draggable':false} ) this.entry_value = create('span','entry_label value', this, {'innerText':target_widget.value, 'draggable':false} ) this.input_element = create("select", 'input', this, {"doesntBlockRefresh":true}) - target_widget.options.values.forEach((o) => this.input_element.add(new Option(o,o))) + const choices = (target_widget.options.values instanceof Function) ? target_widget.options.values() : target_widget.options.values + choices.forEach((o) => this.input_element.add(new Option(o,o))) this.input_element.addEventListener("change", (e)=>{ this.entry_value.innerText = e.target.value }) @@ -101,10 +102,9 @@ export class Entry extends HTMLDivElement { target_widget.element.addEventListener('input', (e)=>{WidgetChangeManager.notify(target_widget)}) } else { if (!target_widget.original_callback) target_widget.original_callback = target_widget.callback - target_widget.callback = () => { - if (target_widget.original_callback) { - target_widget.original_callback(target_widget.value) - } + const callback = target_widget.original_callback + target_widget.callback = function() { + callback?.apply(this, arguments) // (target_widget.value, app.canvas, this.pa) WidgetChangeManager.notify(target_widget) } } @@ -146,7 +146,7 @@ export class Entry extends HTMLDivElement { const v = this.typecheck(e.target.value) if (v != null && this.target_widget.value != v) { this.target_widget.value = v - this.target_widget.callback?.(v) + this.target_widget.callback?.(v, app.canvas, this.parent_nodeblock.node, [e.x,e.y], e) WidgetChangeManager.notify(this.target_widget) } } finally { UpdateController.pop_pause() } From 0ea5996aff7df52cdd1901ebfce8176b5733550a Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 25 Nov 2024 11:34:49 +1100 Subject: [PATCH 08/19] cahnges --- js/update_controller.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/js/update_controller.js b/js/update_controller.js index 92b8a27..2e18fcd 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -83,6 +83,23 @@ export class UpdateController { } } +function hash_node(node) { + /* + hash all the things we want to check for changes. + */ + var hash = `${node.bgcolor} ${node.title} ` + node.inputs.forEach((i)=>{hash += `${i.name} `}) + node.outputs.forEach((o)=>{hash += `${o.name} `}) + return hash +} + +function node_changed(node) { + const new_hash = hash_node(node) + if (new_hash == node._controller_hash) return false + node._controller_hash = new_hash + return true +} + export class OnChangeController { static gap_request_stack = 0 static on_change() { @@ -95,14 +112,7 @@ export class OnChangeController { if (GroupManager.check_for_changes()) { UpdateController.make_request("on_change, change in groups") } else { - const changed_nodes = [] - app.graph._nodes.forEach((node)=>{ - if (node.bgcolor != node._controller_bgcolor || node.title != node._controller_title) { - changed_nodes.push(node.id) - node._controller_bgcolor = node.bgcolor - node._controller_title = node.title - } - }) + const changed_nodes = app.graph._nodes.filter((node)=>(node_changed(node))) if (changed_nodes.length > 1) { UpdateController.make_request("on_change, multiple nodes changed") } else if (changed_nodes.length == 1) { From 2aea13bc9bec00d5d29a36cbdd071d79004fa5b4 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 25 Nov 2024 12:13:46 +1100 Subject: [PATCH 09/19] 1.5 --- js/constants.js | 40 ++++----- js/controller.js | 30 ++++--- js/controller_panel.js | 2 +- js/debug.js | 33 +++++--- js/{controller_controls.js => options.js} | 99 +++++++++-------------- js/update_controller.js | 22 ++--- 6 files changed, 110 insertions(+), 116 deletions(-) rename js/{controller_controls.js => options.js} (74%) diff --git a/js/constants.js b/js/constants.js index e9c5ed2..13148fc 100644 --- a/js/constants.js +++ b/js/constants.js @@ -2,23 +2,23 @@ export const VERSION = "1.4.1" export class SettingIds { - static KEYBOARD_TOGGLE = "Controller.options.keyboard" - static CONTROL_AFTER_GENERATE = "Controller.options.control_after_generate" - static SCROLL_MOVES_SLIDERS = "Controller.options.scroll_moves_slider" - static SCROLL_REVERSED = "Controller.options.scroll_reversed_for_slider" - static EDIT_SLIDERS = "Controller.options.edit_slider" - static DEBUG_LEVEL = "Controller.debug.level" - static FONT_SIZE = "Controller.options.font_size" - static TOOLTIPS = "Controller.options.tooltips" - static DEFAULT_APPLY_TO_SIMILAR = "Controller.options.default_apply_to_similar" - static SHOW_SCROLLBARS = "Controller.options.show_scrollbars" - static SHOW_IN_FOCUS_MODE = "Controller.options.show_in_focus_mode" + static KEYBOARD_TOGGLE = "Controller.Display.keyboard" + static CONTROL_AFTER_GENERATE = "Controller.Display.control_after_generate" + static SCROLL_MOVES_SLIDERS = "Controller.Sliders.scroll_moves_slider" + static SCROLL_REVERSED = "Controller.Sliders.scroll_reversed_for_slider" + static EDIT_SLIDERS = "Controller.Sliders.edit_slider" + static DEBUG_LEVEL = "Controller.Debug.level" + static FONT_SIZE = "Controller.Display.font_size" + static TOOLTIPS = "Controller.Display.tooltips" + static DEFAULT_APPLY_TO_SIMILAR = "Controller.Sliders.default_apply_to_similar" + static SHOW_SCROLLBARS = "Controller.Display.show_scrollbars" + static SHOW_IN_FOCUS_MODE = "Controller.Display.show_in_focus_mode" } export class SettingNames { static KEYBOARD_TOGGLE = "Toggle controller visibility:" - static FONT_SIZE = "Controller font base size:" - static CONTROL_AFTER_GENERATE = "Show control after generate" + static FONT_SIZE = "Base font size:" + static CONTROL_AFTER_GENERATE = "Show 'control before/after generate'" static TOOLTIPS = "Show tooltips" static DEFAULT_APPLY_TO_SIMILAR = "Default apply to similar" static SHOW_IN_FOCUS_MODE = "Show controllers in focus mode" @@ -32,15 +32,15 @@ export class SettingNames { export class Generic { static NEVER = "Never" static ALWAYS = "Always" - static SHIFT = "When shift key pressed" - static CTRL = "When ctrl key pressed" + static SHIFT = "shift key" + static CTRL = "ctrl key" static OFF = "Off" - static THIN = "Thing" + static THIN = "Thin" static NORMAL = "Normal" - static D0 = "Only show errors" - static D1 = "Normal debugging" - static D2 = "Extra information" - static D3 = "Maximum information" + static D0 = "Minimal" + static D1 = "Normal" + static D2 = "Extra" + static D3 = "Verbose" } export class Tooltips { diff --git a/js/controller.js b/js/controller.js index 734d4d5..031076a 100644 --- a/js/controller.js +++ b/js/controller.js @@ -1,17 +1,16 @@ import { app } from "../../scripts/app.js" import { api } from "../../scripts/api.js" import { ControllerPanel } from "./controller_panel.js" -import { create, defineProperty, mouse_change } from "./utilities.js" -import { add_controls } from "./controller_controls.js" +import { create, mouse_change } from "./utilities.js" +import { OPTIONS } from "./options.js" import { add_control_panel_options, NodeInclusionManager, } from "./node_inclusion.js" import { OnChangeController, UpdateController } from "./update_controller.js" import { Debug } from "./debug.js" -import { BASE_PATH } from "./constants.js" +import { BASE_PATH, SettingIds } from "./constants.js" import { ImageManager } from "./image_manager.js" import { global_settings } from "./settings.js" -import { NodeBlock } from "./nodeblock.js" import { FancySlider } from "./input_slider.js" -import { SnapManager, WindowResizeManager } from "./snap_manager.js" +import { WindowResizeManager } from "./snap_manager.js" import { Highlighter } from "./highlighter.js" import { GroupManager } from "./groups.js" @@ -35,7 +34,7 @@ function on_setup() { NodeInclusionManager.node_change_callback = UpdateController.make_request GroupManager.change_callback = ControllerPanel.on_group_details_change - api.addEventListener('graphCleared', ControllerPanel.graph_cleared) + api.addEventListener('graphCleared', ControllerPanel.on_graphCleared) api.addEventListener('executed', ImageManager.on_executed) api.addEventListener('execution_start', ImageManager.on_execution_start) @@ -56,6 +55,17 @@ function on_setup() { ControllerPanel.handle_mouse_move(e) FancySlider.handle_mouse_move(e) }) + window.addEventListener('keypress', (e) => { + if (e.target.tagName=="CANVAS" || e.target.tagName=="BODY") { + const keysetting = app.ui.settings.getSettingValue(SettingIds.KEYBOARD_TOGGLE, "C") + if (keysetting==e.key) { + ControllerPanel.toggle() + e.preventDefault() + e.stopImmediatePropagation() + return false + } + } + }) const original_getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions; @@ -78,6 +88,7 @@ function on_setup() { app.registerExtension({ name: "cg.controller", + settings: OPTIONS, async beforeConfigureGraph() { UpdateController.configuring(true) @@ -107,17 +118,12 @@ app.registerExtension({ on_setup() } catch (e) { Debug.error("on setup", e) } - // add to the canvas menu, and keyboard shortcuts - try { - add_controls() - } catch (e) { Debug.error("add controls", e) } - try { const onAfterChange = app.graph.onAfterChange app.graph.onAfterChange = function () { try { onAfterChange?.apply(this,arguments) - OnChangeController.on_change() + OnChangeController.on_change('graph.onAfterChange') } catch (e) { Debug.error("onAfterChange", e) } diff --git a/js/controller_panel.js b/js/controller_panel.js index ddd22d3..12bf3a2 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -216,7 +216,7 @@ export class ControllerPanel extends HTMLDivElement { ControllerPanel.update_buttons() } - static graph_cleared() { + static on_graphCleared() { UpdateController.make_request("graph_cleared") } diff --git a/js/debug.js b/js/debug.js index 64cba27..393a913 100644 --- a/js/debug.js +++ b/js/debug.js @@ -1,17 +1,26 @@ import { app } from "../../scripts/app.js" import { SettingIds, VERSION } from "./constants.js" -export class Debug { - static last_message = null - static _log(message, level, repeatok) { - if ((message == Debug.last_message && !repeatok) || +export class _Debug { + constructor(title = "Controller") { + this.prefix = title + this.last_message = null + } + + _log(message, level, repeatok) { + if ((message == this.last_message && !repeatok) || level > app.ui.settings.getSettingValue(SettingIds.DEBUG_LEVEL, 1)) return - Debug.last_message = message - console.log(`Controller ${VERSION}: ${message}`) + this.last_message = message + console.log(`${VERSION} ${(this.prefix instanceof Function) ? this.prefix() : this.prefix}: (${level}) ${message}`) } - static error(message, e) { Debug._log(message, 0, true); console.error(e) } - static essential(message, repeatok) { Debug._log(message, 0, repeatok) } - static important(message, repeatok) { Debug._log(message, 1, repeatok) } - static extended(message, repeatok) { Debug._log(message, 2, repeatok) } - static trivia(message, repeatok) { Debug._log(message, 3, repeatok) } -} \ No newline at end of file + error(message, e) { + this._log(message, 0, true); + if (e) console.error(e) + } + essential(message, repeatok) { this._log(message, 0, repeatok) } + important(message, repeatok) { this._log(message, 1, repeatok) } + extended(message, repeatok) { this._log(message, 2, repeatok) } + trivia(message, repeatok) { this._log(message, 3, repeatok) } +} + +export const Debug = new _Debug() \ No newline at end of file diff --git a/js/controller_controls.js b/js/options.js similarity index 74% rename from js/controller_controls.js rename to js/options.js index 304a492..d982ed3 100644 --- a/js/controller_controls.js +++ b/js/options.js @@ -1,18 +1,26 @@ -import { app } from "../../scripts/app.js" import { SettingIds, SettingNames, Tooltips, Generic } from "./constants.js"; -import { ControllerPanel } from "./controller_panel.js"; -export function add_controls() { - app.ui.settings.addSetting({ +export const OPTIONS = [ + { id: SettingIds.KEYBOARD_TOGGLE, name: SettingNames.KEYBOARD_TOGGLE, type: "combo", options: [ {value:0, text:"Off"}, {value:"c", text:"c"}, {value:"C", text:"shift-C"}, {value:"o", text:"o"}, {value:"O", text:"shift-O"}], defaultValue: "C", - }); - - app.ui.settings.addSetting({ + }, + { + id: SettingIds.SHOW_SCROLLBARS, + name: SettingNames.SHOW_SCROLLBARS, + tooltip: Tooltips.SHOW_SCROLLBARS, + type: "combo", + options: [ {value:"no", text:Generic.OFF}, + {value:"thin", text:Generic.THIN}, + {value:"full", text:Generic.NORMAL}, + ], + defaultValue: "thin", + }, + { id: SettingIds.FONT_SIZE, name: SettingNames.FONT_SIZE, tooltip: Tooltips.FONT_SIZE, @@ -22,40 +30,29 @@ export function add_controls() { max: 16 }, defaultValue: 12 - }); - - app.ui.settings.addSetting({ + }, + { id: SettingIds.CONTROL_AFTER_GENERATE, name: SettingNames.CONTROL_AFTER_GENERATE, tooltip: Tooltips.CONTROL_AFTER_GENERATE, type: "boolean", defaultValue: true - }) - - app.ui.settings.addSetting({ + }, + { id: SettingIds.TOOLTIPS, name: SettingNames.TOOLTIPS, tooltip: Tooltips.TOOLTIPS, type: "boolean", defaultValue: true - }) + }, - app.ui.settings.addSetting({ - id: SettingIds.DEFAULT_APPLY_TO_SIMILAR, - name: SettingNames.DEFAULT_APPLY_TO_SIMILAR, - tooltip: Tooltips.DEFAULT_APPLY_TO_SIMILAR, - type: "boolean", - defaultValue: true - }) - - app.ui.settings.addSetting({ + { id: SettingIds.SHOW_IN_FOCUS_MODE, name: SettingNames.SHOW_IN_FOCUS_MODE, type: "boolean", defaultValue: false - }) - - app.ui.settings.addSetting({ + }, + { id: SettingIds.SCROLL_MOVES_SLIDERS, name: SettingNames.SCROLL_MOVES_SLIDERS, type: "combo", @@ -65,29 +62,16 @@ export function add_controls() { {value:"ctrl", text:Generic.CTRL}, ], defaultValue: "yes", - }) - - app.ui.settings.addSetting({ + }, + { id: SettingIds.SCROLL_REVERSED, name: SettingNames.SCROLL_REVERSED, tooltip: Tooltips.SCROLL_REVERSED, type: "boolean", defaultValue: false - }) + }, - app.ui.settings.addSetting({ - id: SettingIds.SHOW_SCROLLBARS, - name: SettingNames.SHOW_SCROLLBARS, - tooltip: Tooltips.SHOW_SCROLLBARS, - type: "combo", - options: [ {value:"no", text:Generic.OFF}, - {value:"thin", text:Generic.THIN}, - {value:"full", text:Generic.NORMAL}, - ], - defaultValue: "thin", - }) - - app.ui.settings.addSetting({ + { id: SettingIds.EDIT_SLIDERS, name: SettingNames.EDIT_SLIDERS, type: "combo", @@ -96,9 +80,15 @@ export function add_controls() { {value:"ctrl", text:"ctrl-click"}, ], defaultValue: "yes", - }) - - app.ui.settings.addSetting({ + }, + { + id: SettingIds.DEFAULT_APPLY_TO_SIMILAR, + name: SettingNames.DEFAULT_APPLY_TO_SIMILAR, + tooltip: Tooltips.DEFAULT_APPLY_TO_SIMILAR, + type: "boolean", + defaultValue: true + }, + { id: SettingIds.DEBUG_LEVEL, name: SettingNames.DEBUG_LEVEL, tooltip: Tooltips.DEBUG_LEVEL, @@ -108,18 +98,5 @@ export function add_controls() { {value:2, text:Generic.D2}, {value:3, text:Generic.D3} ], defaultValue: "1" - }) - - window.addEventListener('keypress', (e) => { - if (e.target.tagName=="CANVAS" || e.target.tagName=="BODY") { - const keysetting = app.ui.settings.getSettingValue(SettingIds.KEYBOARD_TOGGLE, "C") - if (keysetting==e.key) { - ControllerPanel.toggle() - e.preventDefault() - e.stopImmediatePropagation() - return false - } - } - }) - -} \ No newline at end of file + } +].reverse() \ No newline at end of file diff --git a/js/update_controller.js b/js/update_controller.js index 2e18fcd..3112de6 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -1,8 +1,10 @@ import { app } from "../../scripts/app.js" import { Timings } from "./constants.js" -import { Debug } from "./debug.js" +import { _Debug } from "./debug.js" import { GroupManager } from "./groups.js" +const Debug = new _Debug(()=>(new Date().toISOString())) + function message(wait_time) { if (wait_time==0) return "" if (wait_time==-2) return "Graph configuring" @@ -102,30 +104,30 @@ function node_changed(node) { export class OnChangeController { static gap_request_stack = 0 - static on_change() { + static on_change(details) { OnChangeController.gap_request_stack += 1 - setTimeout(OnChangeController._on_change, Timings.ON_CHANGE_GAP) + setTimeout(OnChangeController._on_change, Timings.ON_CHANGE_GAP, details) } - static _on_change() { + static _on_change(details) { OnChangeController.gap_request_stack -= 1 if (OnChangeController.gap_request_stack == 0) { if (GroupManager.check_for_changes()) { - UpdateController.make_request("on_change, change in groups") + UpdateController.make_request(`on_change (${details}), change in groups`) } else { const changed_nodes = app.graph._nodes.filter((node)=>(node_changed(node))) if (changed_nodes.length > 1) { - UpdateController.make_request("on_change, multiple nodes changed") + UpdateController.make_request(`on_change (${details}), ${changed_nodes.length} nodes changed`) } else if (changed_nodes.length == 1) { - UpdateController.single_node(changed_nodes[0], "on_change") + UpdateController.single_node(changed_nodes[0], `on_change (${details}), node ${changed_nodes[0]} changed`) } else if (app.canvas.read_only != app.canvas._controller_read_only) { - UpdateController.make_request(`on_change, read_only ${app.canvas.read_only}`) + UpdateController.make_request(`on_change (${details}), read_only changed to ${app.canvas.read_only}`) app.canvas._controller_read_only = app.canvas.read_only } else { - Debug.trivia("on_change, no changes", true) + Debug.trivia(`on_change (${details}), no changes`, true) } } } else { - Debug.trivia("on_change, too soon", true) + Debug.trivia(`on_change (${details}), too soon`, true) } } From 06c457b10adb0505947fd2c1e3e3847184ea57ee Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 25 Nov 2024 15:10:15 +1100 Subject: [PATCH 10/19] tweaks --- js/controller_panel.js | 12 ++++++++---- js/panel_entry.js | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/js/controller_panel.js b/js/controller_panel.js index 12bf3a2..771f5e6 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -153,8 +153,12 @@ export class ControllerPanel extends HTMLDivElement { Debug.trivia(`ControllerPanel.on_executing ${node_id}`) Object.values(ControllerPanel.instances).forEach((cp)=>{ Object.values(cp.node_blocks).forEach((nb)=>{ - nb.on_progress() - classSet(nb, 'active', nb.node.id==node_id) + try { + nb.on_progress() + classSet(nb, 'active', nb.node.id==node_id) + } catch (e) { + Debug.error(`on_executing: controller ${cp.index}, node ${node_id}`, e) + } }) }) } @@ -689,7 +693,7 @@ export class ControllerPanel extends HTMLDivElement { tab.addEventListener('mouseup', (e) => { if (this.mouse_down_on == tab && Math.abs(this.mouse_down_at_x - e.x) < 2 && Math.abs(this.mouse_down_at_y - e.y) < 2) { if (this.settings.collapsed) { - this.settings.collapsed = false; + this.settings.collapsed = false UpdateController.make_single_request('uncollapse', this) } else { if (this.settings.group_choice == nm) { @@ -781,7 +785,7 @@ export class ControllerPanel extends HTMLDivElement { e.preventDefault(); e.stopPropagation(); if (app.canvas.read_only) return - this.settings.collapsed = (!this.settings.collapsed) + this.settings.collapsed = !this.settings.collapsed UpdateController.make_single_request('collapse', this) }) classSet(this.minimise_button, 'hidden', this.settings.collapsed) diff --git a/js/panel_entry.js b/js/panel_entry.js index dd8b82d..45214d7 100644 --- a/js/panel_entry.js +++ b/js/panel_entry.js @@ -45,6 +45,8 @@ export class Entry extends HTMLDivElement { this.input_element = null this.properties = properties + target_widget.type = target_widget.type ?? target_widget.constructor.name + switch (target_widget.type) { case 'text': this.entry_label = create('span','entry_label text', this, {'innerText':widget_label, 'draggable':false} ) @@ -73,6 +75,7 @@ export class Entry extends HTMLDivElement { this.entry_value.innerText = this.input_element.value } break + //case 'RgthreeBetterButtonWidget': case 'button': this.input_element = create("button", 'input', this, {"innerText":widget_label, "doesntBlockRefresh":true}) break From 1695d0f0766a9ccdf4366e0fda81b18e827bfd43 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Nov 2024 11:13:56 +1100 Subject: [PATCH 11/19] 143 --- js/constants.js | 4 +++ js/context_menu.js | 18 +++++++---- js/nodeblock.js | 74 +++++++++++++++++++++++++++++++++++++++++++--- js/panel_entry.js | 1 + js/settings.js | 1 + 5 files changed, 89 insertions(+), 9 deletions(-) diff --git a/js/constants.js b/js/constants.js index 13148fc..547e856 100644 --- a/js/constants.js +++ b/js/constants.js @@ -41,6 +41,8 @@ export class Generic { static D1 = "Normal" static D2 = "Extra" static D3 = "Verbose" + static SHOW = "Show" + static HIDE = "Hide" } export class Tooltips { @@ -95,6 +97,8 @@ export class Texts { 4 : "Group bypassed.
Click to activate", 9 : "Some nodes muted or bypassed.
Click to activate" } + static REMOVE = "Remove from controllers" + static EDIT_WV = "Edit widget visibility" } export const BASE_PATH = "extensions/cg-controller" diff --git a/js/context_menu.js b/js/context_menu.js index 026c0ca..7b30cd6 100644 --- a/js/context_menu.js +++ b/js/context_menu.js @@ -1,19 +1,27 @@ -import { app, ComfyApp } from "../../scripts/app.js" +import { app } from "../../scripts/app.js" var context_menu +var sah -function close_context_menu() { +function autoclose() { + if (sah) return + close_context_menu() +} + +export function close_context_menu() { + if (context_menu) context_menu.root.remove() context_menu = null } -function _open_context_menu(e, title, values) { +function _open_context_menu(e, title, values, supress_autohide) { + sah = supress_autohide const options = { "title":title, "event":e, } context_menu = LiteGraph.ContextMenu(values, options, app.canvas.getCanvasWindow()) } -export function open_context_menu(e, title, values) { setTimeout(_open_context_menu, 10, e, title, values) } +export function open_context_menu(e, title, values, supress_autohide) { setTimeout(_open_context_menu, 10, e, title, values, supress_autohide) } -window.addEventListener('click',close_context_menu) +window.addEventListener('click',autoclose) diff --git a/js/nodeblock.js b/js/nodeblock.js index e650783..ccfd0b7 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -9,8 +9,10 @@ import { get_image_url, image_is_blob, ImageManager, is_image_upload_node, isIma import { UpdateController } from "./update_controller.js"; import { Debug } from "./debug.js"; import { Highlighter } from "./highlighter.js"; -import { open_context_menu } from "./context_menu.js"; -import { Timings } from "./constants.js"; +import { close_context_menu, open_context_menu } from "./context_menu.js"; +import { Generic, Texts, Timings } from "./constants.js"; +import { InclusionOptions } from "./constants.js"; +import { NodeInclusionManager } from "./node_inclusion.js"; function is_single_image(data) { return (data && data.items && data.items.length==1 && data.items[0].type.includes("image")) } @@ -89,7 +91,7 @@ export class NodeBlock extends HTMLSpanElement { draghandle.addEventListener('dragstart', (e) => { this.drag_me(e) } ) //draghandle.addEventListener('mousedown', (e)=>{ }) draghandle.addEventListener('mouseup', (e)=>{ - if (!NodeBlock.dragged && e.button == 0) this.toggle_minimise() + if (!NodeBlock.dragged && e.button == 0 && !e.ctrlKey) this.toggle_minimise() }) } @@ -196,10 +198,73 @@ export class NodeBlock extends HTMLSpanElement { } else { this.progress.remove() } } + set_widget_visibility(display_name, v) { + const wid = `${this.node.id}:${display_name}` + if (v) { + const index = this.parent_controller.settings.hidden_widgets.findIndex((e)=>(e==wid)) + if (index>=0) this.parent_controller.settings.hidden_widgets.splice(index, 1) + } else { + this.parent_controller.settings.hidden_widgets.push(wid) + } + } + + apply_widget_visibility() { + Array.from(this.main.children).filter((child)=>(child.display_name)).forEach((child)=>{ + const wid = `${this.node.id}:${child.display_name}` + if (this.parent_controller.settings.hidden_widgets.find((e)=>(e==wid))) child.classList.add("hidden") + }) + } + + context_menu(e) { + const ewv_submenu = (value, options, e, menu, node) => { + const choices = [] + const re = /(.*) '(.*)'/ + Array.from(this.main.children).forEach((child)=>{ + if (child.display_name && (child.display_name!="image_viewer" || !this.image_panel.classList.contains('nodeblock_image_empty'))) { + choices.push(`${child.classList.contains('hidden') ? Generic.SHOW : Generic.HIDE} '${child.display_name}'`) + } + }) + const submenu = new LiteGraph.ContextMenu( + choices, + { event: e, callback: (v) => { + const match = v.match(re) + //Debug.extended(`Toggle ${display_name}`) + this.set_widget_visibility(match[2], (match[1]==Generic.SHOW)) + UpdateController.make_request('wve', null, null, this.parent_controller) + close_context_menu() + }, + parentMenu: menu, node:node} + ) + } + open_context_menu(e, null, [ + { + "title" : Texts.REMOVE, + "callback" : ()=>{ + this.node.properties["controller"] = InclusionOptions.EXCLUDE + NodeInclusionManager.node_change_callback?.('context_menu_remove', Timings.GENERIC_SHORT_DELAY); + } + }, + { + "title" : Texts.EDIT_WV, + has_submenu: true, + callback: ewv_submenu, + } + ], true) + } + build_nodeblock() { const new_main = create("span", 'nb_main') this.title_bar = create("span", 'nodeblock_titlebar', new_main) + + this.title_bar.addEventListener('click', (e)=>{ + if (e.ctrlKey) { + this.context_menu(e) + e.stopImmediatePropagation() + e.preventDefault() + } + }) + this.title_bar_left = create("span", 'nodeblock_titlebar_left', this.title_bar) this.draghandle = create("span", 'nodeblock_draghandle', this.title_bar, { }) @@ -239,7 +304,7 @@ export class NodeBlock extends HTMLSpanElement { classSet(this, 'minimised', this.minimised) if (this.image_panel) this.image_panel.remove() - this.image_panel = create("div", "nodeblock_image_panel nodeblock_image_empty", new_main) + this.image_panel = create("div", "nodeblock_image_panel nodeblock_image_empty", new_main, {"display_name":"image_viewer"}) this.widget_count = 0 this.entry_controlling_image = null @@ -323,6 +388,7 @@ export class NodeBlock extends HTMLSpanElement { this._remove_entries() this.replaceChild(new_main, this.main) this.main = new_main + this.apply_widget_visibility() if (this.node.imgs && this.node.imgs.length>0) { const urls = [] diff --git a/js/panel_entry.js b/js/panel_entry.js index 45214d7..2421c92 100644 --- a/js/panel_entry.js +++ b/js/panel_entry.js @@ -37,6 +37,7 @@ export class Entry extends HTMLDivElement { if (target_widget.name=='control_after_generate' && !app.ui.settings.getSettingValue(SettingIds.CONTROL_AFTER_GENERATE, false)) return const widget_label = target_widget.label ?? target_widget.name + this.display_name = widget_label this.classList.add('entry') this.parent_controller = parent_controller diff --git a/js/settings.js b/js/settings.js index fbbb3b1..c9c9a5d 100644 --- a/js/settings.js +++ b/js/settings.js @@ -13,6 +13,7 @@ const DEFAULTS = { "collapsed" : false, "fullheight" : false, "fullwidth" : false, + "hidden_widgets" : [], } const KEYS = Object.keys(DEFAULTS) From a355830e07d53a3d6cd67fb5f0eec33d3e2925aa Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Nov 2024 13:26:47 +1100 Subject: [PATCH 12/19] text to constants --- js/constants.js | 1 + js/nodeblock.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/js/constants.js b/js/constants.js index 547e856..72d9026 100644 --- a/js/constants.js +++ b/js/constants.js @@ -99,6 +99,7 @@ export class Texts { } static REMOVE = "Remove from controllers" static EDIT_WV = "Edit widget visibility" + static IMAGE_WIDGET_NAME = "image viewer" } export const BASE_PATH = "extensions/cg-controller" diff --git a/js/nodeblock.js b/js/nodeblock.js index ccfd0b7..570746d 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -220,7 +220,7 @@ export class NodeBlock extends HTMLSpanElement { const choices = [] const re = /(.*) '(.*)'/ Array.from(this.main.children).forEach((child)=>{ - if (child.display_name && (child.display_name!="image_viewer" || !this.image_panel.classList.contains('nodeblock_image_empty'))) { + if (child.display_name && (child.display_name!=Texts.IMAGE_WIDGET_NAME || !this.image_panel.classList.contains('nodeblock_image_empty'))) { choices.push(`${child.classList.contains('hidden') ? Generic.SHOW : Generic.HIDE} '${child.display_name}'`) } }) @@ -304,7 +304,7 @@ export class NodeBlock extends HTMLSpanElement { classSet(this, 'minimised', this.minimised) if (this.image_panel) this.image_panel.remove() - this.image_panel = create("div", "nodeblock_image_panel nodeblock_image_empty", new_main, {"display_name":"image_viewer"}) + this.image_panel = create("div", "nodeblock_image_panel nodeblock_image_empty", new_main, {"display_name":Texts.IMAGE_WIDGET_NAME}) this.widget_count = 0 this.entry_controlling_image = null From 918bbc8d4639ea3512d4dbf980b3903e14302480 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Nov 2024 13:26:53 +1100 Subject: [PATCH 13/19] 1.5 and docs --- README.md | 25 +++++++++++++++++-------- __init__.py | 2 +- images/batch.png | Bin 0 -> 7523 bytes pyproject.toml | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 images/batch.png diff --git a/README.md b/README.md index 95d4008..1ab0ea6 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,21 @@ If you unpin the image, you will find a handle at the bottom right of the image ## Other things to explore +### Batches + +![image](images/batch.png) + +If you have a batch of images, you can click through them on the preview using the control in the top right corner. + +### Mask Editor + +ctrl-click on an image in the controller to bring up a context menu, from which you can launch the mask editor. + +### Selectively hide widgets + +ctrl-click on the title of a nodeblock and you can hide/show specific widgets. Note that hide/show is specific to the controller, +so you can have the same node showing different widgets on different controllers if you want! + ### Hover and zoom If the magnifying glass in the top control is active ![image](images/top.png) then when you move you mouse over a node in @@ -150,18 +165,12 @@ Controllers will snap to each other and move around together. To break them apar ### Custom widgets -At present only standard Comfy widgets are supported. We'll be working to bring some of the more popular custom node widgets to the controller -in future releases, probably starting with rgthree's Power Lora Loader. +At present only standard Comfy widgets and some custom ones are supported. We'll be working to bring more of the more popular custom node widgets to the controller +in future releases. # The road ahead... -Future features will depend on community feedback, but the aim for the 1.5 release is to include - -- Support for some popular custom node widgets -- Support for batches of images -- Open the mask editor directly from the controller on an image load node - For more details of what's under consideration, take a look at the [issues list](https://github.com/chrisgoringe/cg-controller/issues), and feel free to add your ideas there, or jump into the discussion [here](https://github.com/chrisgoringe/cg-controller/discussions). diff --git a/__init__.py b/__init__.py index bb85e0c..ffa4145 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -VERSION = "1.4.1" +VERSION = "1.5" WEB_DIRECTORY = "./js" NODE_CLASS_MAPPINGS= {} diff --git a/images/batch.png b/images/batch.png new file mode 100644 index 0000000000000000000000000000000000000000..db1775bfe25a4c8c8c312a0d83d8627172af0f50 GIT binary patch literal 7523 zcmV-p9h~BcP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D9Qa8@K~#8N?OlD4 zomF{%_B$Ue3E7yKglrNLl0paplQm#$B=|v!=oG19t(~!zD4mg+j*e6NhaJa%{Kx5Z zI;|bQX6%%4bR3IAf(lZiCf8S(2CpFkj0i+0VPT&+qv-AMd?y_HOMTsky)1 z_nz~7p68tBob#Ud-n&W5<(FSxr6w*aELyZk%2s)8;-W-LUteEy<^O@>*s)_)Q#4n8 z6V2pb%xEV6B1ZF`e<9& z)7s5Mzx}6=(4es*{+@dO_BNu&Hq)V}j8zBC(|!{*XeR$6Ml<;rF`CK0kkNGq&0VSY z=dyzt_%m02fjYaCo(`JHzfduk4|5;?G%Z;$N~d@I3%z0N9ABcNyY^Fc!Mo|okAIuy z%r$m{XeR%H#3Jf`*KO2I`{~eEx6zSj9yfk1D$o2C?f>fc`AJ|IEqT|cjoTQS$-f|R z2hHK#e*EQ!XlT?}=fmhf((5k|QOlxrJYgC~^Ev;5gg(DdCy|PSb>9ytd(Lkr|ANHZ zXvW6((2P3o>(0LF-87eeO-Fu{CsZ&#KtoUeJ5`KyG?V`af;k_4j4r30^!#_eWSptd ze21@z8Z?uC5u=&>|7VyO8KcU`kWs%Mn)m#R0L^=T6E$eL;)*MpEB_A|Jv}|mN4<*~ z&27Jl8Z?uC5u=&>ix@4}zGFjGZ~F1pk6MdX16+!-DnCkCjnP6?cyh87>{(h&6s794 zm#SB_NL0n6a=z+HyD3yzssgJ<4y?^D+qSl%trj|SdWcRO`yEwACm;|#i9zDvh@fY2 z$z>rxHWgZ0Te+P!uBkz#;%%@K`Q`DWNp%(+az)y(hDxDw_}JLDL0JjP@A)l040zS6 zg?Zoyp6H;i9K1>ik^LlGRf0?|=8C;R`lAZ^BdS@=DUHFQb@7R?h2yWqAJL4LG7^q4 z0665qo#2EIpBbjfaeOSo1Ig^*kBL@Jo>B(p357DfOh-!NwYG5Lr4GKB2yJdmk4w+5 z@KA~up`|WLTIO|?`$B0H=iT-(;p8%!n~8)_g~mV%%PV|AKiDvgB>_83HD#qHfT-|` zEIR)2wo{t`X3F3M2&48=iC}DWl*Z1D$SRLcb;Y|b;Jb`)0v|!yr4}r68XPWJ7uv9| zR4P(>jLwHP>h3iy)4H_~cz|RhWi$ZU$TF)E2UVMvP_3sTJn189(B;mc^B_eSt@0q9 z;4l~%2Nd1Y`V3|{V#p|_xnE9UCM!gGoyCnsraWQ@kg$HWGgsmSuEN~ifke;irc zS{ImV1a^%!YHw?otzX8Z%G-NvLCa#i30%{)6}dL1PF4Ii4%t89My|5f1uDz~fDIg| zOsZ02uwu3|B-IG*QqU_p4}fFUNmXPZOMtjCska|&;$S2B6XWAltxSr~V8`3F+^gGU zT_qNZF!|)w0V-Z?ZLQSV*(uxnq^x)(DAh?qVwCqN!mQ-=_d(qD)uZ|}zu_Kah@>ln z$J}JVP^myWqGu`5DSm5GB<2AN8akD$lNgt1n9en2M?0+YR!kbMOTx_#{VMKO@SgTJt)>{~o#{8f zd4YEBe1HxeI%M1kG2@rMX`S{HB>ky{2bK1^E()E=5c%iVKpT4bkGJcsFJ7>awtw_v z^hc}KaN^sUV|tCi)vUzo!%D5$JrE3%o+L1Zw^wY{ePN5A}RRBx7WBuH0n)}C11ey#V~7JW`h)^DK{O+vXH<}CyMSa^|Vzpc;w1WZ{H$^ zs2qvyWQ9i0o~5&AM|jIMUJ<6Q)5g7{#AT~gF7TsobS>(Orqy^rS%|*-HjhJ98*Upl za1YUzDx)+!Jj~Z<&G+yem;|@o_Fk#OGO?aEvTffnDYG0`+CheqBDFz5$90yAhR~=& zwHodfM)OY!k(!;OKxPmTUfAzHeCBhu;v?X{a&kZY;=x_CcVLhzZ8NBgMrgFsMsrrY zg|^(Xg|3{3H(Eql!9h$;;Iu!=+x}Q+B#4uMzJIl;Q4;WOLyoi;C` z1Hg9l)#vH)U+$*<)P2SV6d+-PCAm0)4qntFN4q0ecye8 zj*AlG%58MlO}&yxNq+Sp{nytH(5mfQY3ad7=o=69o8o-B;m!}xHJ4hF(G_+lZW%If zjNr7>>relP9(Z<))@48V?V=aq| ze}-TD3ElrrNRbGE{i^kw4&q?R9{(8rB~?}5A31E@4J&Wy~&o2;q3E2ps)Vx zqx3q6o<92E`v4t{pMvxw)E1f)kMcnot6$l^^Jj-h*mDA?m6q| z_U*URx;d)ay?hm~{nK=M5dPGswgE%fDi7bWf9w}SbltX_X*ss_k$!scTldk^ujhZF z)75)3ZCgJ?k3KPwodbe*d9hy9UCHIBpL}*=AsRwc3~qp{^_t0T3}BkF$W6)ZYbTS^ z>1$C%NH}mO$M(^Sl^bZ&+Wh8-#NWH~3~l_#7FuAuuB&gPO_ja$YhL~0f)8V@e0D(D z2sGX|=oQ}OS1r%~rUp}K=#`h~c+V?LK-$%;_UZDPAVnJ`XUv?%S8_XR03|)f{VE-C4crzo!)0FP9g7s#gkU{U zb=na8Md%$dq?Q#(LuK4ezG&X z3x^MGBG5q6Cg1;e@8^ZSLLafJ)l+o*#2lKNCK%iQ*S|*JdGa`|+;A;jDopl~bR(VU zM~@9q&w>S%?o(a8Tj?9@pP3 zigQ(`gM@Da)leeVVt+kp3rI^ zeb0}Ql^+Kq9TOiTNqQf0OQgqOe?8>*R^SY_d!i-o0~Fk@&CwnEE;K+xevL7&?8LdM>-ns0i^e=UUpp z=Y#%%O{zNN5V4!@wWtC)RAaHT2S*b=WNLx}*MOsm7 zS%XZ4ZM)ag4QqLoKG@Ggm(zUnWQ0iSJmvj;?FpOOHNltAG48 zIy}C3c7}ncn}Q8)zQi9^ng#pBNk0_wN~EHn@)-f9kh%?qomh*|VGW?6&Kb z4q83$IQ{qNV!F|@KVu(BehVZ753=$KmjkrXwayHby z_&IxrF9;YT2kEE(@jadZOX!-u^>o$BUYf@n>*4A9z#qoXtW=mUSbm6qBQA(B5j zV&CB5D#;4Z|#E%8PFWM zwE#iHt)gB`qIOIJQ&E(wZZaTDrUTK6w`~K>gDi4`n1O+c`-ilJKL44|Q3pSti*7Ig zCg|+37wA_{K2HaZ4D#`=jV@iXny%Wkfv#FKJ3LzXjx&00m_~>Bv5Nh=FUeo#lcDN@ z*e5TYN+wAPR!jRVNXcr2PW|=;+Q08*dgVCI_%o?z#TB$>&DFFtx%szoa*>m<@d^6W zJ)e?Wm8^Kk_>m9W{{S}%lb4GA~ZwGxOvw37OCq=P5P=Bf-C z^BYM%wABMQ*;j}DLU1hDV!fm?3iAxt0a^SsG#09f_9M5Y4*RU;+O zK*3N|_|^Ek9($A~_>}H;(}f>Db?}lN722(Z4R*L-n;QknW1G@virNl&j9B&fL~rK< zKLl5J{2~)WKZ%`IL-il^0O8sy^LXm5iW+NH$_AsT;J)kv(1)zkMVkWP|JZA<(l_q? z7QOh=KAi;9hBYDss8vCoKBkaZ{erUSYw5d(zB1fzU?d1WiYPEmZ)|Lgo_Y2;`tp~* zOot8~6p@ZJCIzeLAWjQ>;KLtkdXmLU2-Z8}05xgJttV4xEfe!{4%;3Vs24+(EzIgU zSX)f7Nx2VSw~Asc3gw9ij@?vVxGnA{?xCkoo~DyWUXy?HK>E(d9F3#klVz~+KfJ(7 z^Kbab4}9{EzfBAt03NnuBN?c8Pr0tY8M1Len|v>ae|!XsQob5yNWX!EKLqL9@W1#P z7tF)#5kjkIycDUPz`9Y>o17t&$Lfyr&s@l&MbWkh3=sdSUT!Kt`wmP1|4^qi0vB)@ zpRXsRu5D=pwSCZjMs6VxNc1;cMTk%KpUPsMAk(VHFdcBOqfus$I{TLJL(3R8C8W&S z%ZNy#EQr7eE^AOvk~_#WYJ4+TA>^ST7q-bTU~TPf)ZVGz0fcUgOw`l}%6zdy{AjXu z#p{4+Ut~O3M>%fq{nXb8)zXV$;%Jpg6|58F-Zn1Sa*3FVtxN?zDwqiuI?K3jtw?9f zTtXkE=>|9rVNnKvZ-Z1H1qqWHLD8bzgZ2rpTxzuzY>q+1cj7v`X4toFna8?%af}!b zzA!HC!dul+<$B341CF%_$_bD!t>jnD%4Hhu%54Px4XPKX7oaSSb2s>Qv@C29vBWAdQ`fxw`syuv2 zGptDS6AxalSr1WPU`?ro+d+p9cbxc1`~{I6Djhnhxq{d@eV}0}{K;v4u%m@HtWHvt z4q^eK<)*tQrLe6;MSPpwJ$QRj&oAGV%%DM7@(Te5V3w813h;I_b!F-h zC#dl`1&!VYuuL8|3+xhT5@RX@U@XmGm9^5$*|TH<;1uNT%W2*}>$jLu51;=lOA;?% zu0ShkP+m<~rAT!A>)!!r3kfz7%9v;&2E@cy!?`lVm2d6k1DX=%xYU0kV8DH%66{c^ zqYJR8%zd6duIq1B)`xq>0d(z_jxYV1w#YSp1;MCANEg z_6L~Bx^cJEvS#X;Z7IxN{O~0o=(t~DC5czX;?s0PkmPGq26lor9BD@)TU!Drvn@~| zI3Prqdr4Iwl1HsH00RbIxMHO8u*<`Gi7=2@y|XTvO_$D_%iDjaw3DtR z)G!vZy-S1q%Bvk;o&*oOL|A=dhh&){Pz)owu7x%h+LwDfDomqI<)eM5v7US)$vkPW zaoEZS;0c8~2yWohsu9i!!)T=jRc0X@mm6?9MNNq0uY6o5L7apom_qD;A8nc8;WF?p zTsxomFX7cc>(Wc~Yf>(RjoBd#PYO=saw2D1ZUsmJ*W;)}gf^#w^;6zUV1NqmD@Y6q z84uHSOgTmcU>ZvcYKB(}-2Het1)!$GlfZ~L7>QI^xpVaqBFG_~=Tb~mBe$-lr$n8` zI=lGr$ESaswgs%?AK)Od{$)uKVHNTZ-forif$%*xHsoqH5hOV!QJPwsKWj#Oh#zKv zCr=i3Hj%fE1&Gh84I-|5IK^co;PupJ7J6}}8L34ibV?XKC42(KR(+*~fs{R7DIH|< z-(VB@3UNE-p()wEg~c0gl6XG=qHO>JuiyXUN36og>ki;v6=^*u6zU=sm`@1wSCX!M zRv9KB;*^hkiwDIipex@Hs@7&1()+8Ysva~*BMi0L;FS1~hE%5a_o5!;7-TIWFV4rh zKw@n0#AP@zBsvm3&*yeo~P-qM}m`+$a@rpVTI%(Js|7MdE2}U9H zM8^8^w1MmQ{_1vULAxSB2&G8G3MMjIti)1E#F+L$l~Wxcsi(Tawo`7ZZh$HVO8pyE zN3RBKAdhCy#RXGqVibS0IXWsy2$LL6cTumq=d;*!HB47hO7NgJp zffEEyb5z2??MDur?H$%UPE;j+OW?;yAp~CalH1B%qHzttk;xIg{t6(I;4%{srX)os zRDJ>(umaCp4M|Ap1|S0kUC~!tyrYJ{*v0Bsx5^I)`haTKZ>AYk08>*iAwttILtX8h z3`w>oSrV;T4eBxhxGws3;j7zRk#onizq;*?1DxivC{k#|^<@UA*s_tiP*5(?*2M_F zhg&OQi%XFH?f?0MDM1~dxOd}k^d~2hL9_&9>PDn-yI4xdYdV!83Z=#?SpV!wzmtgm zqS@#ZN!Lq~&WVN)C1g5D_>%KT;|SZnH{`HB3qO?=^?=&sXz+rPcrp{ID?`*#Ene=2 z>pBodWGK=RIzSQ7j4{aY%GbCnqkO-&tsJ~kB%)Lctw@68$G{4~S009}@XoF-YVYh+ z5eFr)!Usy2!5)3)sytZ;OZ~~%^RKr#9cc37C`fRi0?R7GlUhWP4yX}WsCAq;>FsHu ziebPQ;grjljE!*hH4R_vGTI=#;^24-%9rGiO~`FLg|)(d_2dtuw25@s13xsy90Y7t zfsy#g7=Iy*a)NgqeO$DOhm*UQLRk$f{%;7lKS7ah9JcZ~cn^#ruG}9S*B#CJaLwGJ zFeeLbfsFE!wpc+2f;CvB=-<<*sP*9!Cx^y`gknWu%f}ZaH5!XZ`kuxQPrkD`SN#U7 zs4Ng&RtYgm(ISVnHcoykzEHv!`1%Jt# zIO2*&pTgTVcmRQ&sY8}965E1TMidsB2yG}*OV(_`{V$$HpgT3#pXl+HKR!Vt=SJkY z9Z1{GVz29#wPnR(%P2+-4*y^br73N%rL9epj!k@m+Z-Pom%m|Xg*kNH4fL%q^44y0^#CUPJBm4r;t@ne3f^b zD*on<4{Z7d6aR=hTd9e}DED=r80OA*LvuYXB&U53G4;ZA1R(XiVw)0TrDVS#`r_&dN7#!gGWPFiprVqh2xF z^s4EMjF$7Xo^s9)3yh00{Zc=r0{HYT9(9Y@nu^fj1f)y|=wo2<&#;{v8m4pj=hpEE z7gqB=;7Hn zhG>Ehd*}%I5(e1)G3ix>Q#m))7f9QDaU$^@!M09*mgoCC{NoL!7Gzt0O;;O26%>-6 zPCEHq!RUk?QI}Z`83F*M*0p8j7Lj|81M62w>xnbhEk!{%RexMv_3&D2kxxRe^LEdN zymN1ym4A0P8)cUHxMcp^50}9wN~s$y>TaVeRVEq|$v?8Y;FCeTk%T(r@CS7+)dE%B zA=H<*rG-P}-V{T^mNSnECKFjY4K<4IAFJBgC7f>Z`Uc~Mf-QP?Qd z7aDRQp6PMOtKfgvkX2=u1M1kWrB8g}7m2LX$H0*azrc;SQhjXi75qB@JmIkFxj_gS ztR!;3jkycu`KO5L1A-!$DRCP)4akA7*cqj#<1Omup3E153j*#?(hEs#(TK%)>rtVm zX0gYK2}`}AZVrvfKEcDEY2iI_Prd^>a-ZgU8`p8+9?S|^agoLs$B)paLElHI4 t+=oZ5ZR>cjj_e8%>6n#aYK;^`{{v&s9=%G{Jv9IT002ovPDHLkV1juctabnZ literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index cedc405..0d7839f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "cg-controller" description = "A control panel that shows all widgets from selected nodes. A whole new way to interact with Comfy!" -version = "1.4.1" +version = "1.5" license = {file = "LICENSE"} [project.urls] From 437a93eb3876a048f5e6e067bec032d3a7bf9f40 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Nov 2024 13:55:23 +1100 Subject: [PATCH 14/19] 372 --- js/constants.js | 2 ++ js/controller_panel.js | 1 + js/update_controller.js | 9 ++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/js/constants.js b/js/constants.js index 72d9026..ae830b6 100644 --- a/js/constants.js +++ b/js/constants.js @@ -66,6 +66,8 @@ export class InclusionOptions { export class Timings { // ms static GENERIC_SHORT_DELAY = 20 + static GENERIC_LONGER_DELAY = 1000 + static PERIODIC_CHECK = 1000 static DRAG_PAUSE_OVER_BACKGROUND = 500 static SLIDER_ACTIVE_DELAY = 300 static UPDATE_EXCEPTION_WAITTIME = 10000 diff --git a/js/controller_panel.js b/js/controller_panel.js index 771f5e6..29b4c29 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -125,6 +125,7 @@ export class ControllerPanel extends HTMLDivElement { if (changes.removed) { this.settings.groups = this.settings.groups.filter((g)=>g!=oldname) } + this.settings.groups = Array.from(new Set(this.settings.groups)) UpdateController.make_request('group change', Timings.GROUP_CHANGE_DELAY, false, this) } } diff --git a/js/update_controller.js b/js/update_controller.js index 3112de6..6cae0a6 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -103,6 +103,12 @@ function node_changed(node) { } export class OnChangeController { + constructor() { + setTimeout(OnChangeController.start, Timings.GENERIC_LONGER_DELAY) + } + static start() { + setInterval(OnChangeController.on_change, Timings.PERIODIC_CHECK, "tick") + } static gap_request_stack = 0 static on_change(details) { OnChangeController.gap_request_stack += 1 @@ -130,5 +136,6 @@ export class OnChangeController { Debug.trivia(`on_change (${details}), too soon`, true) } } +} -} \ No newline at end of file +const occ = new OnChangeController() \ No newline at end of file From 0e9db163bc86e111df53c7672e89d52ed14a7230 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Nov 2024 14:32:15 +1100 Subject: [PATCH 15/19] 375 --- js/constants.js | 1 + js/controller.js | 4 ++-- js/settings.js | 4 ++-- js/update_controller.js | 2 ++ js/utilities.js | 26 ++++++++++++++++++++++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/js/constants.js b/js/constants.js index ae830b6..ce965cc 100644 --- a/js/constants.js +++ b/js/constants.js @@ -67,6 +67,7 @@ export class InclusionOptions { export class Timings { // ms static GENERIC_SHORT_DELAY = 20 static GENERIC_LONGER_DELAY = 1000 + static GENERIC_MUCH_LONGER_DELAY = 5000 static PERIODIC_CHECK = 1000 static DRAG_PAUSE_OVER_BACKGROUND = 500 static SLIDER_ACTIVE_DELAY = 300 diff --git a/js/controller.js b/js/controller.js index 031076a..078a158 100644 --- a/js/controller.js +++ b/js/controller.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js" import { api } from "../../scripts/api.js" import { ControllerPanel } from "./controller_panel.js" -import { create, mouse_change } from "./utilities.js" +import { create, mouse_change, send_graph_changed } from "./utilities.js" import { OPTIONS } from "./options.js" import { add_control_panel_options, NodeInclusionManager, } from "./node_inclusion.js" import { OnChangeController, UpdateController } from "./update_controller.js" @@ -100,6 +100,7 @@ app.registerExtension({ try { ImageManager.init() ControllerPanel.new_workflow() + send_graph_changed(true) } catch (e) { console.error(e) } @@ -130,7 +131,6 @@ app.registerExtension({ } } catch (e) { Debug.error("ADDING onAfterChange", e) - } const draw = app.canvas.onDrawForeground; diff --git a/js/settings.js b/js/settings.js index c9c9a5d..f598aac 100644 --- a/js/settings.js +++ b/js/settings.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js" import { Debug } from "./debug.js" import { Texts } from "./constants.js" -import { defineProperty } from "./utilities.js" +import { defineProperty, send_graph_changed } from "./utilities.js" const DEFAULTS = { "node_order" : [], @@ -37,7 +37,7 @@ class _Settings { KEYS.forEach((k) => { defineProperty(this, k, { get : () => { return this.settings[k] }, - set : (v) => { this.settings[k] = v /*; console.log(`${this.index} ${k} <= ${v}`)*/} + set : (v) => { this.settings[k] = v; send_graph_changed(); } }) }) } diff --git a/js/update_controller.js b/js/update_controller.js index 6cae0a6..48f309f 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -2,6 +2,7 @@ import { app } from "../../scripts/app.js" import { Timings } from "./constants.js" import { _Debug } from "./debug.js" import { GroupManager } from "./groups.js" +import { send_graph_changed } from "./utilities.js" const Debug = new _Debug(()=>(new Date().toISOString())) @@ -60,6 +61,7 @@ export class UpdateController { if (wait_time == 0) { Debug.extended(`Update ${cont_name} request '${label}' sent`) UpdateController.callback(controller) + send_graph_changed() return } else { var reason_not_to_try_again = null diff --git a/js/utilities.js b/js/utilities.js index aa0dbd3..7634f7e 100644 --- a/js/utilities.js +++ b/js/utilities.js @@ -1,10 +1,36 @@ import { app } from "../../scripts/app.js" +import { api } from "../../scripts/api.js" import { SettingIds, Timings } from "./constants.js"; import { getSettingValue } from "./settings.js"; +import { Debug } from "./debug.js"; export var mouse_is_down export function mouse_change(v) { mouse_is_down = v } +var started = false +var sgc_stack = 0 + +function _send_graph_changed() { + sgc_stack -= 1 + if (sgc_stack == 0) { + Debug.extended("Sending graphChanged") + api.dispatchEvent( + new CustomEvent('graphChanged', { }) + ) + } else { + Debug.trivia("Not sending graphChanged yet") + } +} + +export function send_graph_changed(turn_on) { + started = started || turn_on + if (started) { + sgc_stack += 1 + setTimeout(_send_graph_changed, Timings.GENERIC_LONGER_DELAY) + } +} + + export function create( tag, clss, parent, properties ) { const nd = document.createElement(tag); if (clss) clss.split(" ").forEach((s) => nd.classList.add(s)) From 9fa0f901c0770d087264965b960e35bd6d199879 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 27 Nov 2024 09:43:27 +1100 Subject: [PATCH 16/19] 392 --- js/update_controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/update_controller.js b/js/update_controller.js index 48f309f..4b65d67 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -91,7 +91,7 @@ function hash_node(node) { /* hash all the things we want to check for changes. */ - var hash = `${node.bgcolor} ${node.title} ` + var hash = `${node.bgcolor} ${node.title} ${node.mode} ` node.inputs.forEach((i)=>{hash += `${i.name} `}) node.outputs.forEach((o)=>{hash += `${o.name} `}) return hash @@ -126,7 +126,7 @@ export class OnChangeController { if (changed_nodes.length > 1) { UpdateController.make_request(`on_change (${details}), ${changed_nodes.length} nodes changed`) } else if (changed_nodes.length == 1) { - UpdateController.single_node(changed_nodes[0], `on_change (${details}), node ${changed_nodes[0]} changed`) + UpdateController.single_node(changed_nodes[0].id, `on_change (${details}), node ${changed_nodes[0].id} changed`) } else if (app.canvas.read_only != app.canvas._controller_read_only) { UpdateController.make_request(`on_change (${details}), read_only changed to ${app.canvas.read_only}`) app.canvas._controller_read_only = app.canvas.read_only From 66da5a2b6854bcb67e836a66babf207ef37682a6 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 27 Nov 2024 10:37:15 +1100 Subject: [PATCH 17/19] 394 --- js/groups.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/js/groups.js b/js/groups.js index 9308871..bb13392 100644 --- a/js/groups.js +++ b/js/groups.js @@ -8,19 +8,24 @@ function recompute_safely(group) { const _g = [...group._nodes] const _c = new Set(group._children) group.recomputeInsideNodes() - const nodes = [...group._nodes] + group._controller_nodes = [...group._nodes] + group._controller_children = new Set(group._children) group._nodes = _g group._children = _c - return nodes + return group._controller_nodes +} + +function recompute_all_safely() { + app.graph._groups.forEach((g)=>{recompute_safely(g)}) } export function family_names(group_name) { + recompute_all_safely() /* Given the name of a group, return a list of the names of all parents and children of all groups with this name */ const names = new Set([group_name,]) app.graph._groups.filter((g)=>(g.title==group_name)).forEach((g)=>{ - g.recomputeInsideNodes() - Array.from(g._children).filter((c)=>(c instanceof LGraphGroup)).forEach((c)=>{names.add(c.title)}) - app.graph._groups.filter((p)=>(p._children.has(g))).forEach((p)=>{names.add(p.title)}) + Array.from(g._controller_children).filter((c)=>(c instanceof LGraphGroup)).forEach((c)=>{names.add(c.title)}) + app.graph._groups.filter((p)=>(p._controller_children.has(g))).forEach((p)=>{names.add(p.title)}) }) return names } @@ -102,8 +107,7 @@ export class GroupManager { const modes = {0:0,2:0,4:0} app.graph._groups.forEach((group) => { if (group.title == group_name) { - group.recomputeInsideNodes() - group._nodes.forEach((node) => { + recompute_safely(group).forEach((node) => { modes[node.mode] += 1 }) } @@ -118,8 +122,7 @@ export class GroupManager { const value = mode_change(current_mode,e) app.graph._groups.forEach((group) => { if (group.title == group_name) { - group.recomputeInsideNodes() - group._nodes.forEach((node) => { + recompute_safely(group).forEach((node) => { node.mode = value }) } From ea55eeb2e6e9651640a8840e01e8054580deae6f Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 27 Nov 2024 12:38:40 +1100 Subject: [PATCH 18/19] context menu closing --- js/context_menu.js | 14 ++++++-------- js/nodeblock.js | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/js/context_menu.js b/js/context_menu.js index 7b30cd6..389dd19 100644 --- a/js/context_menu.js +++ b/js/context_menu.js @@ -1,27 +1,25 @@ import { app } from "../../scripts/app.js" var context_menu -var sah -function autoclose() { - if (sah) return +function autoclose(e) { + if (context_menu?.root?.contains(e.target)) return close_context_menu() } export function close_context_menu() { - - if (context_menu) context_menu.root.remove() + context_menu?.close() context_menu = null } -function _open_context_menu(e, title, values, supress_autohide) { - sah = supress_autohide +function _open_context_menu(e, title, values) { + close_context_menu() const options = { "title":title, "event":e, } context_menu = LiteGraph.ContextMenu(values, options, app.canvas.getCanvasWindow()) } -export function open_context_menu(e, title, values, supress_autohide) { setTimeout(_open_context_menu, 10, e, title, values, supress_autohide) } +export function open_context_menu(e, title, values) { setTimeout(_open_context_menu, 10, e, title, values) } window.addEventListener('click',autoclose) diff --git a/js/nodeblock.js b/js/nodeblock.js index 570746d..f6ca514 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -249,7 +249,7 @@ export class NodeBlock extends HTMLSpanElement { has_submenu: true, callback: ewv_submenu, } - ], true) + ]) } build_nodeblock() { From e1c07a020c51329b2b37347be26f0362a52c1134 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 28 Nov 2024 08:36:19 +1100 Subject: [PATCH 19/19] 1.5 --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1ab0ea6..2be86cd 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,12 @@ You can also move it around by dragging the header. Controllers will snap to each other and move around together. To break them apart, move the controller on the right or bottom. +## Supporting Custom Nodes + +Custom nodes which do not add specialised widgets will generally work. Some custom nodes with custom widgets are also supported: + +- [trung0246](https://github.com/Trung0246/ComfyUI-0246) switch + ## Known Limitations ### Custom widgets