Greasy Fork is available in English.

KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

Fra og med 08.10.2022. Se Den nyeste version.

Dette script skal ikke installes direkte. Det er et API for andre scripts der skal inkluderes med meta direktiv // @require https://update.greasyfork.org/scripts/451521/1102409/KameSame%20Open%20Framework%20-%20Settings%20module.js

"use strict";
// ==UserScript==
// @name        KameSame Open Framework - Settings module
// @namespace   timberpile
// @description Settings module for KameSame Open Framework
// @version     0.2
// @copyright   2022+, Robin Findley, Timberpile
// @license     MIT; http://opensource.org/licenses/MIT
// ==/UserScript==
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};

(async function (global) {
    var _KSOFSettings_instances, _KSOFSettings_open_dialog, _KSOFSettings_setting_changed;
    const ksof = global.ksof;
    const background_funcs = () => {
        return {
            open: () => {
                const anchor = install_anchor();
                let bkgd = anchor.find('> #ksofs_bkgd');
                if (bkgd.length === 0) {
                    bkgd = $('<div id="ksofs_bkgd" refcnt="0"></div>');
                    anchor.prepend(bkgd);
                }
                const refcnt = Number(bkgd.attr('refcnt'));
                bkgd.attr('refcnt', refcnt + 1);
            },
            close: () => {
                const bkgd = $('#ksof_ds > #ksofs_bkgd');
                if (bkgd.length === 0)
                    return;
                const refcnt = Number(bkgd.attr('refcnt'));
                if (refcnt <= 0)
                    return;
                bkgd.attr('refcnt', refcnt - 1);
            }
        };
    };
    //########################################################################
    //------------------------------
    // Constructor
    //------------------------------
    class KSOFSettings {
        constructor(config) {
            _KSOFSettings_instances.add(this);
            _KSOFSettings_open_dialog.set(this, void 0);
            this.cfg = config;
            this.config_list = {};
            __classPrivateFieldSet(this, _KSOFSettings_open_dialog, $(), "f");
            this.background = background_funcs();
        }
        //------------------------------
        // Open the settings dialog.
        //------------------------------
        static save(context) {
            if (!ksof.settings)
                throw new Error('ksof.settings not defined');
            if (!ksof.Settings)
                throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
            const script_id = ((typeof context === 'string') ? context : context.cfg.script_id);
            const settings = ksof.settings[script_id];
            if (!settings)
                return Promise.resolve('');
            return ksof.file_cache.save('ksof.settings.' + script_id, settings);
        }
        save() {
            return KSOFSettings.save(this);
        }
        //------------------------------
        // Open the settings dialog.
        //------------------------------
        static async load(context, defaults) {
            const script_id = ((typeof context === 'string') ? context : context.cfg.script_id);
            try {
                const settings = await ksof.file_cache.load('ksof.settings.' + script_id);
                return finish(settings);
            }
            catch (error) {
                return finish.call(null, {});
            }
            function finish(settings) {
                if (!ksof.settings)
                    throw new Error('ksof.settings not defined');
                if (!ksof.Settings)
                    throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
                if (defaults)
                    ksof.settings[script_id] = deep_merge(defaults, settings);
                else
                    ksof.settings[script_id] = settings;
                return ksof.settings[script_id];
            }
        }
        load(defaults) {
            return KSOFSettings.load(this, defaults);
        }
        //------------------------------
        // Save button handler.
        //------------------------------
        save_btn() {
            if (!ksof.settings)
                throw new Error('ksof.settings not defined');
            if (!ksof.Settings)
                throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
            const script_id = this.cfg.script_id;
            const settings = ksof.settings[script_id];
            if (settings) {
                const active_tabs = __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").find('.ui-tabs-active').toArray().map(function (tab) { return '#' + tab.attributes.getNamedItem('id')?.value || ''; });
                if (active_tabs.length > 0)
                    settings.ksofs_active_tabs = active_tabs;
            }
            if (this.cfg.autosave === undefined || this.cfg.autosave === true) {
                this.save();
            }
            if (this.cfg.on_save) {
                this.cfg.on_save(ksof.settings[this.cfg.script_id]);
            }
            ksof.trigger('ksof.settings.save');
            this.keep_settings = true;
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('close');
        }
        //------------------------------
        // Cancel button handler.
        //------------------------------
        cancel() {
            if (!ksof.settings)
                throw new Error('ksof.settings not defined');
            if (!ksof.Settings)
                throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('close');
            if (typeof this.cfg.on_cancel === 'function')
                this.cfg.on_cancel(ksof.settings[this.cfg.script_id]);
        }
        //------------------------------
        // Open the settings dialog.
        //------------------------------
        open() {
            if (!ksof.settings)
                throw new Error('ksof.settings not defined');
            if (!ksof.Settings)
                throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
            if (!ready)
                return;
            if (__classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").length > 0)
                return;
            install_anchor();
            if (this.cfg.background !== false)
                this.background.open();
            __classPrivateFieldSet(this, _KSOFSettings_open_dialog, $('<div id="ksofs_' + this.cfg.script_id + '" class="ksof_settings" style="display:none;"></div>'), "f");
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").html(config_to_html(this));
            const resize = (event, ui) => {
                const is_narrow = __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").hasClass('narrow');
                ui;
                if (is_narrow && ui.size.width >= 510) {
                    __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").removeClass('narrow');
                }
                else if (!is_narrow && ui.size.width < 490) {
                    __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").addClass('narrow');
                }
            };
            const tab_activated = () => {
                const wrapper = $(__classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('widget'));
                if ((wrapper.outerHeight() || 0) + wrapper.position().top > document.body.clientHeight) {
                    __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('option', 'maxHeight', document.body.clientHeight);
                }
            };
            let width = 500;
            if (window.innerWidth < 510) {
                width = 280;
                __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").addClass('narrow');
            }
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog({
                title: this.cfg.title,
                buttons: [
                    { text: 'Save', click: this.save_btn.bind(this) },
                    { text: 'Cancel', click: this.cancel.bind(this) }
                ],
                width: width,
                maxHeight: document.body.clientHeight,
                modal: false,
                autoOpen: false,
                appendTo: '#ksof_ds',
                resize: resize.bind(this),
                close: () => { this.close(false); }
            });
            $(__classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('widget')).css('position', 'fixed');
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").parent().addClass('ksof_settings_dialog');
            $('.ksof_stabs').tabs({ activate: tab_activated.bind(null) });
            const settings = ksof.settings[this.cfg.script_id];
            if (settings && settings.ksofs_active_tabs instanceof Array) {
                const active_tabs = settings.ksofs_active_tabs;
                for (let tab_idx = 0; tab_idx < active_tabs.length; tab_idx++) {
                    const tab = $(active_tabs[tab_idx]);
                    tab.closest('.ui-tabs').tabs({ active: tab.index() });
                }
            }
            const toggle_multi = (e) => {
                if (e.button != 0)
                    return true;
                const multi = $(e.currentTarget);
                const scroll = e.currentTarget.scrollTop;
                e.target.selected = !e.target.selected;
                setTimeout(function () {
                    e.currentTarget.scrollTop = scroll;
                    multi.focus(); // TODO what should this do? it's deprecated
                }, 0);
                return __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_setting_changed).call(this, e);
            };
            const setting_button_clicked = (e) => {
                const name = e.target.attributes.name.value;
                const _item = this.config_list[name];
                if (_item.type == 'button') {
                    const item = _item;
                    item.on_click.call(e, name, item, __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_setting_changed).bind(this, e));
                }
            };
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('open');
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").find('.setting[multiple]').on('mousedown', toggle_multi.bind(this));
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").find('.setting').on('change', __classPrivateFieldGet(this, _KSOFSettings_instances, "m", _KSOFSettings_setting_changed).bind(this));
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").find('form').on('submit', function () { return false; });
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").find('button.setting').on('click', setting_button_clicked.bind(this));
            if (typeof this.cfg.pre_open === 'function')
                this.cfg.pre_open(__classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f"));
            this.reversions = deep_merge({}, ksof.settings[this.cfg.script_id]);
            this.refresh();
        }
        //------------------------------
        // Close and destroy the dialog.
        //------------------------------
        close(keep_settings) {
            if (!ksof.settings)
                throw new Error('ksof.settings not defined');
            if (!ksof.Settings)
                throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
            if (!this.keep_settings && keep_settings !== true) {
                // Revert settings
                ksof.settings[this.cfg.script_id] = deep_merge({}, this.reversions || {});
                delete this.reversions;
            }
            delete this.keep_settings;
            __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").dialog('destroy');
            __classPrivateFieldSet(this, _KSOFSettings_open_dialog, $(), "f");
            if (this.cfg.background !== false)
                this.background.close();
            if (typeof this.cfg.on_close === 'function')
                this.cfg.on_close(ksof.settings[this.cfg.script_id]);
        }
        //------------------------------
        // Update the dialog to reflect changed settings.
        //------------------------------
        refresh() {
            if (!ksof.settings)
                throw new Error('ksof.settings not defined');
            if (!ksof.Settings)
                throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
            const script_id = this.cfg.script_id;
            const settings = ksof.settings[script_id];
            for (const name in this.config_list) {
                const elem = __classPrivateFieldGet(this, _KSOFSettings_open_dialog, "f").find('#' + script_id + '_' + name);
                const _config = this.config_list[name];
                const value = get_value(this, settings, name);
                if (_config.type == 'dropdown') {
                    elem.find('option[name="' + value + '"]').prop('selected', true);
                }
                else if (_config.type == 'list') {
                    const config = _config;
                    if (config.multi === true) {
                        elem.find('option').each(function (i, e) {
                            const opt_name = e.getAttribute('name') || '#' + e.index;
                            e.selected = value[opt_name];
                        });
                    }
                    else {
                        elem.find('option[name="' + value + '"]').prop('selected', true);
                    }
                }
                else if (_config.type == 'checkbox') {
                    elem.prop('checked', value);
                }
                else {
                    elem.val(value);
                }
            }
            if (typeof this.cfg.on_refresh === 'function')
                this.cfg.on_refresh(ksof.settings[this.cfg.script_id]);
        }
    }
    _KSOFSettings_open_dialog = new WeakMap(), _KSOFSettings_instances = new WeakSet(), _KSOFSettings_setting_changed = function _KSOFSettings_setting_changed(event) {
        if (!ksof.settings)
            throw new Error('ksof.settings not defined');
        if (!ksof.Settings)
            throw new Error('ksof.Settings not defined'); // this line is used to tell the compiler that settings and Settigns definitely are defined in the following lines
        const elem = $(event.currentTarget);
        const name = elem.attr('name');
        if (!name)
            return false;
        const _item = this.config_list[name];
        // Extract the value
        let value;
        if (_item.type == 'dropdown') {
            value = elem.find(':checked').attr('name');
        }
        else if (_item.type == 'list') {
            const item = _item;
            if (item.multi === true) {
                value = {};
                elem.find('option').each(function (i, e) {
                    const opt_name = e.getAttribute('name') || '#' + e.index;
                    value[opt_name] = e.selected;
                });
            }
            else {
                value = elem.find(':checked').attr('name');
            }
        }
        else if (_item.type == 'input') {
            const item = _item;
            if (item.subtype === 'number') {
                value = Number(elem.val());
            }
        }
        else if (_item.type == 'checkbox') {
            value = elem.is(':checked');
        }
        else if (_item.type == 'number') {
            value = Number(elem.val());
        }
        else {
            value = elem.val();
        }
        // Validation
        let valid = { valid: true, msg: '' };
        {
            const item = _item;
            if (item.validate) {
                const _valid = item.validate.call(event.target, value, item);
                if (typeof _valid === 'boolean')
                    valid = { valid: _valid, msg: '' };
                else if (typeof _valid === 'string')
                    valid = { valid: false, msg: _valid };
            }
        }
        if (_item.type == 'number') {
            const item = _item;
            if (item.min && Number(value) < item.min) {
                valid.valid = false;
                if (valid.msg.length === 0) {
                    if (typeof item.max === 'number')
                        valid.msg = 'Must be between ' + item.min + ' and ' + item.max;
                    else
                        valid.msg = 'Must be ' + item.min + ' or higher';
                }
            }
            else if (item.max && Number(value) > item.max) {
                valid.valid = false;
                if (valid.msg.length === 0) {
                    if (typeof item.min === 'number')
                        valid.msg = 'Must be between ' + item.min + ' and ' + item.max;
                    else
                        valid.msg = 'Must be ' + item.max + ' or lower';
                }
            }
        }
        else if (_item.type == 'text') {
            const item = _item;
            if (item.match !== undefined && value.match(item.match) === null) {
                valid.valid = false;
                if (valid.msg.length === 0)
                    // valid.msg = item.error_msg || 'Invalid value'; // TODO no item has a error_msg?
                    valid.msg = 'Invalid value';
            }
        }
        // Style for valid/invalid
        const parent = elem.closest('.right');
        parent.find('.note').remove();
        if (typeof valid.msg === 'string' && valid.msg.length > 0)
            parent.append('<div class="note' + (valid.valid ? '' : ' error') + '">' + valid.msg + '</div>');
        if (!valid.valid) {
            elem.addClass('invalid');
        }
        else {
            elem.removeClass('invalid');
        }
        const script_id = this.cfg.script_id;
        const settings = ksof.settings[script_id];
        if (valid.valid) {
            const item = _item;
            // if (item.no_save !== true) set_value(this, settings, name, value); // TODO what is no_save supposed to do?
            set_value(this, settings, name, value);
            if (item.on_change)
                item.on_change.call(event.target, name, value, item);
            if (this.cfg.on_change)
                this.cfg.on_change.call(event.target, name, value, item);
            if (item.refresh_on_change === true)
                this.refresh();
        }
        return false;
    };
    function createSettings() {
        const settings_obj = (config) => {
            return new KSOFSettings(config);
        };
        settings_obj.save = (context) => { return KSOFSettings.save(context); };
        settings_obj.load = (context, defaults) => { return KSOFSettings.load(context, defaults); };
        settings_obj.background = background_funcs();
        return settings_obj;
    }
    ksof.Settings = createSettings();
    ksof.settings = {};
    //########################################################################
    let ready = false;
    //========================================================================
    function deep_merge(...objects) {
        const merged = {};
        function recursive_merge(dest, src) {
            for (const prop in src) {
                if (typeof src[prop] === 'object' && src[prop] !== null) {
                    const srcProp = src[prop];
                    if (Array.isArray(srcProp)) {
                        dest[prop] = srcProp.slice();
                    }
                    else {
                        dest[prop] = dest[prop] || {};
                        recursive_merge(dest[prop], srcProp);
                    }
                }
                else {
                    dest[prop] = src[prop];
                }
            }
            return dest;
        }
        for (const obj in objects) {
            recursive_merge(merged, objects[obj]);
        }
        return merged;
    }
    //------------------------------
    // Convert a config object to html dialog.
    //------------------------------
    /* eslint-disable no-case-declarations */
    function config_to_html(context) {
        context.config_list = {};
        if (!ksof.settings) {
            return '';
        }
        let base = ksof.settings[context.cfg.script_id];
        if (base === undefined)
            ksof.settings[context.cfg.script_id] = base = {};
        let html = '';
        const child_passback = {};
        const id = context.cfg.script_id + '_dialog';
        for (const name in context.cfg.content) {
            html += parse_item(name, context.cfg.content[name], child_passback);
        }
        if (child_passback.tabs && child_passback.pages)
            html = assemble_pages(id, child_passback.tabs, child_passback.pages) + html;
        return '<form>' + html + '</form>';
        //============
        function parse_item(name, _item, passback) {
            if (typeof _item.type !== 'string')
                return '';
            const id = context.cfg.script_id + '_' + name;
            let cname, html = '', child_passback, non_page = '';
            const _type = _item.type;
            if (_type == 'tabset') {
                const item = _item;
                child_passback = {};
                for (cname in item.content) {
                    non_page += parse_item(cname, item.content[cname], child_passback);
                }
                if (child_passback.tabs && child_passback.pages) {
                    html = assemble_pages(id, child_passback.tabs, child_passback.pages);
                }
            }
            else if (_type == 'page') {
                const item = _item;
                if (typeof item.content !== 'object')
                    item.content = {};
                if (!passback.tabs) {
                    passback.tabs = [];
                }
                if (!passback.pages) {
                    passback.pages = [];
                }
                passback.tabs.push('<li id="' + id + '_tab"' + to_title(item.hover_tip) + '><a href="#' + id + '">' + item.label + '</a></li>');
                child_passback = {};
                for (cname in item.content)
                    non_page += parse_item(cname, item.content[cname], child_passback);
                if (child_passback.tabs && child_passback.pages)
                    html = assemble_pages(id, child_passback.tabs, child_passback.pages);
                passback.pages.push('<div id="' + id + '">' + html + non_page + '</div>');
                passback.is_page = true;
                html = '';
            }
            else if (_type == 'group') {
                const item = _item;
                if (typeof item.content !== 'object')
                    item.content = {};
                child_passback = {};
                for (cname in item.content)
                    non_page += parse_item(cname, item.content[cname], child_passback);
                if (child_passback.tabs && child_passback.pages)
                    html = assemble_pages(id, child_passback.tabs, child_passback.pages);
                html = '<fieldset id="' + id + '" class="ksof_group"><legend>' + item.label + '</legend>' + html + non_page + '</fieldset>';
            }
            else if (_type == 'dropdown') {
                const item = _item;
                context.config_list[name] = item;
                let value = get_value(context, base, name);
                if (value === undefined) {
                    if (item.default !== undefined) {
                        value = item.default;
                    }
                    else {
                        value = Object.keys(item.content)[0];
                    }
                    set_value(context, base, name, value);
                }
                html = `<select id="${id}" name="${name}" class="setting"${to_title(item.hover_tip)}>`;
                for (cname in item.content)
                    html += '<option name="' + cname + '">' + escape_text(item.content[cname]) + '</option>';
                html += '</select>';
                html = make_label(item) + wrap_right(html);
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'list') {
                const item = _item;
                context.config_list[name] = item;
                let value = get_value(context, base, name);
                if (value === undefined) {
                    if (item.default !== undefined) {
                        value = item.default;
                    }
                    else {
                        if (item.multi === true) {
                            value = {};
                            Object.keys(item.content).forEach(function (key) {
                                value[key] = false;
                            });
                        }
                        else {
                            value = Object.keys(item.content)[0];
                        }
                    }
                    set_value(context, base, name, value);
                }
                let attribs = ' size="' + (item.size || Object.keys(item.content).length || 4) + '"';
                if (item.multi === true)
                    attribs += ' multiple';
                html = `<select id="${id}" name="${name}" class="setting list"${attribs}${to_title(item.hover_tip)}>`;
                for (cname in item.content)
                    html += '<option name="' + cname + '">' + escape_text(item.content[cname]) + '</option>';
                html += '</select>';
                html = make_label(item) + wrap_right(html);
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'checkbox') {
                const item = _item;
                context.config_list[name] = item;
                html = make_label(item);
                let value = get_value(context, base, name);
                if (value === undefined) {
                    value = (item.default || false);
                    set_value(context, base, name, value);
                }
                html += wrap_right('<input id="' + id + '" class="setting" type="checkbox" name="' + name + '">');
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'input') {
                const item = _item;
                const itype = item.subtype || 'text';
                context.config_list[name] = item;
                html += make_label(item);
                let value = get_value(context, base, name);
                if (value === undefined) {
                    const is_number = (item.subtype === 'number');
                    value = (item.default || (is_number ? 0 : ''));
                    set_value(context, base, name, value);
                }
                html += wrap_right(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ' placeholder="' + escape_attr(item.placeholder) + '"' : '')}>`);
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'number') {
                const item = _item;
                const itype = item.type;
                context.config_list[name] = item;
                html += make_label(item);
                let value = get_value(context, base, name);
                if (value === undefined) {
                    const is_number = (item.type === 'number');
                    value = (item.default || (is_number ? 0 : ''));
                    set_value(context, base, name, value);
                }
                html += wrap_right(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ' placeholder="' + escape_attr(item.placeholder) + '"' : '')}>`);
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'text') {
                const item = _item;
                const itype = item.type;
                context.config_list[name] = item;
                html += make_label(item);
                let value = get_value(context, base, name);
                if (value === undefined) {
                    value = (item.default || '');
                    set_value(context, base, name, value);
                }
                html += wrap_right(`<input id="${id}" class="setting" type="${itype}" name="${name}"${(item.placeholder ? ' placeholder="' + escape_attr(item.placeholder) + '"' : '')}>`);
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'color') {
                const item = _item;
                context.config_list[name] = item;
                html += make_label(item);
                let value = get_value(context, base, name);
                if (value === undefined) {
                    value = (item.default || '#000000');
                    set_value(context, base, name, value);
                }
                html += wrap_right('<input id="' + id + '" class="setting" type="color" name="' + name + '">');
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'button') {
                const item = _item;
                context.config_list[name] = item;
                html += make_label(item);
                const text = escape_text(item.text || 'Click');
                html += wrap_right('<button type="button" class="setting" name="' + name + '">' + text + '</button>');
                html = wrap_row(html, item.full_width, item.hover_tip);
            }
            else if (_type == 'divider') {
                html += '<hr>';
            }
            else if (_type == 'section') {
                const item = _item;
                html += '<section>' + (item.label || '') + '</section>';
            }
            else if (_type == 'html') {
                const item = _item;
                html += make_label(item);
                html += item.html;
                switch (item.wrapper) {
                    case 'row':
                        html = wrap_row(html, undefined, item.hover_tip);
                        break;
                    case 'left':
                        html = wrap_left(html);
                        break;
                    case 'right':
                        html = wrap_right(html);
                        break;
                }
            }
            return html;
            function make_label(item) {
                if (typeof item.label !== 'string')
                    return '';
                return wrap_left('<label for="' + id + '">' + item.label + '</label>');
            }
        }
        /* eslint-enable no-case-declarations */
        //============
        function assemble_pages(id, tabs, pages) { return '<div id="' + id + '" class="ksof_stabs"><ul>' + tabs.join('') + '</ul>' + pages.join('') + '</div>'; }
        function wrap_row(html, full, hover_tip) { return '<div class="row' + (full ? ' full' : '') + '"' + to_title(hover_tip) + '>' + html + '</div>'; }
        function wrap_left(html) { return '<div class="left">' + html + '</div>'; }
        function wrap_right(html) { return '<div class="right">' + html + '</div>'; }
        function escape_text(text) {
            return text.replace(/[<>]/g, (ch) => {
                if (ch == '<')
                    return '&lt';
                if (ch == '>')
                    return '&gt';
                return '';
            });
        }
        function escape_attr(text) { return text.replace(/"/g, '&quot;'); }
        function to_title(tip) { if (!tip)
            return ''; return ' title="' + tip.replace(/"/g, '&quot;') + '"'; }
    }
    function get_value(context, base, name) {
        const item = context.config_list[name];
        const evaluate = (item.path !== undefined);
        const path = (item.path || name);
        try {
            if (!evaluate)
                return base[path];
            return eval(path.replace(/@/g, 'base.'));
        }
        catch (e) {
            return;
        }
    }
    function set_value(context, base, name, value) {
        const item = context.config_list[name];
        const evaluate = (item.path !== undefined);
        const path = (item.path || name);
        try {
            if (!evaluate)
                return base[path] = value;
            let depth = 0;
            let new_path = '';
            let param = '';
            let c;
            for (let idx = 0; idx < path.length; idx++) {
                c = path[idx];
                if (c === '[') {
                    if (depth++ === 0) {
                        new_path += '[';
                        param = '';
                    }
                    else {
                        param += '[';
                    }
                }
                else if (c === ']') {
                    if (--depth === 0) {
                        new_path += JSON.stringify(eval(param)) + ']';
                    }
                    else {
                        param += ']';
                    }
                }
                else {
                    if (c === '@')
                        c = 'base.';
                    if (depth === 0)
                        new_path += c;
                    else
                        param += c;
                }
            }
            eval(new_path + '=value');
        }
        catch (e) {
            return;
        }
    }
    function install_anchor() {
        let anchor = $('#ksof_ds');
        if (anchor.length === 0) {
            anchor = $('<div id="ksof_ds"></div></div>');
            $('body').prepend(anchor);
            $('#ksof_ds').on('keydown keyup keypress', '.ksof_settings_dialog', function (e) {
                // Stop keys from bubbling beyond the background overlay.
                e.stopPropagation();
            });
        }
        return anchor;
    }
    //------------------------------
    // Load jquery UI and the appropriate CSS based on location.
    //------------------------------
    const css_url = ksof.support_files['jqui_ksmain.css'];
    ksof.include('Jquery');
    await ksof.ready('document, Jquery');
    await Promise.all([
        ksof.load_script(ksof.support_files['jquery_ui.js'], true /* cache */),
        ksof.load_css(css_url, true /* cache */)
    ]);
    ready = true;
    // Workaround...  https://community.wanikani.com/t/19984/55
    try {
        const temp = $.fn;
        delete temp.autocomplete;
    }
    catch (e) {
        // do nothing
    }
    // Notify listeners that we are ready.
    // Delay guarantees include() callbacks are called before ready() callbacks.
    setTimeout(function () { ksof.set_state('ksof.Settings', 'ready'); }, 0);
})(window);