From 897461340516c81b97e89cd21e0842be8c86d79d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 1 Nov 2024 14:16:54 +1100 Subject: [PATCH 01/27] tabs --- js/controller.css | 122 ++++++++++++++++++------------------- js/controller.js | 3 +- js/controller_panel.js | 133 +++++++++++++++++++++++------------------ 3 files changed, 139 insertions(+), 119 deletions(-) diff --git a/js/controller.css b/js/controller.css index 64fe15f..5c4eac8 100644 --- a/js/controller.css +++ b/js/controller.css @@ -1,11 +1,15 @@ /* Menu */ -.controller-menu-button { +.controller_menu_buttons { + background-color: var(--comfy-input-bg); + border-radius: 6px; +} +.controller_menu_button { cursor: pointer; - font-size: 14pt; + font-size: 12pt; background-color: var(--comfy-input-bg); - padding: 4px; - border-radius: 4px; + padding: 6px; + border-radius: 6px; } .showing { @@ -58,18 +62,9 @@ .controller.collapsed { overflow: clip; min-width: 0px; - width: 128px; + padding: 0px; } -.header_button.collapse_button { - color: var(--p-button-text-primary-color); - padding-right: 6px; - padding-left:2px; -} - -.controller.collapsed .header_button.collapse_button { - color: var(--mid-fore-color); -} /* Header */ @@ -93,84 +88,88 @@ border-bottom: 1px solid var(--third-back-color); padding: 0px 4px 0px 4px; justify-content: flex-end; - align-items: center; + align-items: stretch; } .subheader1 { cursor:grab; + cursor: grab; + padding: 4px 0px 0px 0px; + border-bottom: 3px solid black; + margin-bottom: 2px; } -.controller.being_dragged .subheader1 { +.controller.grabbed .subheader1 { cursor:grabbing; } -.subheader2 { - padding: 8px 0px 8px 0px; +.tabs.group { + flex-grow: 1; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} +.tab { + border-top-left-radius: 6px; + border-top-right-radius: 6px; + padding: 2px 6px; + border: 1px solid var(--deep-back-color); + color : var(--muted-fore-color); + cursor: pointer; + flex-grow: 1; + text-align: center; +} +.tab.selected { + border-bottom: none; + color: var(--main-fore-color) } -.header_title { - font-family: Arial, sans-serif; - font-size:13px; - flex-grow:1; - text-align: center; - padding: 8px 0px 8px 0px; - } +.controller.grabbed .tab { + cursor:grabbing; +} -.header_select { - max-width: var(--element_width); - color: var(--main-fore-color); - } +.collapsed .tab { + display: none; +} +.collapsed .tab.selected { + display: block; +} + +.header_buttons { + border-bottom: 1px solid var(--deep-back-color); + flex-shrink: 0; +} .header_button { cursor: pointer; font-size: 12pt; color: var(--mid-fore-color); - padding-left: 6px; - padding-right: 2px; + padding: 8px 4px 0px 4px; + top:-4px; + position: relative; } +.header_button.collapse_button { + top: 4px; +} + + + .clicked { color: var(--p-button-text-primary-color); } .being_dragged { opacity: 0.5; + cursor: grabbing; } .extra_controls { height: 15px; } -/* -.advanced_controls { - margin: 5px 2px 5px 0px; - padding: 2px; - font-size: 75%; - display: flex; - justify-content: center; - background-color: #C0808060; -} -.advanced_label { - font-size: 80%; - position: absolute; - top: 6px; - right: 22px; - height: 15px; - cursor: pointer; -} - -.advanced_label_ { - width: 15px; - height: 15px; - background-color: #00000000; - cursor: pointer; - left: -5px; - top: 1px; -} - -.advanced_checkbox { - margin: 0px; -}*/ +.collapsed .main { display:none; } +.collapsed .footer { display: none; } .empty_message { @@ -407,6 +406,7 @@ textarea.input { .tooltip { position: relative; display: inline-block; + z-index:1000; } /* Tooltip text */ diff --git a/js/controller.js b/js/controller.js index 8a8589d..4bf48dc 100644 --- a/js/controller.js +++ b/js/controller.js @@ -65,7 +65,8 @@ app.registerExtension({ {'rel':'stylesheet', 'type':'text/css', 'href':`${BASE_PATH}/controller.css` } ) create('link', null, document.getElementsByTagName('HEAD')[0], {'rel':'stylesheet', 'type':'text/css', 'href':`${BASE_PATH}/slider.css` } ) - + create('link', null, document.getElementsByTagName('HEAD')[0], + {'rel':'stylesheet', 'type':'text/css', 'href':`${BASE_PATH}/tabs.css` } ) // Allow our elements to do any setup they want on_setup() diff --git a/js/controller_panel.js b/js/controller_panel.js index 46e5358..97995bf 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -82,14 +82,18 @@ export class ControllerPanel extends HTMLDivElement { static mouse_up_anywhere() { Object.keys(ControllerPanel.instances).forEach((k)=>{ - const t = ControllerPanel.instances[k] - t.should_update_size = false; - t.being_dragged = false; - t.classList.remove('being_dragged') - t.set_position(true) + ControllerPanel.instances[k].mouse_up() }) } + mouse_up() { + this.should_update_size = false; + this.being_dragged = false; + this.classList.remove('being_dragged') + this.classList.remove('grabbed') + this.set_position(true) + } + redraw() { this.build_controllerPanel() } @@ -135,11 +139,22 @@ export class ControllerPanel extends HTMLDivElement { var spacer = null comfy_menu.childNodes.forEach((node)=>{if (node.classList.contains('flex-grow')) spacer = node}) if (!spacer) spacer = comfy_menu.firstChild - ControllerPanel.menu_button = create('i', 'pi pi-sliders-h controller-menu-button') - add_tooltip(ControllerPanel.menu_button, 'Toggle controller') + const buttons = create('span', 'controller_menu_buttons') + spacer.after(buttons) + + ControllerPanel.menu_button = create('i', 'pi pi-sliders-h controller_menu_button', buttons) + add_tooltip(ControllerPanel.menu_button, 'Toggle controllers') classSet(ControllerPanel.menu_button, 'showing', !global_settings.hidden) - spacer.after(ControllerPanel.menu_button) ControllerPanel.menu_button.addEventListener('click', ControllerPanel.toggle) + + ControllerPanel.refresh_button = create('i', 'pi pi-sync controller_menu_button', buttons) + add_tooltip(ControllerPanel.refresh_button, `Refresh controllers`) + ControllerPanel.refresh_button.addEventListener('click', (e) => { + UpdateController.make_request("refresh_button"); + ControllerPanel.refresh_button.classList.add("clicked"); + setTimeout(()=>{ControllerPanel.refresh_button.classList.remove("clicked")}, 200); + }) + } else { setTimeout(ControllerPanel.create_menu_icon,100) } @@ -325,20 +340,23 @@ export class ControllerPanel extends HTMLDivElement { header_mouse(e) { if (e.type=='mousedown') { - if (e.target==this.header_title) { + if (e.target==this.header_tabs || e.target.parentElement==this.header_tabs) { this.being_dragged = true + this.classList.add('grabbed') this.offset_x = e.x - this.settings.position.x this.offset_y = e.y - this.settings.position.y - this.classList.add('being_dragged') e.preventDefault() e.stopPropagation() } } - if (e.type=='mousemove' && this.being_dragged && e.currentTarget==window) { - this.settings.set_position( e.x - this.offset_x, e.y - this.offset_y, null, null ) - this.set_position(true) - this.offset_x = e.x - this.settings.position.x - this.offset_y = e.y - this.settings.position.y + if (this.being_dragged) { + if (e.type=='mousemove' && e.currentTarget==window) { + this.classList.add('being_dragged') + this.settings.set_position( e.x - this.offset_x, e.y - this.offset_y, null, null ) + this.set_position(true) + this.offset_x = e.x - this.settings.position.x + this.offset_y = e.y - this.settings.position.y + } } } @@ -362,49 +380,46 @@ export class ControllerPanel extends HTMLDivElement { */ this._header = create('span','header') this._main = create('span','main') - - //this.header.innerHTML = "" this.header1 = create('span','subheader subheader1',this._header) - this.minimisedot = create("i", `pi pi-sliders-h header_button collapse_button`, this.header1) - this.minimisedot.addEventListener("click", (e)=>{ - e.preventDefault(); - e.stopPropagation(); - this.settings.collapsed = (!this.settings.collapsed) - UpdateController.make_request('collapse') - }) - add_tooltip(this.minimisedot, `${this.settings.collapsed?"Open":"Collapse"} controller`, 'right') - this.header_title = create('span', 'header_title', this.header1, {"innerText":"CONTROLLER"}) + + + + this.header_tabs = create('span', 'tabs group', this.header1) this.header1.addEventListener('mousedown', (e) => this.header_mouse(e)) window.addEventListener('mousemove', (e) => this.header_mouse(e)) - this.header2 = create('span','subheader subheader2', this._header) - if (GroupManager.any_groups()) { - this.group_select = create("select", 'header_select', this.header2, {"doesntBlockRefresh":true}) - GroupManager.list_group_names().forEach((nm) => { - const o = new Option(nm,nm) - o.style.backgroundColor = GroupManager.group_color(nm) - this.group_select.add(o) + this.settings.group_choice = GroupManager.valid_option(this.settings.group_choice) + + GroupManager.list_group_names().forEach((nm) => { + const tab = create('span','tab',this.header_tabs,{"innerText":nm}) + classSet(tab,'selected',(this.settings.group_choice == nm)) + tab.style.backgroundColor = GroupManager.group_color(nm) + tab.addEventListener('mousedown', (e) => { + tab.mouse_down_at_x = e.x + tab.mouse_down_at_y = e.y + this.header_tabs.mouse_down_on = tab }) - try { this.group_select.value = this.settings.group_choice } - catch { this.group_select.value = Texts.ALL_GROUPS } - this.group_select.style.backgroundColor = GroupManager.group_color(this.group_select.value) - this.group_select.addEventListener('change', (e)=>{ - this.group_select.classList.remove('unrefreshable') - if (this.settings.group_choice != e.target.value) { - this.settings.group_choice = e.target.value; - UpdateController.make_request('group selection changed') + tab.addEventListener('mouseup', (e) => { + if (this.header_tabs.mouse_down_on == tab && Math.abs(tab.mouse_down_at_x - e.x) < 2 && Math.abs(tab.mouse_down_at_y - e.y) < 2) { + if (this.settings.collapsed) { + this.settings.collapsed = false; + UpdateController.make_request('uncollapse') + } else { + this.settings.group_choice = nm + UpdateController.make_request('group selection changed') + } + this.mouse_up() + e.preventDefault() + e.stopPropagation() } - this.group_select.classList.remove('unrefreshable') - }) - this.group_select.addEventListener('mousedown', (e) => { - this.group_select.classList.add('unrefreshable') - setTimeout(()=>{this.group_select.classList.remove('unrefreshable')}, Timings.GROUP_SELECT_NOSELECT_WAIT) + this.header_tabs.mouse_down_on = null }) + }) + + this.header1.style.borderBottomColor = GroupManager.group_color(this.settings.group_choice) - } - this.settings.group_choice = GroupManager.valid_option(this.settings.group_choice) //this.main.innerHTML = "" @@ -426,8 +441,10 @@ export class ControllerPanel extends HTMLDivElement { Back to the header */ if (!this.settings.collapsed) { + this.buttons = create('span', 'header_buttons', this.header1) + if (this.showAdvancedCheckbox) { - this.show_advanced = create('i', `pi pi-bolt header_button${this.settings.advanced ? " clicked":""}`, this.header1) + this.show_advanced = create('i', `pi pi-bolt header_button${this.settings.advanced ? " clicked":""}`, this.buttons) this.show_advanced.addEventListener('click', (e) => { this.settings.advanced = !this.settings.advanced this.redraw() @@ -435,15 +452,17 @@ export class ControllerPanel extends HTMLDivElement { }) add_tooltip(this.show_advanced, `${this.settings.advanced?"Hide":"Show"} advanced controls`) } - this.refresh_button = create('i', 'pi pi-sync header_button', this.header1) - this.refresh_button.addEventListener('click', (e) => { - UpdateController.make_request("refresh_button"); - this.refresh_button.classList.add("clicked"); - setTimeout(()=>{this.refresh_button.classList.remove("clicked")}, 200); - e.stopPropagation() + + this.minimisedot = create("i", `pi pi-minus header_button collapse_button`, this.buttons) + this.minimisedot.addEventListener("click", (e)=>{ + e.preventDefault(); + e.stopPropagation(); + this.settings.collapsed = (!this.settings.collapsed) + UpdateController.make_request('collapse') }) - add_tooltip(this.refresh_button, `Refresh controller`) - this.delete_button = create('i', 'pi pi-times header_button', this.header1) + add_tooltip(this.minimisedot, 'Minimise') + + this.delete_button = create('i', 'pi pi-times header_button', this.buttons) this.delete_button.addEventListener('click', (e) => { this.delete_controller() }) From dae9e1d838ad8964d92b80492ec2d6a820083da5 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 1 Nov 2024 14:34:40 +1100 Subject: [PATCH 02/27] order --- js/controller.css | 9 +++++++-- js/controller_panel.js | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/js/controller.css b/js/controller.css index 5c4eac8..29dee23 100644 --- a/js/controller.css +++ b/js/controller.css @@ -108,20 +108,25 @@ display: flex; flex-wrap: wrap; justify-content: flex-start; + flex-direction: row-reverse; } .tab { + --base-color: black; border-top-left-radius: 6px; border-top-right-radius: 6px; padding: 2px 6px; border: 1px solid var(--deep-back-color); - color : var(--muted-fore-color); + background-color: color-mix(in srgb, var(--base-color), transparent); + color: var(--muted-fore-color); + /*color : color-contrast(var(--base-color) vs var(--muted-fore-color), var(--second-back-color)) */ cursor: pointer; flex-grow: 1; text-align: center; } .tab.selected { border-bottom: none; - color: var(--main-fore-color) + color: var(--main-fore-color); + background-color: var(--base-color); } .controller.grabbed .tab { diff --git a/js/controller_panel.js b/js/controller_panel.js index 97995bf..0fe8aae 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -391,10 +391,14 @@ export class ControllerPanel extends HTMLDivElement { this.settings.group_choice = GroupManager.valid_option(this.settings.group_choice) + var order = 1000 GroupManager.list_group_names().forEach((nm) => { const tab = create('span','tab',this.header_tabs,{"innerText":nm}) classSet(tab,'selected',(this.settings.group_choice == nm)) - tab.style.backgroundColor = GroupManager.group_color(nm) + tab.style.order = (this.settings.group_choice == nm) ? 1001 : order + order -= 1 + //tab.style.backgroundColor = GroupManager.group_color(nm) + tab.style.setProperty('--base-color', GroupManager.group_color(nm)) tab.addEventListener('mousedown', (e) => { tab.mouse_down_at_x = e.x tab.mouse_down_at_y = e.y From 303b4d25b30c239d711ff9467fa48dab6b6aaa50 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 1 Nov 2024 17:26:32 +1100 Subject: [PATCH 03/27] if stacked ensure active tab last --- js/controller.css | 2 +- js/controller_panel.js | 35 +++++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/js/controller.css b/js/controller.css index 29dee23..f9ee7f9 100644 --- a/js/controller.css +++ b/js/controller.css @@ -108,7 +108,7 @@ display: flex; flex-wrap: wrap; justify-content: flex-start; - flex-direction: row-reverse; + flex-direction: row; } .tab { --base-color: black; diff --git a/js/controller_panel.js b/js/controller_panel.js index 0fe8aae..bc2669e 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -240,6 +240,7 @@ export class ControllerPanel extends HTMLDivElement { this.show_overlay(`${Math.round(this.getBoundingClientRect().width)} x ${Math.round(this.getBoundingClientRect().height)}px`, this) this.settings.set_position(null,null,this.getBoundingClientRect().width,this.getBoundingClientRect().height) } + if (this.getBoundingClientRect().width>0) this.are_tabs_wrapped() } consider_adding_node(node_or_node_id) { @@ -360,6 +361,24 @@ export class ControllerPanel extends HTMLDivElement { } } + are_tabs_wrapped() { + const old_value = this.header_tabs_wrapped + this.header_tabs_wrapped = false + if (this.header_tabs?.childNodes?.length > 1) { + var tp = null; + Array.from(this.header_tabs.childNodes).forEach((n)=>{ + if (!tp) { + tp = n.getBoundingClientRect().top + } else { + if (tp != n.getBoundingClientRect().top) this.header_tabs_wrapped = true + } + }) + } + if (old_value!==this.header_tabs_wrapped) { + UpdateController.make_request("Change in tab wrap") + } + } + build_controllerPanel() { this.classList.add('unrefreshable') this.reason = 'already refreshing' @@ -382,22 +401,18 @@ export class ControllerPanel extends HTMLDivElement { this._main = create('span','main') this.header1 = create('span','subheader subheader1',this._header) - - this.header_tabs = create('span', 'tabs group', this.header1) this.header1.addEventListener('mousedown', (e) => this.header_mouse(e)) window.addEventListener('mousemove', (e) => this.header_mouse(e)) - this.settings.group_choice = GroupManager.valid_option(this.settings.group_choice) - var order = 1000 GroupManager.list_group_names().forEach((nm) => { const tab = create('span','tab',this.header_tabs,{"innerText":nm}) classSet(tab,'selected',(this.settings.group_choice == nm)) - tab.style.order = (this.settings.group_choice == nm) ? 1001 : order - order -= 1 - //tab.style.backgroundColor = GroupManager.group_color(nm) + if (this.header_tabs_wrapped && this.settings.group_choice == nm) tab.style.order = 1 + //tab.style.order = (this.settings.group_choice == nm) ? 0 : 1 + //order -= 1 tab.style.setProperty('--base-color', GroupManager.group_color(nm)) tab.addEventListener('mousedown', (e) => { tab.mouse_down_at_x = e.x @@ -420,13 +435,8 @@ export class ControllerPanel extends HTMLDivElement { this.header_tabs.mouse_down_on = null }) }) - this.header1.style.borderBottomColor = GroupManager.group_color(this.settings.group_choice) - - - //this.main.innerHTML = "" - this.new_node_id_list = [] this.remove_absent_nodes() this.settings.node_order.forEach( (n) => {this.consider_adding_node(n)} ) @@ -483,6 +493,7 @@ export class ControllerPanel extends HTMLDivElement { this.set_position(true) observe_resizables( this, this.on_child_height_change.bind(this) ) + setTimeout(this.are_tabs_wrapped.bind(this), 20) } save_node_order() { From 75978f286e1d849f79e36af793639763a2aef8f3 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 1 Nov 2024 23:00:13 +1100 Subject: [PATCH 04/27] 260 --- js/widget_change_manager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/widget_change_manager.js b/js/widget_change_manager.js index 6773f56..0784b5e 100644 --- a/js/widget_change_manager.js +++ b/js/widget_change_manager.js @@ -15,15 +15,14 @@ export class WidgetChangeManager { static notify(widget) { if (widget.wcm_id && WidgetChangeManager.widget_listener_map[widget.wcm_id]) { WidgetChangeManager.widget_listener_map[widget.wcm_id] = WidgetChangeManager.widget_listener_map[widget.wcm_id].filter((l)=>l.wcm_manager_callback()) - //WidgetChangeManager.widget_listener_map[widget.wcm_id].forEach((l)=>{l.wcm_manager_callback()}) } + app.graph.setDirtyCanvas(true,true) } static set_widget_value(widget, v) { widget.value = v if (widget.original_callback) widget.original_callback(widget.value) if (widget.options.min != null) widget.value = clamp(widget.value, widget.options.min, widget.options.max) WidgetChangeManager.notify(widget) - app.graph.setDirtyCanvas(true,true) } } From 5c0c6cfd7e5271016c0cd8d18c93a4e015739291 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 00:30:41 +1100 Subject: [PATCH 05/27] 249 --- js/controller.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/controller.css b/js/controller.css index f9ee7f9..8a2950e 100644 --- a/js/controller.css +++ b/js/controller.css @@ -112,8 +112,7 @@ } .tab { --base-color: black; - border-top-left-radius: 6px; - border-top-right-radius: 6px; + border-radius: 6px; padding: 2px 6px; border: 1px solid var(--deep-back-color); background-color: color-mix(in srgb, var(--base-color), transparent); @@ -122,11 +121,16 @@ cursor: pointer; flex-grow: 1; text-align: center; + margin: 0px 1px 1px 1px; + text-overflow: ellipsis; } .tab.selected { border-bottom: none; color: var(--main-fore-color); background-color: var(--base-color); + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + margin-bottom:0px; } .controller.grabbed .tab { From 17c93a1186743a79213ba74c11110844a82948ce Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 00:31:26 +1100 Subject: [PATCH 06/27] 249 --- js/controller.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/controller.css b/js/controller.css index 8a2950e..7d5bdf8 100644 --- a/js/controller.css +++ b/js/controller.css @@ -121,7 +121,7 @@ cursor: pointer; flex-grow: 1; text-align: center; - margin: 0px 1px 1px 1px; + margin: 0px 1px 4px 1px; text-overflow: ellipsis; } .tab.selected { From 78a0db7924b0b91e181b976565543d813cedb9e7 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 13:23:27 +1100 Subject: [PATCH 07/27] 249 --- js/controller.css | 86 +++++++++++--- js/controller.js | 4 +- js/controller_panel.js | 258 ++++++++++++++++++++++++++--------------- js/groups.js | 7 +- js/settings.js | 1 + 5 files changed, 240 insertions(+), 116 deletions(-) diff --git a/js/controller.css b/js/controller.css index 7d5bdf8..7be2f92 100644 --- a/js/controller.css +++ b/js/controller.css @@ -87,28 +87,48 @@ display: flex; border-bottom: 1px solid var(--third-back-color); padding: 0px 4px 0px 4px; - justify-content: flex-end; - align-items: stretch; -} - -.subheader1 { - cursor:grab; + cursor: grab; cursor: grab; padding: 4px 0px 0px 0px; - border-bottom: 3px solid black; + border-bottom: 1px solid var(--main-back-color); margin-bottom: 2px; + justify-content: space-between; +} + +.subheader1 { + display:flex; +} + +.subheader2 { + padding-top:2px; +} + +.subheader .left { + +} + +.subheader .right { + +} + +.subheader1 .right { + min-width: max-content; } .controller.grabbed .subheader1 { cursor:grabbing; } +.last { order: 100 } + .tabs.group { - flex-grow: 1; display: flex; - flex-wrap: wrap; justify-content: flex-start; flex-direction: row; + + /* allow the group to shirnk */ + min-width: 0px; + flex-shrink: 1; } .tab { --base-color: black; @@ -119,18 +139,23 @@ color: var(--muted-fore-color); /*color : color-contrast(var(--base-color) vs var(--muted-fore-color), var(--second-back-color)) */ cursor: pointer; - flex-grow: 1; text-align: center; margin: 0px 1px 4px 1px; + + /* allow the group to shirnk */ + min-width: 0px; + flex-shrink: 1; text-overflow: ellipsis; + overflow: hidden; } .tab.selected { - border-bottom: none; + color: var(--main-fore-color); background-color: var(--base-color); - border-bottom-left-radius: 0px; + /*border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; - margin-bottom:0px; + border-bottom: none; + margin-bottom:0px;*/ } .controller.grabbed .tab { @@ -153,7 +178,7 @@ cursor: pointer; font-size: 12pt; color: var(--mid-fore-color); - padding: 8px 4px 0px 4px; + margin: 8px 4px 0px 4px; top:-4px; position: relative; } @@ -162,6 +187,10 @@ top: 4px; } +.collapse_button { + margin-top:0; + padding-top: 8px; +} .clicked { @@ -393,11 +422,38 @@ textarea.input { position: absolute; } +/* group add */ + +.group_add_select { + position:absolute; + z-index: 1001; + background-color: var(--comfy-input-bg); + border: thin solid var(--border-color); + border-radius: 6px; + padding: 4px; +} + +.group_add_option { + padding: 2px; + cursor: pointer; + color: var(--fg-color); + border-bottom: 1px solid var(--border-color); + border-left: 2px solid var(--comfy-input-bg); +} + +.group_add_option:last-child { + border-bottom: none; +} + +.group_add_option:hover { + border-left-color: var(--comfy-input-fg); +} + /* Global */ .hidden { - display:none; + display:none !important; } .overlay { diff --git a/js/controller.js b/js/controller.js index 4bf48dc..eaf592e 100644 --- a/js/controller.js +++ b/js/controller.js @@ -27,6 +27,7 @@ function on_setup() { api.addEventListener('b_preview', OnExecutedManager.on_b_preview) window.addEventListener("resize", ControllerPanel.onWindowResize) window.addEventListener('mouseup', ControllerPanel.mouse_up_anywhere) + window.addEventListener('mousemove', ControllerPanel.mouse_move_anywhere) const original_getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions; @@ -129,7 +130,4 @@ app.registerExtension({ }, - //registerCustomNodes() { - // LiteGraph.registerNodeType("CGControllerNode", CGControllerNode) - //} }) \ No newline at end of file diff --git a/js/controller_panel.js b/js/controller_panel.js index bc2669e..50ce66b 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -77,7 +77,6 @@ export class ControllerPanel extends HTMLDivElement { this.addEventListener('mousedown', ()=>{this.should_update_size = true}) new ResizeObserver((x) => this.on_size_change()).observe(this) - } static mouse_up_anywhere() { @@ -86,6 +85,12 @@ export class ControllerPanel extends HTMLDivElement { }) } + static mouse_move_anywhere(e) { + Object.keys(ControllerPanel.instances).forEach((k)=>{ + ControllerPanel.instances[k].mouse_move(e) + }) + } + mouse_up() { this.should_update_size = false; this.being_dragged = false; @@ -185,6 +190,16 @@ export class ControllerPanel extends HTMLDivElement { return response } + choose_suitable_initial_group() { + const all_options = GroupManager.list_group_names() + const all_used = new Set() + Object.keys(ControllerPanel.instances).forEach((k)=>{ + ControllerPanel.instances[k].settings.groups.forEach((g)=>all_used.add(g)) + }) + const unused_options = all_options.filter((g)=>!(all_used.has(g))) + return (unused_options.length) ? unused_options[0] : Texts.ALL_GROUPS + } + _can_refresh() { try { //if (!this.showing) { return -1 } @@ -240,7 +255,6 @@ export class ControllerPanel extends HTMLDivElement { this.show_overlay(`${Math.round(this.getBoundingClientRect().width)} x ${Math.round(this.getBoundingClientRect().height)}px`, this) this.settings.set_position(null,null,this.getBoundingClientRect().width,this.getBoundingClientRect().height) } - if (this.getBoundingClientRect().width>0) this.are_tabs_wrapped() } consider_adding_node(node_or_node_id) { @@ -339,19 +353,19 @@ export class ControllerPanel extends HTMLDivElement { } } - header_mouse(e) { - if (e.type=='mousedown') { - if (e.target==this.header_tabs || e.target.parentElement==this.header_tabs) { - this.being_dragged = true - 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() - } - } + header_mouse_down(e) { + //if (e.target==this.header_tabs || e.target.parentElement==this.header_tabs) { + this.being_dragged = true + 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() + //} + } + mouse_move(e) { if (this.being_dragged) { - if (e.type=='mousemove' && e.currentTarget==window) { + if (e.currentTarget==window) { this.classList.add('being_dragged') this.settings.set_position( e.x - this.offset_x, e.y - this.offset_y, null, null ) this.set_position(true) @@ -361,24 +375,6 @@ export class ControllerPanel extends HTMLDivElement { } } - are_tabs_wrapped() { - const old_value = this.header_tabs_wrapped - this.header_tabs_wrapped = false - if (this.header_tabs?.childNodes?.length > 1) { - var tp = null; - Array.from(this.header_tabs.childNodes).forEach((n)=>{ - if (!tp) { - tp = n.getBoundingClientRect().top - } else { - if (tp != n.getBoundingClientRect().top) this.header_tabs_wrapped = true - } - }) - } - if (old_value!==this.header_tabs_wrapped) { - UpdateController.make_request("Change in tab wrap") - } - } - build_controllerPanel() { this.classList.add('unrefreshable') this.reason = 'already refreshing' @@ -392,35 +388,96 @@ export class ControllerPanel extends HTMLDivElement { _build_controllerPanel() { classSet(this, 'hidden', global_settings.hidden) this.style.setProperty('--font-size',`${1.333*getSettingValue(SettingIds.FONT_SIZE, 12)}px`) - GroupManager.setup( ) + GroupManager.setup( ) /* Create the top section */ this._header = create('span','header') - this._main = create('span','main') + this._main = create('span','main') this.header1 = create('span','subheader subheader1',this._header) + this.header2 = create('span','subheader subheader2',this._header) + + this.header1_left = create('span', 'left tabs group', this.header1) + this.header1_right = create('span', 'right', this.header1) + this.header2_left = create('span', 'left', this.header2) + this.header2_right = create('span', 'right', this.header2) + + this.header1.addEventListener('mousedown', (e) => this.header_mouse_down(e)) + this.header2.addEventListener('mousedown', (e) => this.header_mouse_down(e)) + + if (this.settings.groups.length==0) this.settings.groups = [this.choose_suitable_initial_group(),] + if (this.settings.group_choice == null || !this.settings.groups.includes(this.settings.group_choice)) { + this.settings.group_choice = this.settings.groups[0] + } + + this.find_groups_not_included() + this.add_tabs() - this.header_tabs = create('span', 'tabs group', this.header1) - this.header1.addEventListener('mousedown', (e) => this.header_mouse(e)) - window.addEventListener('mousemove', (e) => this.header_mouse(e)) + if (this.settings.collapsed) { + this.minimise_button = create("i", `pi pi-minus header_button collapse_button`, this.header1_right) + this.delete_button = create('i', 'pi pi-times header_button', this.header1_right) + } else { + this.add_group_button = create('i', 'pi pi-plus header_button last', this.header1_left) + this.remove_group_button = create('i', 'pi pi-trash header_button', this.header2_left) + //this.bypass_group_button = create('i', 'pi pi-ban header_button', this.header2_left) + this.show_advanced_button = create('i', `pi pi-bolt header_button${this.settings.advanced ? " clicked":""}`, this.header2_left) + this.minimise_button = create("i", `pi pi-minus header_button collapse_button`, this.header1_right) + this.delete_button = create('i', 'pi pi-times header_button', this.header1_right) + } + + + /* + Node blocks + */ - this.settings.group_choice = GroupManager.valid_option(this.settings.group_choice) + this.new_node_id_list = [] + this.remove_absent_nodes() + this.settings.node_order.forEach( (n) => {this.consider_adding_node(n)} ) + app.graph._nodes.forEach( (n) => {this.consider_adding_node(n)} ) + if (this.new_node_id_list.length>0) this.settings.node_order = this.new_node_id_list + + const node_count = this.set_node_visibility() - GroupManager.list_group_names().forEach((nm) => { - const tab = create('span','tab',this.header_tabs,{"innerText":nm}) + if (node_count.nodes == 0) { + const EMPTY_MESSAGE = + "

Add nodes to the controller
by right-clicking the node
and using the Controller Panel submenu

" + create('span', 'empty_message', this._main, {"innerHTML":EMPTY_MESSAGE}) + } + + this.add_button_actions() + + /* + Finalise + */ + this.replaceChild(this._main, this.main) + this.replaceChild(this._header, this.header) + this.header = this._header + this.main = this._main + + this.set_position(true) + observe_resizables( this, this.on_child_height_change.bind(this) ) + } + + find_groups_not_included() { + const all_options = GroupManager.list_group_names() + const all_used = new Set() + this.settings.groups.forEach((g)=>all_used.add(g)) + this.groups_not_included = all_options.filter((g)=>!(all_used.has(g))) + } + + add_tabs() { + this.settings.groups.forEach((nm) => { + const tab = create('span','tab',this.header1_left,{"innerText":nm}) classSet(tab,'selected',(this.settings.group_choice == nm)) - if (this.header_tabs_wrapped && this.settings.group_choice == nm) tab.style.order = 1 - //tab.style.order = (this.settings.group_choice == nm) ? 0 : 1 - //order -= 1 tab.style.setProperty('--base-color', GroupManager.group_color(nm)) tab.addEventListener('mousedown', (e) => { - tab.mouse_down_at_x = e.x - tab.mouse_down_at_y = e.y - this.header_tabs.mouse_down_on = tab + this.mouse_down_at_x = e.x + this.mouse_down_at_y = e.y + this.mouse_down_on = tab }) tab.addEventListener('mouseup', (e) => { - if (this.header_tabs.mouse_down_on == tab && Math.abs(tab.mouse_down_at_x - e.x) < 2 && Math.abs(tab.mouse_down_at_y - e.y) < 2) { + 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; UpdateController.make_request('uncollapse') @@ -432,68 +489,68 @@ export class ControllerPanel extends HTMLDivElement { e.preventDefault() e.stopPropagation() } - this.header_tabs.mouse_down_on = null + this.mouse_down_on = null }) }) - this.header1.style.borderBottomColor = GroupManager.group_color(this.settings.group_choice) + } - this.new_node_id_list = [] - this.remove_absent_nodes() - this.settings.node_order.forEach( (n) => {this.consider_adding_node(n)} ) - app.graph._nodes.forEach( (n) => {this.consider_adding_node(n)} ) - if (this.new_node_id_list.length>0) this.settings.node_order = this.new_node_id_list + add_button_actions() { + if (this.add_group_button) { + this.add_group_button.addEventListener('click', (e) => { + e.preventDefault() + e.stopPropagation() + this.show_group_select(e) + }) + add_tooltip(this.add_group_button, 'Add new group tab', 'right') + classSet(this.add_group_button, 'hidden', (this.groups_not_included.length==0)) + } - const node_count = this.set_node_visibility() + if (this.remove_group_button) { + this.remove_group_button.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.settings.groups = this.settings.groups.filter((g)=>g!=this.settings.group_choice) + if (this.settings.groups.length==0) { + this.delete_controller() + } + UpdateController.make_request('group removed') + }) + add_tooltip(this.remove_group_button, 'Remove active group tab', 'right') + } - if (node_count.nodes == 0) { - const EMPTY_MESSAGE = - "

Add nodes to the controller
by right-clicking the node
and using the Controller Panel submenu

" - create('span', 'empty_message', this._main, {"innerHTML":EMPTY_MESSAGE}) + if (this.bypass_group_button) { + this.bypass_group_button.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + alert("yet to be implemented") + }) + add_tooltip(this.bypass_group_button, `Toggle group bypass`, 'right') } - /* - Back to the header - */ - if (!this.settings.collapsed) { - this.buttons = create('span', 'header_buttons', this.header1) - - if (this.showAdvancedCheckbox) { - this.show_advanced = create('i', `pi pi-bolt header_button${this.settings.advanced ? " clicked":""}`, this.buttons) - this.show_advanced.addEventListener('click', (e) => { - this.settings.advanced = !this.settings.advanced - this.redraw() - e.stopPropagation() - }) - add_tooltip(this.show_advanced, `${this.settings.advanced?"Hide":"Show"} advanced controls`) - } - - this.minimisedot = create("i", `pi pi-minus header_button collapse_button`, this.buttons) - this.minimisedot.addEventListener("click", (e)=>{ + if (this.show_advanced_button) { + this.show_advanced_button.addEventListener('click', (e) => { + this.settings.advanced = !this.settings.advanced + this.redraw() + e.stopPropagation() + }) + add_tooltip(this.show_advanced_button, `${this.settings.advanced?"Hide":"Show"} advanced controls`, 'right') + classSet(this.show_advanced_button, 'hidden', !this.showAdvancedCheckbox) + } + + if (this.minimise_button) { + this.minimise_button.addEventListener("click", (e)=>{ e.preventDefault(); e.stopPropagation(); this.settings.collapsed = (!this.settings.collapsed) UpdateController.make_request('collapse') }) - add_tooltip(this.minimisedot, 'Minimise') + } - this.delete_button = create('i', 'pi pi-times header_button', this.buttons) + if (this.delete_button) { this.delete_button.addEventListener('click', (e) => { this.delete_controller() }) - add_tooltip(this.delete_button, `Delete this controller`) } - - /* - Finalise - */ - this.replaceChild(this._main, this.main) - this.replaceChild(this._header, this.header) - this.header = this._header - this.main = this._main - - this.set_position(true) - observe_resizables( this, this.on_child_height_change.bind(this) ) - setTimeout(this.are_tabs_wrapped.bind(this), 20) } save_node_order() { @@ -502,6 +559,23 @@ export class ControllerPanel extends HTMLDivElement { this.settings.node_order = node_id_list } + show_group_select(e) { + const the_select = create('span','group_add_select', document.body) + this.groups_not_included.forEach((g)=>{ + const the_choice = create('div', 'group_add_option', the_select, {"innerText":g}) + the_choice.style.backgroundColor = GroupManager.group_color(g) + the_choice.addEventListener('click', (e)=>{ + this.settings.groups.push(g) + this.settings.group_choice = g + the_select.remove() + UpdateController.make_request('group tab added') + }) + }) + the_select.addEventListener('mouseleave', (e)=>{the_select.remove()}) + the_select.style.left = `${e.x - 8}px` + the_select.style.top = `${e.y - 8}px` + } + } customElements.define('cp-div', ControllerPanel, {extends: 'div'}) diff --git a/js/groups.js b/js/groups.js index 0266104..74ec11e 100644 --- a/js/groups.js +++ b/js/groups.js @@ -44,13 +44,8 @@ export class GroupManager { static is_node_in(group_name, node_id) { if (group_name==Texts.ALL_GROUPS) return true - return (GroupManager.instance.groups[group_name] && GroupManager.instance.groups[group_name].has(parseInt(node_id))) + return (GroupManager.instance.groups?.[group_name] && GroupManager.instance.groups[group_name].has(parseInt(node_id))) } static any_groups() { return (Object.keys(GroupManager.instance.groups).length > 0) } - - static valid_option(group_name) { - if (group_name && GroupManager.instance.groups[group_name]) return group_name - return Texts.ALL_GROUPS - } } \ No newline at end of file diff --git a/js/settings.js b/js/settings.js index 0df59b8..a368b16 100644 --- a/js/settings.js +++ b/js/settings.js @@ -5,6 +5,7 @@ import { Texts } from "./constants.js" const DEFAULTS = { "node_order" : [], "advanced" : false, + "groups" : [], "group_choice" : Texts.ALL_GROUPS, "position" : {"x" : 0, "y" : 0, "w" : 250, "h" : 180}, "userposition" : {"x" : 0, "y" : 0, "w" : 250, "h" : 180}, From cfa86ceba9b1d772cb43d69a76d483967b7e5a27 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 20:11:17 +1100 Subject: [PATCH 08/27] more 1.3 --- js/constants.js | 11 +++++ js/controller.css | 23 ++++++++++ js/controller_panel.js | 95 ++++++++++++++++++++++++++--------------- js/groups.js | 22 ++++++++-- js/node_inclusion.js | 34 ++++----------- js/nodeblock.js | 10 +++++ js/update_controller.js | 17 +++++--- 7 files changed, 141 insertions(+), 71 deletions(-) diff --git a/js/constants.js b/js/constants.js index 62d0275..1255f63 100644 --- a/js/constants.js +++ b/js/constants.js @@ -8,6 +8,15 @@ export class SettingIds { static TOOLTIPS = "Controller.options.tooltips" } +export class InclusionOptions { + static EXCLUDE = "Don't include this node" + static INCLUDE = "Include this node" + static ADVANCED = "Include this node as advanced control" + static EXCLUDES = InclusionOptions.EXCLUDE.replace('this node', 'these nodes') + static INCLUDES = InclusionOptions.INCLUDE.replace('this node', 'these nodes') + static ADVANCEDS = InclusionOptions.ADVANCED.replace('this node', 'these nodes') +} + export class Timings { // ms static RESIZE_DELAY_BEFORE_REDRAW = 200 static SETTINGS_TRY_RELOAD = 1000 @@ -22,11 +31,13 @@ export class Timings { // ms export class Colors { static DARK_BACKGROUND = '#222222' + static MENU_HIGHLIGHT = '#C08080' } export class Texts { static ALL_GROUPS = "All" static UNGROUPED = "Ungrouped" + static CONTEXT_MENU = "Controller Panel" } export const BASE_PATH = "extensions/cg-controller" \ No newline at end of file diff --git a/js/controller.css b/js/controller.css index 7be2f92..6f91178 100644 --- a/js/controller.css +++ b/js/controller.css @@ -32,6 +32,9 @@ --overlay-background: #ffffff66; --overlay-foreground: #353535; + --bypass-button-color: #d179ff; + --bypass-overlay-color: #d179ff80; + --toggle-on: #8899aa; --toggle-off: #333333; @@ -192,6 +195,15 @@ padding-top: 8px; } +.header_button.none_bypassed { + +} +.header_button.some_bypassed { + +} +.header_button.all_bypassed { + color: var(--bypass-button-color) +} .clicked { color: var(--p-button-text-primary-color); @@ -306,6 +318,17 @@ display:none; } +.nodeblock.bypassed { + opacity: 0.5; +} + +.bypass_overlay { + position:absolute; + width: 100%; + height: 100%; + background-color: var(--bypass-overlay-color); +} + /* Entry (widget) */ .entry { diff --git a/js/controller_panel.js b/js/controller_panel.js index 50ce66b..dd3b8fb 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -165,12 +165,16 @@ export class ControllerPanel extends HTMLDivElement { } } - static redraw() { + static redraw(c) { if (!valid_settings()) { ControllerPanel.new_workflow() return } - Object.keys(ControllerPanel.instances).forEach((k)=>{ControllerPanel.instances[k].redraw() }) + if (c) { + c.redraw() + } else { + Object.keys(ControllerPanel.instances).forEach((k)=>{ControllerPanel.instances[k].redraw() }) + } } static toggle() { @@ -179,15 +183,20 @@ export class ControllerPanel extends HTMLDivElement { UpdateController.make_request('toggle') } - static can_refresh() { // returns -1 to say "no, and don't try again", 0 to mean "go ahead!", or n to mean "wait n ms then ask again" + static can_refresh(c) { // returns -1 to say "no, and don't try again", 0 to mean "go ahead!", or n to mean "wait n ms then ask again" if (app.configuringGraph) { Debug.trivia("configuring"); return -1 } - var response = 0 - Object.keys(ControllerPanel.instances).forEach((k)=>{ - const r = ControllerPanel.instances[k]._can_refresh() - if (r==-1 || response==-1) response = -1 - else response = Math.max(r, response) - }) - return response + + if (c) { + return c._can_refresh() + } else { + var response = 0 + Object.keys(ControllerPanel.instances).forEach((k)=>{ + const r = ControllerPanel.instances[k]._can_refresh() + if (r==-1 || response==-1) response = -1 + else response = Math.max(r, response) + }) + return response + } } choose_suitable_initial_group() { @@ -261,7 +270,7 @@ export class ControllerPanel extends HTMLDivElement { const node_id = node_or_node_id.id ?? node_or_node_id if (this.new_node_id_list.includes(node_id)) return // already got it in the new list if (NodeInclusionManager.include_node(node_or_node_id)) { // is it still valid? - if (this.node_blocks[node_id]) { + if (this.node_blocks[node_id] && this.node_blocks[node_id].can_reuse()) { this.node_blocks[node_id].build_nodeblock() } else { this.maybe_create_node_block_for_node(node_id) @@ -283,29 +292,25 @@ export class ControllerPanel extends HTMLDivElement { set_node_visibility() { this.showAdvancedCheckbox = false - var count_included = 0 - var count_visible = 0 + this.node_count = 0 Object.keys(this.node_blocks).forEach((node_id) => { const node_block = this.node_blocks[node_id] if (NodeInclusionManager.include_node(node_block.node)) { - if (GroupManager.is_node_in(this.settings.group_choice, node_id) || NodeInclusionManager.node_in_all_views(node_block.node)) { - count_included += 1 + var show = false; + if (GroupManager.is_node_in(this.settings.group_choice, node_id)) { + this.node_count += 1 if (NodeInclusionManager.advanced_only(node_block.node)) { this.showAdvancedCheckbox = true if (this.settings.advanced) { - node_block.classList.remove('hidden') - count_visible += 1 - } else node_block.classList.add('hidden') + show = true; + } } else { - node_block.classList.remove('hidden') - count_visible += 1 + show = true; } - } else { - node_block.classList.add('hidden') - } + } + classSet(node_block, 'hidden', (!show)) } }) - return { "nodes":count_included, "visible_nodes":count_visible } } check_dimensions() { @@ -420,7 +425,7 @@ export class ControllerPanel extends HTMLDivElement { } else { this.add_group_button = create('i', 'pi pi-plus header_button last', this.header1_left) this.remove_group_button = create('i', 'pi pi-trash header_button', this.header2_left) - //this.bypass_group_button = create('i', 'pi pi-ban header_button', this.header2_left) + this.bypass_group_button = create('i', 'pi pi-ban header_button', this.header2_left) this.show_advanced_button = create('i', `pi pi-bolt header_button${this.settings.advanced ? " clicked":""}`, this.header2_left) this.minimise_button = create("i", `pi pi-minus header_button collapse_button`, this.header1_right) this.delete_button = create('i', 'pi pi-times header_button', this.header1_right) @@ -437,9 +442,9 @@ export class ControllerPanel extends HTMLDivElement { app.graph._nodes.forEach( (n) => {this.consider_adding_node(n)} ) if (this.new_node_id_list.length>0) this.settings.node_order = this.new_node_id_list - const node_count = this.set_node_visibility() + this.set_node_visibility() - if (node_count.nodes == 0) { + if (this.node_count == 0) { const EMPTY_MESSAGE = "

Add nodes to the controller
by right-clicking the node
and using the Controller Panel submenu

" create('span', 'empty_message', this._main, {"innerHTML":EMPTY_MESSAGE}) @@ -480,10 +485,13 @@ export class ControllerPanel extends HTMLDivElement { 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; - UpdateController.make_request('uncollapse') + UpdateController.make_single_request('uncollapse', this) } else { + if (tab.only_tab) { + return + } this.settings.group_choice = nm - UpdateController.make_request('group selection changed') + UpdateController.make_single_request('uncollapse', this) } this.mouse_up() e.preventDefault() @@ -491,6 +499,14 @@ 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() + this.show_group_select(e, true) + }) + } }) } @@ -513,7 +529,7 @@ export class ControllerPanel extends HTMLDivElement { if (this.settings.groups.length==0) { this.delete_controller() } - UpdateController.make_request('group removed') + UpdateController.make_single_request('group removed', this) }) add_tooltip(this.remove_group_button, 'Remove active group tab', 'right') } @@ -525,6 +541,10 @@ export class ControllerPanel extends HTMLDivElement { alert("yet to be implemented") }) add_tooltip(this.bypass_group_button, `Toggle group bypass`, 'right') + const bypass = GroupManager.bypassed(this.settings.group_choice) + classSet(this.bypass_group_button, 'all_bypassed', bypass.all) + classSet(this.bypass_group_button, 'some_bypassed', bypass.any && !bypass.all) + classSet(this.bypass_group_button, 'none_bypassed', !bypass.any) } if (this.show_advanced_button) { @@ -542,8 +562,9 @@ export class ControllerPanel extends HTMLDivElement { e.preventDefault(); e.stopPropagation(); this.settings.collapsed = (!this.settings.collapsed) - UpdateController.make_request('collapse') + UpdateController.make_single_request('collapse', this) }) + classSet(this.minimise_button, 'hidden', this.settings.collapsed) } if (this.delete_button) { @@ -559,16 +580,20 @@ export class ControllerPanel extends HTMLDivElement { this.settings.node_order = node_id_list } - show_group_select(e) { + show_group_select(e, replace) { const the_select = create('span','group_add_select', document.body) this.groups_not_included.forEach((g)=>{ const the_choice = create('div', 'group_add_option', the_select, {"innerText":g}) the_choice.style.backgroundColor = GroupManager.group_color(g) - the_choice.addEventListener('click', (e)=>{ - this.settings.groups.push(g) + the_choice.addEventListener('click', (e)=> { + if (replace) { + this.settings.groups = [g,] + } else { + this.settings.groups.push(g) + } this.settings.group_choice = g the_select.remove() - UpdateController.make_request('group tab added') + UpdateController.make_single_request('group tab added', this) }) }) the_select.addEventListener('mouseleave', (e)=>{the_select.remove()}) diff --git a/js/groups.js b/js/groups.js index 74ec11e..b6e7d90 100644 --- a/js/groups.js +++ b/js/groups.js @@ -5,12 +5,12 @@ import { Colors, Texts } from "./constants.js" export class GroupManager { static instance = null constructor() { - this.groups = {} + this.groups = {} // maps group name to Set of node ids const ungrouped = new Set() app.graph._nodes.forEach((node)=>{ if (NodeInclusionManager.node_includable(node)) ungrouped.add(node.id) }) - this.colors = {} + this.colors = {} // maps group name to color app.graph._groups.forEach((group) => { if (!group.graph) { group.graph = app.graph @@ -27,13 +27,13 @@ export class GroupManager { } }) }) - this.groups[Texts.UNGROUPED] = ungrouped + if (ungrouped.length>0) this.groups[Texts.UNGROUPED] = ungrouped } static setup() { GroupManager.instance = new GroupManager() } static list_group_names() { - const names = [Texts.ALL_GROUPS] + const names = [Texts.ALL_GROUPS,] Object.keys(GroupManager.instance.groups).forEach((gp) => {names.push(gp)}) return names } @@ -42,6 +42,20 @@ export class GroupManager { return GroupManager.instance.colors[group_name] ?? Colors.DARK_BACKGROUND } + static bypassed(group_name) { + var any = false + var all = true + app.graph._groups.forEach((group) => { + if (group.title == group_name) { + group._nodes.forEach((node) => { + any = any || (node.mode!=0) + all = all && (node.mode!=0) + }) + } + }) + return {"any":any, "all":all} + } + static is_node_in(group_name, node_id) { if (group_name==Texts.ALL_GROUPS) return true return (GroupManager.instance.groups?.[group_name] && GroupManager.instance.groups[group_name].has(parseInt(node_id))) diff --git a/js/node_inclusion.js b/js/node_inclusion.js index 609086d..9adb8cd 100644 --- a/js/node_inclusion.js +++ b/js/node_inclusion.js @@ -1,37 +1,23 @@ import { get_node } from "./utilities.js"; import { app } from "../../scripts/app.js" +import { InclusionOptions, Texts, Colors } from "./constants.js"; export class NodeInclusionManager { - static EXCLUDE = "Don't include this node" - static INCLUDE = "Include this node" - static ALWAYS = "Include this node in all group views" - static ADVANCED = "Include this node as advanced control" - - static EXCLUDES = NodeInclusionManager.EXCLUDE.replace('this node', 'these nodes') - static INCLUDES = NodeInclusionManager.INCLUDE.replace('this node', 'these nodes') - static ALWAYSS = NodeInclusionManager.ALWAYS.replace('this node', 'these nodes') - static ADVANCEDS = NodeInclusionManager.ADVANCED.replace('this node', 'these nodes') - static node_change_callback = null static node_includable(node_or_node_id) { const nd = get_node(node_or_node_id) - return (nd && nd.properties["controller"] && nd.properties["controller"]!=NodeInclusionManager.EXCLUDE) - } - - static node_in_all_views(node_or_node_id){ - const nd = get_node(node_or_node_id) - return (nd && nd.properties["controller"] && nd.properties["controller"]==NodeInclusionManager.ALWAYS) + return (nd && nd.properties["controller"] && nd.properties["controller"]!=InclusionOptions.EXCLUDE) } static include_node(node_or_node_id) { const nd = get_node(node_or_node_id) - return (nd && nd.properties["controller"] && nd.properties["controller"]!=NodeInclusionManager.EXCLUDE && nd.mode == 0) + return (nd && nd.properties["controller"] && nd.properties["controller"]!=InclusionOptions.EXCLUDE) } static advanced_only(node_or_node_id) { const nd = get_node(node_or_node_id) - return (nd && nd.properties["controller"] && nd.properties["controller"]==NodeInclusionManager.ADVANCED && nd.mode == 0) + return (nd && nd.properties["controller"] && nd.properties["controller"]==InclusionOptions.ADVANCED) } static visual(ctx, node) { @@ -59,23 +45,22 @@ function selected_nodes() { } function cp_callback_submenu(value, options, e, menu, node) { - const current = node.properties["controller"] ?? NodeInclusionManager.EXCLUDE; + const current = node.properties["controller"] ?? InclusionOptions.EXCLUDE; const selection = selected_nodes() const choices = (selection.length==1) ? - [NodeInclusionManager.EXCLUDE, NodeInclusionManager.INCLUDE, NodeInclusionManager.ALWAYS, NodeInclusionManager.ADVANCED] : - [NodeInclusionManager.EXCLUDES, NodeInclusionManager.INCLUDES, NodeInclusionManager.ALWAYSS, NodeInclusionManager.ADVANCEDS] + [InclusionOptions.EXCLUDE, InclusionOptions.INCLUDE, InclusionOptions.ADVANCED] : + [InclusionOptions.EXCLUDES, InclusionOptions.INCLUDES, InclusionOptions.ADVANCEDS] const submenu = new LiteGraph.ContextMenu( choices, { event: e, callback: function (v) { selection.forEach((nd)=>{nd.properties["controller"] = v.replace('these nodes', 'this node')}) - //node.properties["controller"] = v; NodeInclusionManager.node_change_callback?.(); app.canvas.setDirty(true, true) }, parentMenu: menu, node:node} ) Array.from(submenu.root.children).forEach(child => { - if (child.innerText == current) child.style.borderLeft = "2px solid #C08080"; + if (child.innerText == current) child.style.borderLeft = `2px solid ${Colors.MENU_HIGHLIGHT}`; }); } @@ -83,11 +68,10 @@ export function add_control_panel_options(options) { if (options[options.length-1] != null) options.push(null); options.push( { - content: "Controller Panel", + content: Texts.CONTEXT_MENU, has_submenu: true, callback: cp_callback_submenu, } ) - //options.push(null); } diff --git a/js/nodeblock.js b/js/nodeblock.js index 352244c..2656f18 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -24,11 +24,21 @@ export class NodeBlock extends HTMLSpanElement { this.node.properties.controller_widgets = {} } this.classList.add("nodeblock") + this.bypassed = (this.node.mode!=0) + if (this.bypassed) { + create('span', 'bypass_overlay', this) + } + classSet(this, 'bypassed', this.bypassed) this.main = create("span",null,this) this.build_nodeblock() this.add_block_drag_handlers() } + can_reuse() { + if (this.bypassed != (this.node.mode!=0)) return false + return true + } + add_block_drag_handlers() { this.addEventListener('dragover', function (e) { NodeBlock.drag_over_me(e) } ) this.addEventListener('drop', function (e) { NodeBlock.drop_on_me(e) } ) diff --git a/js/update_controller.js b/js/update_controller.js index c91194b..e1a1d33 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -21,15 +21,18 @@ export class UpdateController { static push_pause() { UpdateController.pause_stack += 1 } static pop_pause() { UpdateController.pause_stack -= 1 } - static make_request(label, after_ms, noretry) { + static make_single_request(label, controller) { + UpdateController.make_request(label, null, null, controller) + } + static make_request(label, after_ms, noretry, controller) { if (after_ms) { if (label) Debug.extended(`${label} made request`) - setTimeout(UpdateController.make_request, after_ms, label, null, noretry) + setTimeout(UpdateController.make_request, after_ms, label, null, noretry, controller) } else { - const wait_time = UpdateController.pause_stack>0 ? Timings.PAUSE_STACK_WAIT : UpdateController.permission() + const wait_time = UpdateController.pause_stack>0 ? Timings.PAUSE_STACK_WAIT : UpdateController.permission(controller) if (wait_time == 0) { - UpdateController.callback() + UpdateController.callback(controller) return } @@ -44,15 +47,15 @@ export class UpdateController { Debug.extended(`${label} not trying again because ${reason_not_to_try_again}`) } else { UpdateController.requesting = true - setTimeout( UpdateController.deferred_request, wait_time, label) + setTimeout( UpdateController.deferred_request, wait_time, label, controller) } } } - static deferred_request(label) { + static deferred_request(label, controller) { UpdateController.requesting = false - UpdateController.make_request(label) + UpdateController.make_request(label, null, null, controller) } } \ No newline at end of file From 7ac5c144dcfac687c59da0b0d1175adce9dba627 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 23:24:52 +1100 Subject: [PATCH 09/27] 263 --- js/nodeblock.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/js/nodeblock.js b/js/nodeblock.js index 2656f18..4632529 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -7,9 +7,15 @@ import { Entry } from "./panel_entry.js" import { make_resizable } from "./resize_manager.js"; import { WidgetChangeManager, OnExecutedManager } from "./widget_change_manager.js"; import { UpdateController } from "./update_controller.js"; +import { Debug } from "./debug.js"; function is_single_image(data) { return (data && data.items && data.items.length==1 && data.items[0].type.includes("image")) } +function isImageNode(node) { + if (node.type=="SaveImage" || node.type=="PreviewImage") return true + return false +} + export class NodeBlock extends HTMLSpanElement { /* NodeBlock represents a single node - zero or more Entry children, and zero or one images. @@ -189,6 +195,23 @@ export class NodeBlock extends HTMLSpanElement { OnExecutedManager.add_listener(this.node.id, this) + if (isImageNode(this.node)) { + const add_upstream = (nd) => { + if (nd==this.node || !isImageNode(nd)) { + OnExecutedManager.add_listener(nd.id, this) + //Debug.trivia(`${this.node.id} listening to ${nd.id}`) + nd.inputs.forEach((i)=>{ + if (i.type=="IMAGE" || i.type=="LATENT") { + const lk = i.link + const upstream_id = lk ? app.graph.links[lk]?.origin_id : null + if (upstream_id) add_upstream(app.graph._nodes_by_id[upstream_id]) + } + }) + } + } + add_upstream(this.node) + } + this.replaceChild(new_main, this.main) this.main = new_main From 40ed64602bdd2afa0c4e9a8aeaf18f28a7e5a133 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 23:29:23 +1100 Subject: [PATCH 10/27] 265 --- js/controller_panel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/controller_panel.js b/js/controller_panel.js index dd3b8fb..cafa97d 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -480,6 +480,7 @@ export class ControllerPanel extends HTMLDivElement { this.mouse_down_at_x = e.x this.mouse_down_at_y = e.y this.mouse_down_on = tab + if (document.activeElement) document.activeElement.blur() }) 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) { From af153d31cc5ef2b8a499512b29ff0d2641f5c11b Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 2 Nov 2024 23:37:54 +1100 Subject: [PATCH 11/27] 238 --- js/controller.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/controller.css b/js/controller.css index 6f91178..159222b 100644 --- a/js/controller.css +++ b/js/controller.css @@ -268,7 +268,7 @@ } .titlebar_nocolor { - border-bottom: 2px solid var(--second-back-color); + border-bottom: 1px solid var(--second-back-color); padding-bottom: 1px; background-color: var(--third-back-color) } From 4b94691c6481c4379447cb8e63a606042a1acd2f Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 3 Nov 2024 14:34:36 +1100 Subject: [PATCH 12/27] 237 --- js/groups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/groups.js b/js/groups.js index b6e7d90..13fbc60 100644 --- a/js/groups.js +++ b/js/groups.js @@ -27,7 +27,7 @@ export class GroupManager { } }) }) - if (ungrouped.length>0) this.groups[Texts.UNGROUPED] = ungrouped + if (ungrouped.size>0) this.groups[Texts.UNGROUPED] = ungrouped } static setup() { GroupManager.instance = new GroupManager() } From d899da60c746780d61cd4cfc36970ea646b76c32 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 3 Nov 2024 14:35:13 +1100 Subject: [PATCH 13/27] 263 --- js/controller.js | 11 +++--- js/image_manager.js | 72 +++++++++++++++++++++++++++++++++++++ js/nodeblock.js | 35 ++++++++---------- js/widget_change_manager.js | 39 -------------------- 4 files changed, 94 insertions(+), 63 deletions(-) create mode 100644 js/image_manager.js diff --git a/js/controller.js b/js/controller.js index eaf592e..b595379 100644 --- a/js/controller.js +++ b/js/controller.js @@ -8,7 +8,7 @@ import { add_control_panel_options, NodeInclusionManager, } from "./node_inclus import { UpdateController } from "./update_controller.js" import { Debug } from "./debug.js" import { BASE_PATH } from "./constants.js" -import { OnExecutedManager } from "./widget_change_manager.js" +import { ImageManager } from "./image_manager.js" import { global_settings } from "./settings.js" @@ -22,9 +22,12 @@ function on_setup() { }) NodeInclusionManager.node_change_callback = UpdateController.make_request api.addEventListener('graphCleared', ControllerPanel.graph_cleared) - api.addEventListener('executed', OnExecutedManager.on_executed) - api.addEventListener('executing', OnExecutedManager.on_executing) - api.addEventListener('b_preview', OnExecutedManager.on_b_preview) + + api.addEventListener('executed', ImageManager.on_executed) + api.addEventListener('execution_start', ImageManager.on_execution_start) + api.addEventListener('executing', ImageManager.on_executing) + api.addEventListener('b_preview', ImageManager.on_b_preview) + window.addEventListener("resize", ControllerPanel.onWindowResize) window.addEventListener('mouseup', ControllerPanel.mouse_up_anywhere) window.addEventListener('mousemove', ControllerPanel.mouse_move_anywhere) diff --git a/js/image_manager.js b/js/image_manager.js new file mode 100644 index 0000000..3b3fafa --- /dev/null +++ b/js/image_manager.js @@ -0,0 +1,72 @@ +import { api } from "../../scripts/api.js"; +import { Debug } from "./debug.js"; + +export class ImageManager { + /* + node_listener_map is a map from node_id to a Set of listeners. + Listeners must have the method manage_image(url) which returns + false if the listener is no longer interested + */ + + static node_listener_map = {} // map to Set + static node_src_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) + } + + static _send(node_id) { + const src = ImageManager.node_src_map[node_id] + if (src) { + if (ImageManager.node_listener_map[node_id]) { + Array.from(ImageManager.node_listener_map[node_id]).forEach((l)=>{ + if (!l.manage_image(src)) ImageManager.node_listener_map[node_id].delete(node_id) + }) + } + } + } + + static _set_source(node_id, src) { + ImageManager.node_src_map[node_id] = src + ImageManager._send(node_id) + } + + /* called by a nodeblock if it has an imgs value. If we're running, we ignore this */ + static node_has_img(node_id, v) { + if (ImageManager.executing_node==null) { + 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) + } + } + + static on_execution_start() { + ImageManager.node_src_map = {} + } + + static on_executing(e) { + ImageManager.executing_node = e.detail + } + + static on_b_preview(e) { + Debug.trivia(`${ImageManager.executing_node} on_b_preview`) + ImageManager._set_source( ImageManager.executing_node, window.URL.createObjectURL(e.detail) ) + } + + static on_executed(e) { + Debug.trivia(`${e.detail.node} on_executed`) + 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 ?? ""}` + ) ) + } + } + + +} \ No newline at end of file diff --git a/js/nodeblock.js b/js/nodeblock.js index 4632529..7602f32 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -1,11 +1,11 @@ import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; + import { ComfyWidgets } from "../../scripts/widgets.js"; import { create, darken, classSet } from "./utilities.js"; import { Entry } from "./panel_entry.js" import { make_resizable } from "./resize_manager.js"; -import { WidgetChangeManager, OnExecutedManager } from "./widget_change_manager.js"; +import { ImageManager } from "./image_manager.js"; import { UpdateController } from "./update_controller.js"; import { Debug } from "./debug.js"; @@ -193,12 +193,10 @@ export class NodeBlock extends HTMLSpanElement { make_resizable( this.image_panel, this.node.id, "__image_panel", this.node.properties.controller_widgets['__image_panel'] ) new ResizeObserver(this.rescale_image.bind(this)).observe(this.image_panel) - OnExecutedManager.add_listener(this.node.id, this) - if (isImageNode(this.node)) { const add_upstream = (nd) => { if (nd==this.node || !isImageNode(nd)) { - OnExecutedManager.add_listener(nd.id, this) + ImageManager.add_listener(nd.id, this) //Debug.trivia(`${this.node.id} listening to ${nd.id}`) nd.inputs.forEach((i)=>{ if (i.type=="IMAGE" || i.type=="LATENT") { @@ -211,21 +209,21 @@ export class NodeBlock extends HTMLSpanElement { } add_upstream(this.node) } + ImageManager.add_listener(this.node.id, this) // add ourself last to take priority this.replaceChild(new_main, this.main) this.main = new_main - if (this.node.imgs) this.show_image(this.node.imgs) - else OnExecutedManager.resend(this.node.id) - + if (this.node.imgs && this.node.imgs.length>0) { + ImageManager.node_has_img(this.node.id, this.node.imgs[0]) + } + this.valid_nodeblock = true } - oem_manager_callback(o) { + manage_image(url) { if (!this.parentElement) return false - if (o && o.images && !this.hidden) { - this.show_image(o.images) - } + this.show_image(url) return true } @@ -262,14 +260,11 @@ export class NodeBlock extends HTMLSpanElement { return ( this.node.pasteFile != undefined ) } - show_image(v) { - classSet(this.image_panel, 'nodeblock_image_empty', !(v?.length>0)) - if (v.length==0) return - var src = v[0].src ?? api.apiURL( - `/view?filename=${encodeURIComponent(v[0].filename ?? v)}&type=${v[0].type ?? "input"}&subfolder=${v[0].subfolder ?? ""}` - ) - if (this.image_image.src != src) { - this.image_image.src = src + show_image(url) { + classSet(this.image_panel, 'nodeblock_image_empty', !url) + + if (this.image_image.src != url) { + this.image_image.src = url this.image_panel.style.maxHeight = '' } } diff --git a/js/widget_change_manager.js b/js/widget_change_manager.js index 0784b5e..5aed22d 100644 --- a/js/widget_change_manager.js +++ b/js/widget_change_manager.js @@ -25,42 +25,3 @@ export class WidgetChangeManager { WidgetChangeManager.notify(widget) } } - -export class OnExecutedManager { - static node_listener_map = {} - static add_listener(node_id, listener) { - if (!OnExecutedManager.node_listener_map[node_id]) OnExecutedManager.node_listener_map[node_id] = new Set() - OnExecutedManager.node_listener_map[node_id].add(listener) - } - - static send(node_id, output) { - if (OnExecutedManager.node_listener_map[node_id]) { - Array.from(OnExecutedManager.node_listener_map[node_id]).forEach((l)=>{ - if (!l.oem_manager_callback(output)) OnExecutedManager.node_listener_map[node_id].delete(node_id) - }) - } - } - - static last_output_map = {} - static on_executed(e) { - const node_id = e.detail.node - const output = e.detail.output - OnExecutedManager.last_output_map[node_id] = output - OnExecutedManager.send(node_id, output) - } - - static resend(node_id) { - if (OnExecutedManager.last_output_map[node_id]) OnExecutedManager.send(node_id,OnExecutedManager.last_output_map[node_id] ) - } - - static executing_node = null - static on_executing(e) { - OnExecutedManager.executing_node = e.detail - } - - static on_b_preview(e) { - const node_id = OnExecutedManager.executing_node - const output = {"images":[{"src":window.URL.createObjectURL(e.detail)}]} - OnExecutedManager.send(node_id, output) - } -} \ No newline at end of file From db9008910295b2554144b10c2b464fdb6f49d51a Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 3 Nov 2024 14:39:01 +1100 Subject: [PATCH 14/27] bypass logic --- js/controller_panel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/controller_panel.js b/js/controller_panel.js index cafa97d..1f49bdd 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -473,7 +473,7 @@ export class ControllerPanel extends HTMLDivElement { add_tabs() { this.settings.groups.forEach((nm) => { - const tab = create('span','tab',this.header1_left,{"innerText":nm}) + const tab = create('span','tab',this.header1_left,{"innerHTML":nm.replaceAll(' ',' ')}) classSet(tab,'selected',(this.settings.group_choice == nm)) tab.style.setProperty('--base-color', GroupManager.group_color(nm)) tab.addEventListener('mousedown', (e) => { @@ -543,7 +543,7 @@ export class ControllerPanel extends HTMLDivElement { }) add_tooltip(this.bypass_group_button, `Toggle group bypass`, 'right') const bypass = GroupManager.bypassed(this.settings.group_choice) - classSet(this.bypass_group_button, 'all_bypassed', bypass.all) + classSet(this.bypass_group_button, 'all_bypassed', bypass.any && bypass.all) classSet(this.bypass_group_button, 'some_bypassed', bypass.any && !bypass.all) classSet(this.bypass_group_button, 'none_bypassed', !bypass.any) } From fbc73a488b6ad389b5a7293c45c9b0464985bb18 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 3 Nov 2024 15:00:55 +1100 Subject: [PATCH 15/27] 269 --- js/controller.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/controller.css b/js/controller.css index 159222b..086b496 100644 --- a/js/controller.css +++ b/js/controller.css @@ -128,7 +128,7 @@ display: flex; justify-content: flex-start; flex-direction: row; - + align-items: center; /* allow the group to shirnk */ min-width: 0px; flex-shrink: 1; @@ -167,6 +167,8 @@ .collapsed .tab { display: none; + margin-bottom: 0px; + margin-left:4px; } .collapsed .tab.selected { display: block; From acda880aedc1e15895b24cd9e864865f97a8b64d Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 3 Nov 2024 15:06:17 +1100 Subject: [PATCH 16/27] mute color of collapsed nodeblock --- js/controller.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/controller.css b/js/controller.css index 086b496..a74da8f 100644 --- a/js/controller.css +++ b/js/controller.css @@ -257,6 +257,10 @@ color: var(--mid-fore-color); } +.minimised .nodeblock_titlebar { + color: var(--muted-fore-color); +} + .minimisedot { padding: 0px 4px 0px 0px; cursor: url(collapse.png) 8 8, zoom-out; From d56dae0e43ba03f58415d9d158e280ed1756c727 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 3 Nov 2024 23:34:54 +1100 Subject: [PATCH 17/27] image upload --- js/controller.js | 5 ++++- js/controller_panel.js | 1 + js/image_manager.js | 15 ++++++++++++--- js/nodeblock.js | 28 ++++++++++------------------ js/update_controller.js | 5 ++--- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/js/controller.js b/js/controller.js index b595379..4870b9d 100644 --- a/js/controller.js +++ b/js/controller.js @@ -128,7 +128,10 @@ app.registerExtension({ node._imgs = node.imgs Object.defineProperty(node, 'imgs', { get: () => { return node._imgs }, - set: (v) => { node._imgs = v; UpdateController.make_request('imgs', 100)} + set: (v) => { + node._imgs = v; + if (v && v.length>0) ImageManager.node_has_img(node, v[0]) + } }) }, diff --git a/js/controller_panel.js b/js/controller_panel.js index 1f49bdd..159b212 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -309,6 +309,7 @@ export class ControllerPanel extends HTMLDivElement { } } classSet(node_block, 'hidden', (!show)) + node_block.is_hidden = (!show) } }) } diff --git a/js/image_manager.js b/js/image_manager.js index 3b3fafa..241abab 100644 --- a/js/image_manager.js +++ b/js/image_manager.js @@ -1,6 +1,15 @@ import { api } from "../../scripts/api.js"; import { Debug } from "./debug.js"; +export function is_image_upload_node(node) { + return ( node?.pasteFile != undefined ) +} + +export function isImageNode(node) { + if (node.type=="SaveImage" || node.type=="PreviewImage") return true + return false +} + export class ImageManager { /* node_listener_map is a map from node_id to a Set of listeners. @@ -36,12 +45,12 @@ export class ImageManager { } /* called by a nodeblock if it has an imgs value. If we're running, we ignore this */ - static node_has_img(node_id, v) { - if (ImageManager.executing_node==null) { + static node_has_img(node, v) { + 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) + ImageManager._set_source(node.id, src) } } diff --git a/js/nodeblock.js b/js/nodeblock.js index 7602f32..2062dc3 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -5,17 +5,12 @@ import { ComfyWidgets } from "../../scripts/widgets.js"; import { create, darken, classSet } from "./utilities.js"; import { Entry } from "./panel_entry.js" import { make_resizable } from "./resize_manager.js"; -import { ImageManager } from "./image_manager.js"; +import { ImageManager, is_image_upload_node, isImageNode } from "./image_manager.js"; import { UpdateController } from "./update_controller.js"; import { Debug } from "./debug.js"; function is_single_image(data) { return (data && data.items && data.items.length==1 && data.items[0].type.includes("image")) } -function isImageNode(node) { - if (node.type=="SaveImage" || node.type=="PreviewImage") return true - return false -} - export class NodeBlock extends HTMLSpanElement { /* NodeBlock represents a single node - zero or more Entry children, and zero or one images. @@ -90,7 +85,7 @@ export class NodeBlock extends HTMLSpanElement { } if (e.dataTransfer.types.includes('Files')) { - if (nodeblock_over?.is_image_upload_node?.() && is_single_image(e.dataTransfer)) { + if (is_image_upload_node(nodeblock_over?.node) && is_single_image(e.dataTransfer)) { e.dataTransfer.dropEffect = "move" e.stopPropagation() } else { @@ -104,7 +99,7 @@ export class NodeBlock extends HTMLSpanElement { if (NodeBlock.dragged) { e.preventDefault(); } else if (e.dataTransfer.types.includes('Files')) { - if (e.currentTarget.is_image_upload_node?.() && is_single_image(e.dataTransfer)) { + if (is_image_upload_node(e.currentTarget?.node) && is_single_image(e.dataTransfer)) { const node = e.currentTarget.node e.preventDefault(); e.stopImmediatePropagation() @@ -171,6 +166,9 @@ 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.valid_nodeblock = false this.node.widgets?.forEach(w => { if (!this.node.properties.controller_widgets[w.name]) this.node.properties.controller_widgets[w.name] = {} @@ -183,10 +181,8 @@ export class NodeBlock extends HTMLSpanElement { } }) - if (this.image_panel) this.image_panel.remove() - if (!this.node.properties.controller_widgets['__image_panel']) this.node.properties.controller_widgets['__image_panel'] = {} - this.image_panel = create("div", "nodeblock_image_panel nodeblock_image_empty", new_main) + this.image_image = create('img', 'nodeblock_image', this.image_panel) this.image_image.addEventListener('load', this.rescale_image.bind(this)) @@ -195,7 +191,7 @@ export class NodeBlock extends HTMLSpanElement { if (isImageNode(this.node)) { const add_upstream = (nd) => { - if (nd==this.node || !isImageNode(nd)) { + if (nd==this.node || (!isImageNode(nd) && !is_image_upload_node(nd))) { ImageManager.add_listener(nd.id, this) //Debug.trivia(`${this.node.id} listening to ${nd.id}`) nd.inputs.forEach((i)=>{ @@ -215,7 +211,7 @@ export class NodeBlock extends HTMLSpanElement { this.main = new_main if (this.node.imgs && this.node.imgs.length>0) { - ImageManager.node_has_img(this.node.id, this.node.imgs[0]) + ImageManager.node_has_img(this.node, this.node.imgs[0]) } this.valid_nodeblock = true @@ -223,7 +219,7 @@ export class NodeBlock extends HTMLSpanElement { manage_image(url) { if (!this.parentElement) return false - this.show_image(url) + if (!(this.bypassed || this.hidden)) this.show_image(url) return true } @@ -256,10 +252,6 @@ export class NodeBlock extends HTMLSpanElement { this.rescaling = false } - is_image_upload_node() { - return ( this.node.pasteFile != undefined ) - } - show_image(url) { classSet(this.image_panel, 'nodeblock_image_empty', !url) diff --git a/js/update_controller.js b/js/update_controller.js index e1a1d33..16daa1a 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -26,18 +26,17 @@ export class UpdateController { } static make_request(label, after_ms, noretry, controller) { if (after_ms) { - if (label) Debug.extended(`${label} made request`) setTimeout(UpdateController.make_request, after_ms, label, null, noretry, controller) } else { + const wait_time = UpdateController.pause_stack>0 ? Timings.PAUSE_STACK_WAIT : UpdateController.permission(controller) + if (label) Debug.extended(`${label} made update request and got ${wait_time}`) if (wait_time == 0) { UpdateController.callback(controller) return } - if (label) Debug.extended(`${label} got asked to wait ${wait_time}`) - var reason_not_to_try_again = null if (wait_time < 0) reason_not_to_try_again = "delay was negative" if (noretry) reason_not_to_try_again = "noretry was set" From bd068775c373af70c260223c062e43794de8d5e0 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 09:43:16 +1100 Subject: [PATCH 18/27] docs --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 635116d..620906f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ To create a Controller window, right click on the background and select "New Con At first, the Controller will be empty. To add nodes to it, right-click on the node and in the Controller Panel menu select "Include this node". +The button next to the Controller toggle is the refresh. Not all changes in the workflow are automatically picked up by the controller, +so sometimes after you make a change you will need to click this. + ![menu](images/menu.png) When you include a node, it appears on the Controller panel, and it also gains a coloured dot in the top-right hand corner, From 0e16628751a756c5049c2bd8ba6531264ec3f81a Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 10:48:01 +1100 Subject: [PATCH 19/27] 278 --- js/controller.js | 3 ++- js/image_manager.js | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/js/controller.js b/js/controller.js index 4870b9d..8f8a3f3 100644 --- a/js/controller.js +++ b/js/controller.js @@ -58,8 +58,9 @@ app.registerExtension({ async afterConfigureGraph() { /* This is now just for backward compatibility - we *remove* the ControllerNode and put the data in app.graph.extras */ //CGControllerNode.remove() - + ImageManager.init() ControllerPanel.new_workflow() + }, /* Called at the end of the application startup */ diff --git a/js/image_manager.js b/js/image_manager.js index 241abab..4656e76 100644 --- a/js/image_manager.js +++ b/js/image_manager.js @@ -17,6 +17,12 @@ export class ImageManager { false if the listener is no longer interested */ + static init() { + ImageManager.node_listener_map = {} + ImageManager.node_src_map = {} + ImageManager.executing_node = null + } + static node_listener_map = {} // map to Set static node_src_map = {} // map to url static executing_node = null From d040d8f5cd34b6e3ab5a8d6dae89fba9db8d6dd1 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 10:48:05 +1100 Subject: [PATCH 20/27] 1.3 --- __init__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index bee5962..07002aa 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -VERSION = "1.2" +VERSION = "1.3" WEB_DIRECTORY = "./js" NODE_CLASS_MAPPINGS= {} diff --git a/pyproject.toml b/pyproject.toml index 5a0d5d3..281f3e0 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.2" +version = "1.3" license = {file = "LICENSE"} [project.urls] From 25865eab1c3d1f730656e77060e6672a70858bb8 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 11:32:22 +1100 Subject: [PATCH 21/27] allow smaller --- js/controller.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/controller.css b/js/controller.css index a74da8f..29118df 100644 --- a/js/controller.css +++ b/js/controller.css @@ -47,8 +47,7 @@ background: var(--main-back-color); padding: 0 0 0 2px; position: absolute; - - min-width: 215px; + min-width: 130px; overflow: clip scroll; border-width: 4px; border-style: solid; From 35f6bdfec8f8105ccbd04bbc773b7d36db878b60 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 11:33:48 +1100 Subject: [PATCH 22/27] 264 --- js/controller_panel.js | 9 +++++---- js/nodeblock.js | 15 +++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/js/controller_panel.js b/js/controller_panel.js index 159b212..d60574e 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -211,7 +211,6 @@ export class ControllerPanel extends HTMLDivElement { _can_refresh() { try { - //if (!this.showing) { return -1 } if (this.classList.contains('unrefreshable')) { Debug.trivia("already refreshing"); return -1 } if (this.contains(document.activeElement) && !document.activeElement.doesntBlockRefresh) { Debug.trivia("delay refresh because active element"); return 1 } @@ -307,9 +306,11 @@ export class ControllerPanel extends HTMLDivElement { } else { show = true; } - } - classSet(node_block, 'hidden', (!show)) - node_block.is_hidden = (!show) + classSet(node_block, 'hidden', (!show)) + node_block.is_hidden = (!show) + } else { + node_block.remove() + } } }) } diff --git a/js/nodeblock.js b/js/nodeblock.js index 2062dc3..14ba374 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -181,12 +181,19 @@ export class NodeBlock extends HTMLSpanElement { } }) - if (!this.node.properties.controller_widgets['__image_panel']) this.node.properties.controller_widgets['__image_panel'] = {} - + this.image_panel_id = `__image_panel.${this.parent_controller.settings.index}` + + if (!this.node.properties.controller_widgets[this.image_panel_id]) { + this.node.properties.controller_widgets[this.image_panel_id] = {} + if (this.node.properties.controller_widgets['__image_panel']) { // backward compatibility - get the size from the per-node version + Object.assign(this.node.properties.controller_widgets[this.image_panel_id], this.node.properties.controller_widgets['__image_panel']) + delete this.node.properties.controller_widgets['__image_panel'] + } + } this.image_image = create('img', 'nodeblock_image', this.image_panel) this.image_image.addEventListener('load', this.rescale_image.bind(this)) - make_resizable( this.image_panel, this.node.id, "__image_panel", this.node.properties.controller_widgets['__image_panel'] ) + make_resizable( this.image_panel, this.node.id, this.image_panel_id, this.node.properties.controller_widgets[this.image_panel_id] ) new ResizeObserver(this.rescale_image.bind(this)).observe(this.image_panel) if (isImageNode(this.node)) { @@ -230,7 +237,7 @@ export class NodeBlock extends HTMLSpanElement { if (this.image_image) { const box = this.image_panel.getBoundingClientRect() if (box.width) { - this.node.properties.controller_widgets['__image_panel'].height = box.height + this.node.properties.controller_widgets[this.image_panel_id].height = box.height const w = box.width - 8 const im_h = this.image_image?.naturalHeight const im_w = this.image_image?.naturalWidth From d756a0fed2e1d2032d484644423de689ce9844a4 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 11:53:34 +1100 Subject: [PATCH 23/27] 252 --- js/input_slider.js | 20 ++++++++++++++++---- js/slider.css | 9 +++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/js/input_slider.js b/js/input_slider.js index 0d43e46..1782212 100644 --- a/js/input_slider.js +++ b/js/input_slider.js @@ -8,7 +8,9 @@ import { WidgetChangeManager } from "./widget_change_manager.js"; function copy_to_widget(node, widget, options) { const opt = { - "min":options.min, "max":options.max, "round":options.round, "precision":options.precision, "step":options.step + "min":options.min, "max":options.max, + "round":options.round, "precision":options.precision, "step":options.step, + "org_min":options.org_min, "org_max":options.org_max } Object.assign(widget.options, opt) widget.options.step *= 10 // clicking the arrows moves a widget by 0.1 * step ???? @@ -17,7 +19,7 @@ function copy_to_widget(node, widget, options) { } class SliderOptions { - static KEYS = [ "min", "max", "step", "precision", "round" ] + static KEYS = [ "min", "max", "step", "precision", "round", "org_min", "org_max" ] constructor(widget_options, saved_values, is_integer) { const options = {} Object.assign(options, widget_options) @@ -27,6 +29,8 @@ class SliderOptions { this.max = options.max this.precision = options.precision this._step = null + this.org_min = options.org_min ?? options.min + this.org_max = options.org_max ?? options.max Object.defineProperty(this, "step", { get : () => { return this._step }, set : (v) => { @@ -142,8 +146,16 @@ class SliderOptionEditor extends HTMLSpanElement { step_bad = step_bad || (parseFloat(this.step_edit.value) != parseInt(this.step_edit.value)) } - classSet(this.min_edit, 'bad_value', (min_bad || both_bad)) - classSet(this.max_edit, 'bad_value', (max_bad || both_bad)) + min_bad = min_bad || both_bad + max_bad = max_bad || both_bad + + const min_danger = (!min_bad && this.min_edit.value < this.slider_options.org_min) + const max_danger = (!max_bad && this.max_edit.value > this.slider_options.org_max) + + classSet(this.min_edit, 'bad_value', min_bad) + classSet(this.max_edit, 'bad_value', max_bad) + classSet(this.min_edit, 'danger_value', min_danger) + classSet(this.max_edit, 'danger_value', max_danger) classSet(this.step_edit, 'bad_value', (step_bad)) this.button_save.disabled = (min_bad || max_bad || step_bad || diff --git a/js/slider.css b/js/slider.css index cd7ec7c..733abc4 100644 --- a/js/slider.css +++ b/js/slider.css @@ -96,6 +96,7 @@ .option_setting_input { width: 50px; font-size: 90%; + background-color: #00ff0080; } .option_setting_input.min { text-align:right; @@ -123,5 +124,9 @@ } .bad_value { - background: #ff000042; -} \ No newline at end of file + background: #ff000080; +} + +.danger_value { + background: #ff800080; +} From 5467b8c18f7f9b1544b8899cb8632afbb6cc3eff Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 13:20:37 +1100 Subject: [PATCH 24/27] greatly reduce rebuild requirements for performance --- js/controller.js | 22 +++++++-------- js/controller_panel.js | 15 ++++++++--- js/image_manager.js | 6 ++++- js/nodeblock.js | 9 ++++--- js/update_controller.js | 60 +++++++++++++++++++++++++---------------- 5 files changed, 69 insertions(+), 43 deletions(-) diff --git a/js/controller.js b/js/controller.js index 8f8a3f3..3885824 100644 --- a/js/controller.js +++ b/js/controller.js @@ -13,13 +13,7 @@ import { global_settings } from "./settings.js" function on_setup() { - UpdateController.setup(ControllerPanel.redraw, ControllerPanel.can_refresh, (node_id)=>{ - var interest = false - Object.keys(ControllerPanel.instances).forEach((k)=>{ - if (ControllerPanel.instances[k].node_blocks[node_id]) interest = true - }) - return interest - }) + UpdateController.setup(ControllerPanel.redraw, ControllerPanel.can_refresh) NodeInclusionManager.node_change_callback = UpdateController.make_request api.addEventListener('graphCleared', ControllerPanel.graph_cleared) @@ -54,13 +48,15 @@ function on_setup() { app.registerExtension({ name: "cg.controller", + async beforeConfigureGraph() { + UpdateController.configuring(true) + }, + /* Called when the graph has been configured (page load, workflow load) */ async afterConfigureGraph() { - /* This is now just for backward compatibility - we *remove* the ControllerNode and put the data in app.graph.extras */ - //CGControllerNode.remove() ImageManager.init() ControllerPanel.new_workflow() - + UpdateController.configuring(false) }, /* Called at the end of the application startup */ @@ -104,17 +100,17 @@ app.registerExtension({ const onInputRemoved = nodeType.prototype.onInputRemoved nodeType.prototype.onInputRemoved = function () { onInputRemoved?.apply(this,arguments) - UpdateController.node_change(this.id) + ControllerPanel.node_change(this.id) } const onInputAdded = nodeType.prototype.onInputAdded nodeType.prototype.onInputAdded = function () { onInputAdded?.apply(this,arguments) - UpdateController.node_change(this.id) + ControllerPanel.node_change(this.id) } const onModeChange = nodeType.prototype.onModeChange nodeType.prototype.onModeChange = function () { onModeChange?.apply(this,arguments) - UpdateController.node_change(this.id) + ControllerPanel.node_change(this.id) } }, diff --git a/js/controller_panel.js b/js/controller_panel.js index d60574e..14b23e2 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -173,7 +173,7 @@ export class ControllerPanel extends HTMLDivElement { if (c) { c.redraw() } else { - Object.keys(ControllerPanel.instances).forEach((k)=>{ControllerPanel.instances[k].redraw() }) + Object.values(ControllerPanel.instances).forEach((cp)=>{cp.redraw()}) } } @@ -199,6 +199,14 @@ export class ControllerPanel extends HTMLDivElement { } } + static node_change(node_id) { + Object.values(ControllerPanel.instances).forEach((cp)=>{cp._node_change(node_id)}) + } + + _node_change(node_id) { + if (this.node_blocks[node_id] && this.node_blocks[node_id].parentElement) UpdateController.make_single_request(`node ${node_id} changed`,this) + } + choose_suitable_initial_group() { const all_options = GroupManager.list_group_names() const all_used = new Set() @@ -270,7 +278,7 @@ export class ControllerPanel extends HTMLDivElement { if (this.new_node_id_list.includes(node_id)) return // already got it in the new list if (NodeInclusionManager.include_node(node_or_node_id)) { // is it still valid? if (this.node_blocks[node_id] && this.node_blocks[node_id].can_reuse()) { - this.node_blocks[node_id].build_nodeblock() + //this.node_blocks[node_id].build_nodeblock() do this in set_node_visibility instead } else { this.maybe_create_node_block_for_node(node_id) } @@ -308,6 +316,7 @@ export class ControllerPanel extends HTMLDivElement { } classSet(node_block, 'hidden', (!show)) node_block.is_hidden = (!show) + if (show) node_block.build_nodeblock() } else { node_block.remove() } @@ -494,7 +503,7 @@ export class ControllerPanel extends HTMLDivElement { return } this.settings.group_choice = nm - UpdateController.make_single_request('uncollapse', this) + UpdateController.make_single_request(`tab ${nm} clicked`, this) } this.mouse_up() e.preventDefault() diff --git a/js/image_manager.js b/js/image_manager.js index 4656e76..f2df804 100644 --- a/js/image_manager.js +++ b/js/image_manager.js @@ -10,6 +10,10 @@ export function isImageNode(node) { return false } +export function image_is_blob(url) { + return url.startsWith('blob') +} + export class ImageManager { /* node_listener_map is a map from node_id to a Set of listeners. @@ -39,7 +43,7 @@ export class ImageManager { if (src) { if (ImageManager.node_listener_map[node_id]) { Array.from(ImageManager.node_listener_map[node_id]).forEach((l)=>{ - if (!l.manage_image(src)) ImageManager.node_listener_map[node_id].delete(node_id) + if (!l.manage_image(src, (ImageManager.executing_node!=null))) ImageManager.node_listener_map[node_id].delete(node_id) }) } } diff --git a/js/nodeblock.js b/js/nodeblock.js index 14ba374..13a8ccb 100644 --- a/js/nodeblock.js +++ b/js/nodeblock.js @@ -5,7 +5,7 @@ import { ComfyWidgets } from "../../scripts/widgets.js"; import { create, darken, classSet } from "./utilities.js"; import { Entry } from "./panel_entry.js" import { make_resizable } from "./resize_manager.js"; -import { ImageManager, is_image_upload_node, isImageNode } from "./image_manager.js"; +import { image_is_blob, ImageManager, is_image_upload_node, isImageNode } from "./image_manager.js"; import { UpdateController } from "./update_controller.js"; import { Debug } from "./debug.js"; @@ -224,9 +224,12 @@ export class NodeBlock extends HTMLSpanElement { this.valid_nodeblock = true } - manage_image(url) { + manage_image(url, running) { if (!this.parentElement) return false - if (!(this.bypassed || this.hidden)) this.show_image(url) + 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) + } return true } diff --git a/js/update_controller.js b/js/update_controller.js index 16daa1a..8da2532 100644 --- a/js/update_controller.js +++ b/js/update_controller.js @@ -1,52 +1,66 @@ +import { app } from "../../scripts/app.js" import { Timings } from "./constants.js" import { Debug } from "./debug.js" +function message(wait_time) { + if (wait_time==0) return "" + if (wait_time==-2) return "Graph configuring" + if (wait_time<0) return "Controller refused with no retry" + return `Controller requested retry after ${wait_time}ms` +} + export class UpdateController { static callback = ()=>{} static permission = ()=>{return false} - static interest_in = (node_id)=>{return false} static pause_stack = 0 + static _configuring = false - static setup(callback, permission, interest_in) { + static setup(callback, permission) { UpdateController.callback = callback UpdateController.permission = permission - UpdateController.interest_in = interest_in - } - - - static node_change(node_id) { - if (this.interest_in(node_id)) UpdateController.make_request(`node ${node_id} changed`) } static push_pause() { UpdateController.pause_stack += 1 } static pop_pause() { UpdateController.pause_stack -= 1 } + static configuring(v) { UpdateController._configuring = v } + static make_single_request(label, controller) { UpdateController.make_request(label, null, null, controller) } static make_request(label, after_ms, noretry, controller) { + label = label ?? "" + const cont_name = controller ? `for controller ${controller.settings.index}` : `all controllers` if (after_ms) { - setTimeout(UpdateController.make_request, after_ms, label, null, noretry, controller) + if (UpdateController._configuring) { + Debug.extended(`Update ${cont_name} requested because '${label}': ignored because still configuring`) + } else { + setTimeout(UpdateController.make_request, after_ms, label, null, noretry, controller) + } } else { - - const wait_time = UpdateController.pause_stack>0 ? Timings.PAUSE_STACK_WAIT : UpdateController.permission(controller) - if (label) Debug.extended(`${label} made update request and got ${wait_time}`) + var wait_time = 0 + if (wait_time==0 && UpdateController.pause_stack>0) wait_time = Timings.PAUSE_STACK_WAIT + if (wait_time==0 && UpdateController._configuring) wait_time = -2 + if (wait_time==0) wait_time = UpdateController.permission(controller) + Debug.extended(`Update ${cont_name} requested because '${label}'. ${message(wait_time)}`) if (wait_time == 0) { + Debug.extended(`Update ${cont_name} request '${label}' sent`) UpdateController.callback(controller) return - } - - var reason_not_to_try_again = null - if (wait_time < 0) reason_not_to_try_again = "delay was negative" - if (noretry) reason_not_to_try_again = "noretry was set" - if (UpdateController.requesting) reason_not_to_try_again = "a retry is already pending" - - if (reason_not_to_try_again) { - Debug.extended(`${label} not trying again because ${reason_not_to_try_again}`) } else { - UpdateController.requesting = true - setTimeout( UpdateController.deferred_request, wait_time, label, controller) + var reason_not_to_try_again = null + if (wait_time < 0) reason_not_to_try_again = "delay was negative" + if (noretry) reason_not_to_try_again = "noretry was set" + if (UpdateController.requesting) reason_not_to_try_again = "a retry is already pending" + + if (reason_not_to_try_again) { + Debug.extended(`Update ${cont_name} request '${label}' cancelled because ${reason_not_to_try_again}`) + } else { + Debug.extended(`Update ${cont_name} request '${label}' rescheduled for ${wait_time}ms`) + UpdateController.requesting = true + setTimeout( UpdateController.deferred_request, wait_time, label, controller) + } } } From 6f9e1a4272ec8e017e3cde4168d49cb18cbfba70 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 13:26:00 +1100 Subject: [PATCH 25/27] 270 --- js/controller.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/controller.js b/js/controller.js index 3885824..60513e6 100644 --- a/js/controller.js +++ b/js/controller.js @@ -131,6 +131,15 @@ app.registerExtension({ } }) + node._mode = node._mode + Object.defineProperty(node, 'mode', { + get: () => { return node._mode }, + set: (v) => { + node._mode = v; + ControllerPanel.node_change(node.id); + } + }) + }, }) \ No newline at end of file From 469ef1ea40af6ff2d5bf97a031f882cfea4c8ba8 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 15:04:09 +1100 Subject: [PATCH 26/27] 89 --- js/controller_panel.js | 11 +++++++---- js/groups.js | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/js/controller_panel.js b/js/controller_panel.js index 14b23e2..ddcd404 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -436,7 +436,9 @@ export class ControllerPanel extends HTMLDivElement { } else { this.add_group_button = create('i', 'pi pi-plus header_button last', this.header1_left) this.remove_group_button = create('i', 'pi pi-trash header_button', this.header2_left) - this.bypass_group_button = create('i', 'pi pi-ban header_button', this.header2_left) + if (this.settings.group_choice != Texts.ALL_GROUPS && this.settings.group_choice != Texts.UNGROUPED) { + this.bypass_group_button = create('i', 'pi pi-ban header_button', this.header2_left) + } this.show_advanced_button = create('i', `pi pi-bolt header_button${this.settings.advanced ? " clicked":""}`, this.header2_left) this.minimise_button = create("i", `pi pi-minus header_button collapse_button`, this.header1_right) this.delete_button = create('i', 'pi pi-times header_button', this.header1_right) @@ -547,13 +549,14 @@ export class ControllerPanel extends HTMLDivElement { } if (this.bypass_group_button) { + const bypass = GroupManager.bypassed(this.settings.group_choice) this.bypass_group_button.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); - alert("yet to be implemented") + GroupManager.toggle_bypass(this.settings.group_choice) }) - add_tooltip(this.bypass_group_button, `Toggle group bypass`, 'right') - const bypass = GroupManager.bypassed(this.settings.group_choice) + add_tooltip(this.bypass_group_button, `${bypass.all ? 'enable' : 'bypass'} group nodes`, 'right') + classSet(this.bypass_group_button, 'all_bypassed', bypass.any && bypass.all) classSet(this.bypass_group_button, 'some_bypassed', bypass.any && !bypass.all) classSet(this.bypass_group_button, 'none_bypassed', !bypass.any) diff --git a/js/groups.js b/js/groups.js index 13fbc60..9a29369 100644 --- a/js/groups.js +++ b/js/groups.js @@ -56,6 +56,20 @@ export class GroupManager { return {"any":any, "all":all} } + static toggle_bypass(group_name) { + GroupManager.set_bypass(group_name, !(GroupManager.bypassed(group_name).all)) + } + static set_bypass(group_name, value) { + value = value ? 4 : 0 + app.graph._groups.forEach((group) => { + if (group.title == group_name) { + group._nodes.forEach((node) => { + node.mode = value + }) + } + }) + } + static is_node_in(group_name, node_id) { if (group_name==Texts.ALL_GROUPS) return true return (GroupManager.instance.groups?.[group_name] && GroupManager.instance.groups[group_name].has(parseInt(node_id))) From 8984d1101adfaafba05281b0f1bc2425aa8397df Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 4 Nov 2024 20:53:09 +1100 Subject: [PATCH 27/27] 89 --- js/controller_panel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/controller_panel.js b/js/controller_panel.js index ddcd404..636e8d8 100644 --- a/js/controller_panel.js +++ b/js/controller_panel.js @@ -554,6 +554,7 @@ export class ControllerPanel extends HTMLDivElement { e.preventDefault(); e.stopPropagation(); GroupManager.toggle_bypass(this.settings.group_choice) + app.canvas.setDirty(true,true) }) add_tooltip(this.bypass_group_button, `${bypass.all ? 'enable' : 'bypass'} group nodes`, 'right')