File size: 14,096 Bytes
baa8e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
import { app } from "/scripts/app.js";
// import { app } from "../../../scripts/app.js";
// import { ComfyWidgets } from "../../../scripts/widgets.js";

const ids1 = new Set(["smZ CLIPTextEncode"])
const ids2 = new Set(["smZ Settings"])
const widgets = ['mean_normalization', 'multi_conditioning', 'use_old_emphasis_implementation', 'with_SDXL']
const widgets_sdxl = ['ascore', 'width', 'height', 'crop_w', 'crop_h', 'target_width', 'target_height', 'text_g', 'text_l']
const getSetWidgets = new Set(['parser', 'with_SDXL'])

let origProps = {};
const HIDDEN_TAG = "smZhidden"

const findWidgetByName = (node, name) => node.widgets.find((w) => w.name === name);
const findWidgetsByName = (node, name) => node.widgets.filter((w) => w.name.endsWith(name));

const doesInputWithNameExist = (node, name) => node.inputs ? node.inputs.some((input) => input.name === name) : false;

// round in increments of x, with an offset
function round(number, increment = 10, offset = 0) {
    return Math.ceil((number - offset) / increment ) * increment + offset;
}

function toggleWidget(node, widget, show = false, suffix = "") {
    if (!widget || doesInputWithNameExist(node, widget.name)) return;
    if (!origProps[widget.name]) {
        origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize};
    }
    const origSize = node.size;

    widget.type = show ? origProps[widget.name].origType : HIDDEN_TAG + suffix;
    widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -3.3];

    widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show));

    const height = show ? Math.max(node.computeSize()[1], origSize[1]) : node.size[1];
    node.setSize([node.size[0], height]);
    if (show)
        delete widget.computedHeight;
    else
        widget.computedHeight = 0;
}

function widgetLogic(node, widget) {
    const wname = widget.name
    if (wname.endsWith("parser")) {
        const in_comfy = widget.value.includes("comfy")
        toggleMenuOption(node, ['multi_conditioning', wname], !in_comfy)
        toggleMenuOption(node, ['mean_normalization', wname], widget.value !== "comfy")
        if (in_comfy) {
            toggleMenuOption(node, ['use_old_emphasis_implementation', wname], false)
        }
    } else if (wname.endsWith("with_SDXL")) {
        toggleMenuOption(node, ['text', wname], !widget.value)

        // Resize node when widget is set to false
        if (!widget.value) {
            // Prevents resizing on init/webpage reload
            if(widget.init === false) {
                // Resize when set to false
                node.setSize([node.size[0], Math.max(100, round(node.size[1]/1.5))])
            }
        } else {
            // When enabled, set init to false
            widget.init = false
        }

        // Toggle sdxl widgets if sdxl widget value is true/false
        for (const w of widgets_sdxl) {
            toggleMenuOption(node, [w, wname], widget.value)
        }

        // Keep showing the widget if it's enabled
        if (widget.value && widget.type === HIDDEN_TAG) {
            toggleMenuOption(node, [widget.name, wname], true)
        }
    }
}

function getGroupNodeConfig(node) {
    let ls = []
    let nodeData = node.constructor?.nodeData
    if (nodeData) {
        for(let sym of Object.getOwnPropertySymbols(nodeData) ) {
            const o = nodeData[sym]; 
            if (!o) continue;
            ls.push(o)
        }
    }
    return ls
}

function getSetters(node) {
    if (node.widgets) {
        let gncl = getGroupNodeConfig(node)
        for (const w of node.widgets) {
            for (const gsw of [...getSetWidgets]) {
                if (!w.name.endsWith(gsw)) continue;
                let shouldBreak = false
                for (const gnc of gncl) {
                    const nwmap = gnc.newToOldWidgetMap[w.name]
                    if (nwmap && !(nwmap.node.type === [...ids1][0] && nwmap.inputName === gsw))
                        shouldBreak = true
                }
                if (shouldBreak) break;
                widgetLogic(node, w);
                w._value = w.value

                Object.defineProperty(w, 'value', {
                    get() {
                        return w._value;
                    },
                    set(newVal) {
                        w._value = newVal
                        widgetLogic(node, w);
                    }
                });

                // Hide SDXL widget on init
                // Doing it in nodeCreated fixes its toggling for some reason
                if (w.name.endsWith('with_SDXL')) {
                    toggleMenuOption(node, ['with_SDXL', w.name])
                    w.init = true
                    
                    // Hide steps
                    toggleMenuOption(node, ['smZ_steps', w.name] , false)
                }
            }
        }
    }
}

function toggleMenuOption(node, widget_arr, _show = null, perform_action = true) {
    const gncl = getGroupNodeConfig(node)
    const [widget_name, companion_widget_name] = Array.isArray(widget_arr) ? widget_arr : [widget_arr]
    let nwname = widget_name
    // Use companion_widget_name to get the correct widget with the new name
    if (companion_widget_name) {
        for (const gnc of gncl) {
            const omap = Object.values(gnc.oldToNewWidgetMap).find(x => Object.values(x).find(z => z === companion_widget_name))
            const tmp2 = omap[widget_name]
            if (tmp2) nwname = tmp2;
        }
    }
    const widgets = companion_widget_name ? [findWidgetByName(node, nwname)] : findWidgetsByName(node, nwname)
    for (const widget of widgets)
        toggleMenuOption0(node, widget, _show, perform_action)
}

function toggleMenuOption0(node, widget, _show = null, perform_action = true) {
    if (!widget || doesInputWithNameExist(node, widget.name)) return;
    if (!origProps[widget.name]) {
        origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize};
    }
    const show = (widget.type === origProps[widget.name].origType)
    if (perform_action) {
        toggleWidget(node, widget, _show !== null ? _show : !show)
        node.setDirtyCanvas(true);
    }
}

function toggle_all_settings_desc_widgets(node, _show = null) {
    let found_widgets = node.widgets.filter((w) => w.name.includes('info'));
    let is_showing = _show !== null ? _show : null
    found_widgets.forEach(w => {
        toggleMenuOption(node, [w.name, w.name], _show)
        is_showing = _show !== null ? _show : w.type === origProps[w.name].origType
    });

    let w = node.widgets.find((w) => w.name === 'extra');
    if (w) {
        let value = null;
        try {
            value =JSON.parse(w.value);
        } catch (error) {
            // when node definitions change due to an update or some other error
            value = {"show":true}
        }
        value.show = is_showing;
        w.value = JSON.stringify(value);
    }

    // Collapse the node if the widgets aren't showing
    if (!is_showing) {
        node.setSize([node.size[0], node.computeSize()[1]])
    }
}

function create_custom_option(content, _callback) {
    return {
        content: content,
        callback: () => _callback(),
    }
};

app.registerExtension({
    name: "comfy.smZ.dynamicWidgets",

    /**
     * Called when a node is created. Used to add menu options to nodes.
     * @param node The node that was created.
     * @param app The app.
     */
    nodeCreated(node) {
        const nodeType = node.type || node.constructor?.type
        let inGroupNode = false
        let inGroupNode2 = false
        let innerNodes = node.getInnerNodes?.()
        if (innerNodes) {
            for (const inode of innerNodes) {
                const _nodeType = inode.type || inode.constructor?.type
                if (ids1.has(_nodeType))
                    inGroupNode = ids1.has(_nodeType)
                if (inGroupNode)
                    ids1.add(nodeType) // GroupNode's type
                if (ids2.has(_nodeType))
                    inGroupNode2 = ids2.has(_nodeType)
                if (inGroupNode2)
                    ids2.add(nodeType) // GroupNode's type
            }
        }
        // let nodeData = node.constructor?.nodeData
        // if (nodeData) {
        //     for(let sym of Object.getOwnPropertySymbols(nodeData) ) {
        //     const nds = nodeData[sym];
        //     if (nds) {
        //         inGroupNode=true
        //         inGroupNode2=true
        //         break
        //     }
        //     }
        // }
        // ClipTextEncode++ node
        if (ids1.has(nodeType) || inGroupNode) {
            node.widgets.forEach(w => w._name = w.name)

            getSetters(node)

            // Reduce initial node size cause of SDXL widgets
            // node.setSize([node.size[0], Math.max(node.size[1]/1.5, 220)])
            node.setSize([node.size[0], 220])
        }
        // Settings node
        if (ids2.has(nodeType) || inGroupNode2) {
            node.serialize_widgets = true

            const onConfigure = node.onConfigure;
            node.onConfigure = function(o) {
                const r = onConfigure ? onConfigure.apply(this, arguments) : undefined;
                const w = this.widgets.find(w => w.name === 'extra')
                let value = null
                try {
                    value = JSON.parse(w.value);
                } catch (error) {
                    // when node definitions change due to an update or some other error
                    value = {"show":true}
                }
                toggle_all_settings_desc_widgets(this, value.show)
                return r;
            }

            // Styling.
            node.widgets.forEach(function(w) {
                w._name = w.name
                if (w.name.includes('ㅤ')) {
                    w.heading = true
                } else if (w.name.includes('info')) {
                    w.info = true
                    w.inputEl.disabled = true;
                    w.inputEl.readOnly = true;
                    w.inputEl.style.opacity = 0.75;
                    w.inputEl.style.alignContent = 'center';
                    w.inputEl.style.textAlign = 'center';
                }
            })
            // Hide `extra` widget
            toggleMenuOption(node, 'extra', false)
        }
        // Add extra MenuOptions for
        // ClipTextEncode++ and Settings node
        if (ids1.has(nodeType) || inGroupNode || ids2.has(nodeType) || inGroupNode2) {
            // Save the original options
            const getExtraMenuOptions = node.getExtraMenuOptions;

            node.getExtraMenuOptions = function (_, options) {
                // Call the original function for the default menu options
                const r = getExtraMenuOptions ? getExtraMenuOptions.apply(this, arguments) : undefined;
                let customOptions = []
                node.setDirtyCanvas(true, true);
                // if (!r) return r;

                if (ids2.has(nodeType) || inGroupNode2) {
                    const content_hide_show = "Hide/show all descriptions";
                    customOptions.push(null) // seperator
                    customOptions.push(create_custom_option(content_hide_show, toggle_all_settings_desc_widgets.bind(this, node)))
    
                    // Alternate way to cleanup MenuOption
                    const toHideWidgets = node.widgets.filter(w => w.name.includes('ㅤ') || w.name.includes('info') || w.name.includes('extra'))
                    const wo = options.filter(o => o === null || (o && !toHideWidgets.some(w => o.content.includes(`Convert ${w.name} to input`))))
                    options.splice(0, options.length, ...wo);
                }

                if (ids1.has(nodeType) || inGroupNode) {
                    // Dynamic MenuOption depending on the widgets
                    const content_hide_show = "Hide/show ";
                    // const whWidgets = node.widgets.filter(w => w.name === 'width' || w.name === 'height')
                    const hiddenWidgets = node.widgets.filter(w => w.type === HIDDEN_TAG)
                    // doesn't take GroupNode into account
                    const with_SDXL = node.widgets.find(w => w.name === 'with_SDXL')
                    const parser = node.widgets.find(w => w.name === 'parser')
                    const in_comfy = parser.value.includes("comfy")
                    let ws = widgets.map(widget_name => create_custom_option(content_hide_show + widget_name, toggleMenuOption.bind(this, node, widget_name)))
                    ws = ws.filter((w) => (in_comfy && parser.value !== 'comfy' && w.content.includes('mean_normalization')) || (in_comfy && w.content.includes('with_SDXL')) || !in_comfy )
                    // customOptions.push(null) // seperator
                    customOptions.push(...ws)

                    let wo = options.filter(o => o === null || (o && !hiddenWidgets.some(w => o.content.includes(`Convert ${w.name} to input`))))
                    const width = node.widgets.find(w => w.name === 'width')
                    const height = node.widgets.find(w => w.name === 'height')
                    if (width && height) {
                        const width_type = width.type.toLowerCase()
                        const height_type = height.type.toLowerCase()
                        if (!(width_type.includes('number') || width_type.includes('int') || width_type.includes('float') ||
                        height_type.includes('number') || height_type.includes('int') || height_type.includes('float')))
                            wo = wo.filter(o => o === null || (o && !o.content.includes('Swap width/height')))
                    }
                    options.splice(0, options.length, ...wo);
                }
                // options.unshift(...customOptions); // top
                options.splice(options.length - 1, 0, ...customOptions)
                // return r;
            }
        }
    }
});