Greasy Fork is available in English.

KameSame Open Framework - Settings module

Settings module for KameSame Open Framework

Tính đến 06-10-2022. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greasyfork.org/scripts/451521/1101691/KameSame%20Open%20Framework%20-%20Settings%20module.js

// ==UserScript==
// @name        KameSame Open Framework - Settings module
// @namespace   timberpile
// @description Settings module for KameSame Open Framework
// @version     0.1
// @copyright   2022+, Robin Findley, Timberpile
// @license     MIT; http://opensource.org/licenses/MIT
// ==/UserScript==
(async function (global) {
    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) {
            // if (!config.content) config.content = config.settings; // TODO what is config.settings?
            this.cfg = config;
            this.config_list = {};
            this.open_dialog = $();
            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 = this.open_dialog.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 (typeof this.cfg.on_save === 'function')
                this.cfg.on_save(ksof.settings[this.cfg.script_id]);
            // ksof.trigger('ksof.settings.save'); // TODO what should this do?
            this.keep_settings = true;
            this.open_dialog.dialog('close');
        }
        //------------------------------
        // Cancel button handler.
        //------------------------------
        cancel_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
            this.open_dialog.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 (this.open_dialog.length > 0)
                return;
            install_anchor();
            if (this.cfg.background !== false)
                this.background.open();
            this.open_dialog = $('<div id="ksofs_' + this.cfg.script_id + '" class="ksof_settings" style="display:none;"></div>');
            this.open_dialog.html(config_to_html(this));
            const resize = (event, ui) => {
                const is_narrow = this.open_dialog.hasClass('narrow');
                ui;
                if (is_narrow && ui.size.width >= 510) {
                    this.open_dialog.removeClass('narrow');
                }
                else if (!is_narrow && ui.size.width < 490) {
                    this.open_dialog.addClass('narrow');
                }
            };
            const tab_activated = () => {
                const wrapper = $(this.open_dialog.dialog('widget'));
                if ((wrapper.outerHeight() || 0) + wrapper.position().top > document.body.clientHeight) {
                    this.open_dialog.dialog('option', 'maxHeight', document.body.clientHeight);
                }
            };
            let width = 500;
            if (window.innerWidth < 510) {
                width = 280;
                this.open_dialog.addClass('narrow');
            }
            this.open_dialog.dialog({
                title: this.cfg.title,
                buttons: [
                    { text: 'Save', click: this.save_btn.bind(this) },
                    { text: 'Cancel', click: this.cancel_btn.bind(this) }
                ],
                width: width,
                maxHeight: document.body.clientHeight,
                modal: false,
                autoOpen: false,
                appendTo: '#ksof_ds',
                resize: resize.bind(this),
                close: () => { this.close(false); }
            });
            $(this.open_dialog.dialog('widget')).css('position', 'fixed');
            this.open_dialog.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 this.setting_changed(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, this.setting_changed.bind(this, e));
                }
            };
            this.open_dialog.dialog('open');
            this.open_dialog.find('.setting[multiple]').on('mousedown', toggle_multi.bind(this));
            this.open_dialog.find('.setting').on('change', this.setting_changed.bind(this));
            this.open_dialog.find('form').on('submit', function () { return false; });
            this.open_dialog.find('button.setting').on('click', setting_button_clicked.bind(this));
            if (typeof this.cfg.pre_open === 'function')
                this.cfg.pre_open(this.open_dialog);
            this.reversions = deep_merge({}, ksof.settings[this.cfg.script_id]);
            this.refresh();
            //============
        }
        //------------------------------
        // Handler for live settings changes.  Handles built-in validation and user callbacks.
        //------------------------------
        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;
        }
        //------------------------------
        // 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;
            this.open_dialog.dialog('destroy');
            this.open_dialog = $();
            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 = this.open_dialog.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]);
        }
    }
    // TODO find better name than SettingsClass. SettingsObj?
    function createSettingsObj() {
        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 = createSettingsObj();
    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);