On this page

# GUI.js

    import * as util from '../src/utils.js'
    import dat from '../vendor/dat.gui.js'

    /** @class */
    class GUI {
        /**
         * @param {Object} template A set of name/object pairs, one per UI element
         *
         * @example
         * const gui = new GUI ({
         *     opacity: { // slider
         *         val: [canvas.opacity, [0, 1, 0.1]],
         *         cmd: val => canvas.setOpacity(val),
         *     },
         *     download: { // button
         *         cmd: () => util.downloadBlob(data, 'data.json', false),
         *     },
         *     ...
         * })
         */
        constructor(template) {
            this.template = template

            this.controllers = {}
            this.values = {} // the key/val's from each template

            this.gui = new dat.GUI()
            const guis = [this.gui]

            // this.folders['default'] = this.baseGui

            let newFolder = obj => !obj.val && !obj.cmd
            const parseGuis = obj => {
                util.forLoop(obj, (obj, key) => {
                    if (newFolder(obj)) {
                        console.log('new follder', key)

                        this.gui = this.gui.addFolder(key)
                        guis.push(this.gui)
                        parseGuis(obj)
                        guis.pop()
                        this.gui = guis.at(-1)
                    } else {
                        this.controllers[key] = this.addUI(obj, key)
                    }
                })
            }
            parseGuis(template)

            console.log('controllers, values', this.controllers, this.values, guis)
        }

        type(obj) {
            const { val, cmd } = obj
            const valType = util.typeOf(val)
            const cmdType = util.typeOf(cmd)

            if (this.isDatColor(val)) return 'color'
            if (valType === 'undefined') return 'button'
            // if (val === 'listen') return 'monitor'
            if (valType === 'boolean') return 'toggle'
            if (valType === 'string') return 'input'
            if (valType === 'array' && val.length === 2) {
                if (util.typeOf(val[0]) === 'number') return 'slider'
                if (util.typeOf(val[0]) === 'string') return 'chooser'
                if (util.typeOf(val[0]) === 'object') return 'monitor'
                if (util.typeOf(val[0]) === 'array') return 'monitor'
            }

            throw Error('GUI type error, val: ' + val + ' cmd: ' + cmd)
        }

        /**
         *
         * @param {Object} obj A gui object with two optional objects: 'val' and 'cmd'
         * @param {string} key The name of the gui
         * @returns A dat.gui control object
         */
        addUI(obj, key) {
            let { val, cmd } = obj
            const type = this.type(obj)

            let control, extent

            if (type === 'monitor') cmd = () => val[0][val[1]]
            if (['slider', 'chooser'].includes(type)) [val, extent] = val
            if (type === 'button') val = cmd

            console.log('addUI:', type, key, val, cmd)

            this.values[key] = val

            switch (type) {
                case 'slider':
                    const [min, max, step = 1] = extent
                    control = this.gui.add(this.values, key, min, max).step(step)
                    break

                case 'chooser':
                    control = this.gui.add(this.values, key, extent)
                    break

                case 'color':
                    control = this.gui.addColor(this.values, key)
                    break

                case 'button':
                case 'toggle':
                case 'input':
                    control = this.gui.add(this.values, key)
                    break

                case 'monitor':
                    control = this.gui.add(val[0], val[1])
                    break

                default:
                    throw Error(`Controller.addUI: bad type: ${type}`)
            }

            // initialize: set model etc initial values to this value
            if (!['monitor', 'button', 'toggle'].includes(type)) cmd(val)
            // if (cmd && val && type !== 'monitor') cmd(val)
            // if (val === 'listen') this.setListener(key, cmd)

            if (cmd) {
                // if (val === 'listen') control.listen().onChange(cmd)
                if (type === 'monitor') control.listen() //.onChange(cmd)
                else control.onChange(cmd)
            }

            return control
        }
        updateGui(name, value) {
            console.log('updateGui name, value', name, value)
        }

        isDatColor(val) {
            if (util.typeOf(val) === 'string') {
                if (val[0] === '#') return val.length === 7 || val.length === 4
                if (val.startsWith('rgb(') || val.startsWith('rgba('))
                    return val.endsWith(')')
                if (val.startsWith('hsl(') || val.startsWith('hsla('))
                    return val.endsWith(')')
                if (val.startsWith('hsv(') || val.startsWith('hsva('))
                    return val.endsWith(')')
            }

            if (util.typeOf(val) === 'object')
                return val.h != null && val.s != null && val.v != null
            // if (util.typeOf(val) === 'array') {
            //     if (val.length === 3 || val.length === 4)
            //         return val.every(i => util.typeOf(i) === 'number')
            // }

            return false
        }
        // setListener(key, cmd) {
        //     this.values[key] = cmd()
        // }
        // update() {
        //     util.forLoop(this.template, (obj, key) => {
        //         if (obj.val === 'listen') this.setListener(key, obj.cmd)
        //     })
        // }
    }

    export default GUI

Type anything to view search result