File size: 4,522 Bytes
c6535db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { BaseSteppedWidget } from "./BaseSteppedWidget.js"
import { warnDeprecated, clamp } from "../utils/widget.js"
const UNIQUE_MESSAGE_LIMIT = 10_000
/**
 * This is used as an (invalid) assertion to resolve issues with legacy duck-typed values.
 *
 * Function style in use by:
 * https://github.com/kijai/ComfyUI-KJNodes/blob/c3dc82108a2a86c17094107ead61d63f8c76200e/web/js/setgetnodes.js#L401-L404
 */
function toArray(values) {
    return Array.isArray(values) ? values : Object.keys(values)
}

export class ComboWidget extends BaseSteppedWidget {
    constructor(widget, node) {
        super(widget, node);
        this.type = "combo";
    }

    get _displayValue() {
        const { values: rawValues } = this.options
        if (rawValues) {
            const values = typeof rawValues === "function" ? rawValues() : rawValues

            if (values && !Array.isArray(values)) {
                return values[this.value]
            }
        }
        return typeof this.value === "number" ? String(this.value) : this.value
    }

    #getValues(node) {
        const { values } = this.options
        if (values == null) throw new Error("[ComboWidget]: values is required")

        return typeof values === "function"
            ? values(this, node)
            : values
    }

    /**
     * Checks if the value is {@link Array.at at} the given index in the combo list.
     * @param increment `true` if checking the use of the increment button, `false` for decrement
     * @returns `true` if the value is at the given index, otherwise `false`.
     */
    #canUseButton(increment) {
        const { values } = this.options
        // If using legacy duck-typed method, false is the most permissive return value
        if (typeof values === "function") return false

        const valuesArray = toArray(values)
        if (!(valuesArray.length > 1)) return false

        // Edge case where the value is both the first and last item in the list
        const firstValue = valuesArray.at(0)
        const lastValue = valuesArray.at(-1)
        if (firstValue === lastValue) return true

        return this.value !== (increment ? lastValue : firstValue)
    }

    /**
     * Returns `true` if the current value is not the last value in the list.
     * Handles edge case where the value is both the first and last item in the list.
     */
    canIncrement() {
        return this.#canUseButton(true)
    }

    canDecrement() {
        return this.#canUseButton(false)
    }

    incrementValue(options) {
        this.#tryChangeValue(1, options)
    }

    decrementValue(options) {
        this.#tryChangeValue(-1, options)
    }

    #tryChangeValue(delta, options) {
        const values = this.#getValues(options.node)
        const indexedValues = toArray(values)

        // avoids double click event
        options.canvas.last_mouseclick = 0

        const foundIndex = typeof values === "object"
            ? indexedValues.indexOf(String(this.value)) + delta
            : indexedValues.indexOf(this.value) + delta

        const index = clamp(foundIndex, 0, indexedValues.length - 1)

        const value = Array.isArray(values)
            ? values[index]
            : index
        this.setValue(value, options)
    }

    onClick({ e, node, canvas }) {
        const x = e.canvasX - node.pos[0]
        const width = this.width || node.size[0]

        // Deprecated functionality (warning as of v0.14.5)
        if (typeof this.options.values === "function") {
            warnDeprecated("Using a function for values is deprecated. Use an array of unique values instead.")
        }

        // Determine if clicked on left/right arrows
        if (x < 40) return this.decrementValue({ e, node, canvas })
        if (x > width - 40) return this.incrementValue({ e, node, canvas })

        // Otherwise, show dropdown menu
        const values = this.#getValues(node)
        const values_list = toArray(values)

        // Handle center click - show dropdown menu
        const text_values = values != values_list ? Object.values(values) : values
        new LiteGraph.ContextMenu(text_values, {
            scale: Math.max(1, canvas.ds.scale),
            event: e,
            className: "dark",
            callback: (value) => {
                this.setValue(
                    values != values_list
                        ? text_values.indexOf(value)
                        : value,
                    { e, node, canvas },
                )
            },
        })
    }
}