Sleazy Fork is available in English.

Brazen UI Generator

Helper methods to generate a control panel UI for scripts

ของเมื่อวันที่ 17-11-2020 ดู เวอร์ชันล่าสุด

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.sleazyfork.org/scripts/416104/870040/Brazen%20UI%20Generator.js

// ==UserScript==
// @name         Brazen UI Generator
// @namespace    brazen
// @version      1.0.1
// @author       brazenvoid
// @license      GPL-3.0-only
// @description  Helper methods to generate a control panel UI for scripts
// @require      https://greasyfork.org/scripts/375557-base-resource/code/Base%20Resource.js?version=869326
// @grant        GM_addStyle
// ==/UserScript==

/**
 * @function GM_addStyle
 * @param {string} style
 */
GM_addStyle(
    `@keyframes fadeEffect{from{opacity:0}to{opacity:1}}button.form-button{padding:0 5px;width:100%}button.show-settings{background-color:#ffa31a;border:0;color:#000;font-size:.7rem;height:90vh;left:0;margin:0;padding:0;position:fixed;top:5vh;width:.1vw;writing-mode:sideways-lr;z-index:999}button.tab-button{background-color:inherit;border:1px solid #000;border-bottom:0;border-top-left-radius:3px;border-top-right-radius:3px;cursor:pointer;float:left;outline:none;padding:5px 10px;transition:.3s}button.tab-button:hover{background-color:#fff}button.tab-button.active{background-color:#fff;display:block}div.form-actions{text-align:center}div.form-actions button.form-button{padding:0 15px;width:auto}div.form-actions-wrapper{display:inline-flex}div.form-actions-wrapper > div.form-group + *{margin-left:15px}div.form-group{align-items:center;display:flex;min-height:20px;padding:4px 0}div.form-group.form-range-input-group > input{padding:0 5px;width:75px}div.form-group.form-range-input-group > input + input{margin-left:5px}div.form-section{text-align:center}div.form-section button + button{margin-left:5px}div.form-section label.title{display:block;height:20px;width:100%}div.form-section button.form-button{width:auto}div.tab-panel{animation:fadeEffect 1s;border:1px solid #000;display:none;padding:5px 10px}div.tab-panel.active{display:block}div.tabs-nav{overflow:hidden}div.tabs-section{margin-bottom:5px}hr{margin:3px}input.form-input{text-align:center}input.form-input.check-radio-input{margin-right:5px}input.form-input.regular-input{width:100px}label.form-label{flex-grow:1}label.form-stat-label{padding:2px 0}section.form-section{color:#000;font-size:12px;font-weight:700;position:fixed;left:0;padding:5px 10px;z-index:1000}select.form-dropdown{float:right;height:18px;text-align:center;width:100px}textarea.form-input{display:block;height:auto;position:relative;width:98%}`)

class BrazenUIGenerator
{
    /**
     * @param {JQuery} nodes
     */
    static appendToBody (nodes)
    {
        $('body').append(nodes)
    }

    /**
     * @param {boolean} showUI
     * @param {string} selectorPrefix
     */
    constructor (showUI, selectorPrefix)
    {
        /**
         * @type {*}
         * @private
         */
        this._buttonBackroundColor = null

        /**
         * @type {JQuery}
         * @private
         */
        this._section = null

        /**
         * @type {SelectorGenerator}
         * @private
         */
        this._selectorGenerator = new SelectorGenerator(selectorPrefix)

        /**
         * @type {string}
         * @private
         */
        this._selectorPrefix = selectorPrefix

        /**
         * @type {boolean}
         * @private
         */
        this._showUI = showUI

        /**
         * @type {JQuery}
         * @private
         */
        this._statusLine = null

        /**
         * @type {string}
         * @private
         */
        this._statusText = ''
    }

    /**
     * @param {JQuery} node
     * @param {string} text
     * @return {this}
     * @private
     */
    _addHelpTextOnHover (node, text)
    {
        node.on('mouseover', () => this.updateStatus(text, true))
        node.on('mouseout', () => this.resetStatus())
    }

    /**
     * @return {JQuery}
     */
    createBreakSeparator ()
    {
        return $('<br/>')
    }

    /**
     * @param {JQuery|JQuery[]} children
     * @return {JQuery}
     */
    createFormActions (children)
    {
        return $('<div class="form-actions"/>').append($('<div class="form-actions-wrapper"/>').append(children))
    }

    /**
     * @param {string} caption
     * @param {JQuery.EventHandler} onClick
     * @param {string} hoverHelp
     * @return {JQuery}
     */
    createFormButton (caption, onClick, hoverHelp = '')
    {
        let button = $('<button class="form-button">').
            css('backgroundColor', this._buttonBackroundColor ?? 'revert').
            text(caption).
            on('click', onClick)

        if (hoverHelp !== '') {
            this._addHelpTextOnHover(button, hoverHelp)
        }
        return button
    }

    /**
     * @return {JQuery}
     */
    createFormGroup ()
    {
        return $('<div class="form-group"/>')
    }

    /**
     * @param {string} id
     * @param {Array} keyValuePairs
     * @param {boolean} multiple
     *
     * @return {JQuery}
     */
    createFormGroupDropdown (id, keyValuePairs, multiple = false)
    {
        let dropdown = $('<select>').
            attr('id'.id).
            addClass(multiple ? 'form-dropdown-multiple' : 'form-dropdown').
            prop('multiple', multiple)

        for (let i = 0; i < keyValuePairs.length; i++) {
            dropdown.append(
                $('<option>').
                    val(keyValuePairs[i][0]).
                    text(keyValuePairs[i][1]).
                    prop('selected', multiple ? false : (i === 0)),
            )
        }
        return dropdown
    }

    /**
     * @param {string} id
     * @param {string} type
     *
     * @return {JQuery}
     */
    createFormGroupInput (id, type)
    {
        let inputFormGroup = $('<input class="form-input">').attr('id', id).attr('type', type)
        switch (type) {
            case 'number':
            case 'text':
                inputFormGroup.addClass('regular-input')
                break

            case 'radio':
            case 'checkbox':
                inputFormGroup.addClass('check-radio-input')
                break
        }
        return inputFormGroup
    }

    /**
     * @param {string} label
     * @param {string} inputID
     * @param {string} inputType
     * @return {JQuery}
     */
    createFormGroupLabel (label, inputID = '', inputType = '')
    {
        let labelFormGroup = $('<label class="form-label">').attr('for', inputID).text(label)
        if (inputType !== '') {
            switch (inputType) {
                case 'number':
                case 'text':
                    labelFormGroup.addClass('regular-input')
                    labelFormGroup.text(labelFormGroup.text() + ': ')
                    break
                case 'radio':
                case 'checkbox':
                    labelFormGroup.addClass('check-radio-input')
                    break
            }
        }
        return labelFormGroup
    }

    /**
     * @param {string} statisticType
     * @return {JQuery}
     */
    createFormGroupStatLabel (statisticType)
    {
        return $('<label class="form-stat-label">').
            attr('id', this._selectorGenerator.getStatLabelSelector(statisticType)).
            text('0')
    }

    /**
     * @param {string} label
     * @param {string} inputType
     * @param {string} hoverHelp
     * @return {JQuery}
     */
    createFormInputGroup (label, inputType = 'text', hoverHelp = '')
    {
        let inputID = this._selectorGenerator.getSettingsInputSelector(label)
        let divFormInputGroup = this.createFormGroup().append([
            this.createFormGroupLabel(label, inputID, inputType),
            this.createFormGroupInput(inputID, inputType),
        ])
        if (hoverHelp !== '') {
            this._addHelpTextOnHover(divFormInputGroup, hoverHelp)
        }
        return divFormInputGroup
    }

    /**
     * @param {string} label
     * @param {string} inputsType
     *
     * @return {JQuery}
     */
    createFormRangeInputGroup (label, inputsType = 'text')
    {
        let divFormInputGroup = this.createFormGroup().append([
            this.createFormGroupLabel(label, '', inputsType),
            this.createFormGroupInput(this._selectorGenerator.getSettingsRangeInputSelector(label, true), inputsType),
            this.createFormGroupInput(this._selectorGenerator.getSettingsRangeInputSelector(label, false), inputsType),
        ])
        return divFormInputGroup.addClass('form-range-input-group')
    }

    /**
     * @param {string} title
     * @return {JQuery}
     */
    createFormSection (title = '')
    {
        let sectionDiv = $('<div class="form-section">')
        if (title !== '') {
            sectionDiv.append($('<label class="title">').text(title))
        }
        return sectionDiv
    }

    /**
     * @param {string} caption
     * @param {string} tooltip
     * @param {JQuery.EventHandler} onClick
     * @param {string} hoverHelp
     * @return {JQuery}
     */
    createFormSectionButton (caption, tooltip, onClick, hoverHelp = '')
    {
        return this.createFormButton(caption, onClick, hoverHelp).attr('title', tooltip)
    }

    /**
     * @param {string} label
     * @param {int} rows
     * @param {string} hoverHelp
     * @return {JQuery}
     */
    createFormTextAreaGroup (label, rows, hoverHelp = '')
    {
        let group = this.createFormGroup().append([
            this.createFormGroupLabel(label).css('textAlign', 'center'),
            $('<textarea class="form-input">').
                attr('id', this._selectorGenerator.getSettingsInputSelector(label)).
                attr('rows', rows),
        ])
        if (hoverHelp !== '') {
            this._addHelpTextOnHover(group, hoverHelp)
        }
        return group
    }

    /**
     * @return {JQuery}
     */
    createSection ()
    {
        this._section = $('<section class="form-section">')
        return this._section
    }

    /**
     * @return {JQuery}
     */
    createSeparator ()
    {
        return $('<hr/>')
    }

    /**
     * @param {LocalStore} localStore
     * @param {JQuery.EventHandler} onClick
     * @return {JQuery}
     */
    createSettingsFormActions (localStore, onClick)
    {
        return this.createFormSection().append([
            this.createFormActions([
                this.createFormButton('Apply', onClick, 'Filter items as per the settings in the dialog.'),
                this.createFormButton('Reset', (event) => {
                    localStore.retrieve()
                    onClick(event)
                }, 'Restore and apply saved configuration.'),
            ]),
        ])
    }

    /**
     * @param {string} label
     * @param {Array} keyValuePairs
     * @param {boolean} multiple
     *
     * @return {JQuery}
     */
    createSettingsDropDownFormGroup (label, keyValuePairs, multiple = false)
    {
        let dropdownID = this._selectorGenerator.getSettingsInputSelector(label)
        let optionsSelector = '#' + dropdownID + ' option'

        if (multiple) {
            return this.createFormSection(label).append([
                this.createFormGroup().append([
                    this.createFormGroupDropdown(dropdownID, keyValuePairs, multiple),
                ]),
                this.createFormActions([
                    this.createFormButton(
                        'Select All',
                        () => {
                            $(optionsSelector).each((index, element) => {
                                $(element).prop('selected', true)
                            })
                        },
                        'Select all options.',
                    ),
                    this.createFormButton(
                        'Deselect All',
                        () => {
                            $(optionsSelector).each((index, element) => {
                                $(element).prop('selected', false)
                            })
                        },
                        'Deselect all options.',
                    ),
                ]),
            ])
        }
        return this.createFormGroup().append([
            this.createFormGroupLabel(label, dropdownID, 'text'),
            this.createFormGroupDropdown(dropdownID, keyValuePairs, multiple),
        ])
    }

    /**
     * @return {JQuery}
     */
    createSettingsHideButton ()
    {
        return this.createFormButton('<< Hide', () => this._section.style.display = 'none')
    }

    /**
     * @param {string} caption
     * @param {JQuery} settingsSection
     * @param {JQuery.EventHandler|null} onMouseLeave
     *
     * @return {JQuery}
     */
    createSettingsShowButton (caption, settingsSection, onMouseLeave = null)
    {
        return $('<button class="show-settings">').
            text(caption).
            on('click', () => settingsSection.toggle(500)).
            on('mouseleave', onMouseLeave ? () => onMouseLeave() : () => settingsSection.hide(500))
    }

    /**
     * @param {string} statisticsType
     * @param {string} label
     * @return {JQuery}
     */
    createStatisticsFormGroup (statisticsType, label = '')
    {
        return this.createFormGroup().append([
            this.createFormGroupLabel((label === '' ? statisticsType : label) + ' Filter'),
            this.createFormGroupStatLabel(statisticsType),
        ])
    }

    /**
     * @return {JQuery}
     */
    createStatisticsTotalsGroup ()
    {
        return this.createFormGroup().append([
            this.createFormGroupLabel('Total'),
            this.createFormGroupStatLabel('Total'),
        ])
    }

    /**
     * @return {JQuery}
     */
    createStatusSection ()
    {
        this._statusLine = this.createFormGroupLabel('Status').attr('id', this._selectorGenerator.getSelector('status'))
        return this.createFormSection().append(this._statusLine)
    }

    /**
     * @param {LocalStore} localStore
     * @return {JQuery}
     */
    createStoreFormSection (localStore)
    {
        return this.createFormSection('Cached Configuration').append([
            this.createFormActions([
                this.createFormSectionButton(
                    'Update',
                    'Save UI settings in store',
                    () => localStore.save(), 'Saves applied settings.',
                ),
                this.createFormSectionButton(
                    'Purge',
                    'Purge store',
                    () => localStore.delete(),
                    'Removes saved settings. Settings will then be sourced from the defaults defined in the script.',
                ),
            ]),
        ])
    }

    /**
     * @param {string} tabName
     * @param {boolean} isFirst
     * @return {JQuery}
     */
    createTabButton (tabName, isFirst)
    {
        let tabButton = $('<button class="tab-button">').
            text(tabName).
            on('click', (event) => {
                let button = $(event.currentTarget)
                button.parents('.tabs-section:first').find('.tab-button,.tab-panel').removeClass('active')
                button.addClass('active')
                console.log(button.text())
                console.log(Utilities.toKebabCase(button.text()))
                console.log('#' + Utilities.toKebabCase(button.text()))
                $('#' + Utilities.toKebabCase(button.text())).addClass('active')
            })
        if (isFirst) {
            tabButton.addClass('active')
        }
        return tabButton
    }

    /**
     * @param {string} tabName
     * @param {boolean} isFirst
     * @return {JQuery}
     */
    createTabPanel (tabName, isFirst = false)
    {
        let tabPanel = $('<div class="tab-panel">').attr('id', Utilities.toKebabCase(tabName))
        if (isFirst) {
            tabPanel.addClass('active')
        }
        return tabPanel
    }

    /**
     * @param {string[]} tabNames
     * @param {JQuery[]} tabPanels
     * @return {JQuery}
     */
    createTabsSection (tabNames, tabPanels)
    {
        let tabButtons = []
        for (let i = 0; i < tabNames.length; i++) {
            tabButtons.push(this.createTabButton(tabNames[i], i === 0))
        }
        let nav = $('<div class="tabs-nav">').append(tabButtons)
        return $('<div class="tabs-section">').append(nav).append(...tabPanels)
    }

    /**
     * @return {JQuery}
     */
    getSelectedSection ()
    {
        return this._section
    }

    /**
     * @param {string} label
     * @return {JQuery}
     */
    getSettingsInput (label)
    {
        return $(this._selectorGenerator.getSettingsInputSelector(label))
    }

    /**
     * @param {string} label
     * @return {number}
     */
    getSettingsInputNumberValue (label)
    {
        return parseInt(this.getSettingsInput(label).val())
    }

    /**
     * @param {string} label
     * @return {string|string[]|boolean}
     */
    getSettingsInputValue (label)
    {
        let input = this.getSettingsInput(label)
        if (input.attr('type') === 'checkbox') {
            return input.prop('checked')
        }
        return input.val()
    }

    /**
     * @param {string} label
     * @param {boolean} getMinInput
     * @return {JQuery}
     */
    getSettingsRangeInput (label, getMinInput)
    {
        return $(this._selectorGenerator.getSettingsRangeInputSelector(label, getMinInput))
    }

    /**
     * @param {string} label
     * @param {boolean} getMinInputValue
     * @return {number}
     */
    getSettingsRangeInputValue (label, getMinInputValue)
    {
        return parseInt(this.getSettingsRangeInput(label, getMinInputValue).val())
    }

    resetStatus ()
    {
        this._statusLine.text(this._statusText)
    }

    /**
     * @param {string} label
     * @param {*} value
     */
    setSettingsInputValue (label, value)
    {
        let input = this.getSettingsInput(label)
        if (input.attr('type') === 'checkbox') {
            input.prop('checked', value)
        } else {
            input.val(value)
        }
    }

    /**
     * @param {string} label
     * @param {number} lowerBound
     * @param {number} upperBound
     */
    setSettingsRangeInputValue (label, lowerBound, upperBound)
    {
        this.getSettingsRangeInput(label, true).val(lowerBound)
        this.getSettingsRangeInput(label, false).val(upperBound)
    }

    /**
     * @param {string} status
     * @param {boolean} transient
     */
    updateStatus (status, transient = false)
    {
        if (!transient) {
            this._statusText = status
        }
        this._statusLine.text(status)
    }
}