R4 Settings

R4 Settings Library

Verzia zo dňa 13.12.2023. Pozri najnovšiu verziu.

Tento skript by nemal byť nainštalovaný priamo. Je to knižnica pre ďalšie skripty, ktorú by mali používať cez meta príkaz // @require https://update.greasyfork.org/scripts/482052/1294691/R4%20Settings.js

// ==UserScript==
// @name        R4 Settings
// @description R4 Settings Library
// @version     1.2
// ==/UserScript==


function R4Settings(options) {

    options = options || {};

    const images = R4Images();

    GM_addStyle(`
    /* css */

    /* Settings */

    .r4-settings {
        position: relative;
    }

    .r4-settings > ul {
        width: 350px;
        display: none;
        background: #313131;
        border-top: 0;
        position: absolute;
        top: 50px;
        left: 0px;
        white-space: nowrap;
        box-shadow: 0 5px 20px 0px #000;
        border-color: #222d33;
        border-style: solid;
        border-width: 3px 3px 3px 3px;
        padding: 5px 0 0 0;
    }
    .r4-settings > ul:before {
        content: '';
        display: block;
        position: absolute;
        top: -13px;
        left: 20px;
        width: 0;
        height: 0;
        border-left: 10px solid transparent;
        border-right: 10px solid transparent;
        border-bottom: 10px solid #222d33;
    }

    .r4-settings > ul:after {
        content: '';
        display: block;
        position: absolute;
        top: -9px;
        left: 21px;
        width: 0;
        height: 0;
        border-left: 9px solid transparent;
        border-right: 9px solid transparent;
        border-bottom: 9px solid #313131;
    }

    body.r4-settings-active .r4-settings > ul {
        display: block !important;
    }

    .r4-settings > ul > li,
    .r4-setting-submenu > ul > li {
        color: #777;
        font-size: 10px;
        font-weight: bold;
        margin: 0 !important;
        padding-left: 10px;
        padding-right: 10px;
        padding-top: 5px;
        padding-bottom: 5px;
        min-height: 30px;
    }


    .r4-settings > ul > li .r4-setting,
    .r4-setting-submenu > ul > li .r4-setting {
        display: inline-block;
        width: 100%;
    }

    .r4-settings > ul > li .r4-tumbler,
    .r4-setting-submenu > ul > li .r4-tumbler {
        float: right;
    }

    .r4-settings .r4-setting-header {
        text-align: center;
    }

    .r4-settings .r4-setting-text-value {
        display: block;
        opacity: .5;
    }

    .r4-settings .r4-setting-text-block {
        float: left;
        position: relative;
        padding-top: 5px;
    }

    .r4-setting-submenu {
        position: relative;
        cursor: pointer;
    }

    .r4-setting-submenu > ul {
        background: #212121;
        margin: 30px -10px 0;
        padding: 10px 0;
        cursor: auto;
    }

    .r4-settings > ul > li:last-child .r4-setting-submenu > ul {
        margin-bottom: -5px;
    }

    .r4-setting-submenu-arrow {
        float: right;
        width: 15px;
        height: 15px;
        margin-right: 10px;
        margin-top: 5px;
        background-size: 15px 15px;
        background-repeat: no-repeat;
        background-image: url(${images.arrow});
        filter: invert(100%) sepia(95%) saturate(21%) hue-rotate(280deg) brightness(106%) contrast(106%);
        transform: rotate(180deg);
    }

    /* Tumbler */

    .r4-tumbler {
        width: 38px;
        height: 30px;
        background-color: #000;
        border: #1d92b2;
        border-radius: 30px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 6px;
        cursor: pointer;
        position: relative;
        user-select: none;
        box-sizing: content-box;
    }
    .r4-tumbler-point {
        border-radius: 50%;
        content: '';
        display: block;
        height: 20px;
        width: 20px;
        background-color: #999;
        background-clip: content-box;
        box-sizing: border-box;
        border-color: transparent;
        border-style: solid;
        border-width: 5px;
    }
    .r4-tumbler > .r4-tumbler-dot {
        position: absolute;
        height: 20px;
        width: 20px;
        border-radius: 50%;
        background-color: #fff;
        transition: transform .5s,background-color .5s;
        will-change: transform;
    }

    /* Tumbler On-Off */

    .r4-on-of-tumbler .r4-tumbler-point:nth-child(1) {
        background-color: green;
    }
    .r4-on-of-tumbler .r4-tumbler-point:nth-child(2) {
        background-color: indianred;
    }

    /* Tumbler Settings */

    .r4-tumbler-settings {
        width: 40px !important;
    }
    .r4-tumbler-settings .r4-tumbler-point {
        background-size: 15px 15px;
        background-repeat: no-repeat;
        background-position: center;
        border-width: 2px;
    }
    .r4-tumbler-settings .r4-tumbler-point:nth-child(1) {
        background-image: url('${images.settings}');
        background-color: transparent !important;
    }
    .r4-tumbler-settings .r4-tumbler-point:nth-child(2) {
        background-image: url('${images.settingsclose}');
        background-color: transparent !important;
    }

    .r4-tumbler-settings-update,
    .r4-tumbler-settings-update:hover {
        height: 30px;
        background: #f4363630;
        position: absolute;
        left: 0;
        margin-left: 30px;
        margin-top: 5px;
        border-radius: 30px;
        color: #b44b44 !important;
        line-height: 30px;
        padding: 0 20px 0 40px;
        cursor: pointer;
        text-decoration: none;
    }

    /* Tooltip */

    .r4-tooltip {
        position: relative;
        display: inline-block;
    }

    .r4-tooltip .tooltiptext {
        background: #313131;
        border-top: 0;
        position: absolute;
        top: -10px;
        left: 35px;
        white-space: nowrap;
        box-shadow: 0 5px 20px 0px #000;
        border-color: #222d33;
        border-style: solid;
        border-width: 3px;
        visibility: hidden;
        width: 300px;
        white-space: normal;
        padding: 15px;
        position: absolute;
        z-index: 3;
    }

    .r4-tooltip:hover .tooltiptext {
        visibility: visible;
    }

    .r4-tooltip .tooltiptext:before {
        content: '';
        display: block;
        position: absolute;
        left: -13px;
        top: 11px;
        width: 0;
        height: 0;
        border-top: 10px solid transparent;
        border-bottom: 10px solid transparent;
        border-right: 10px solid #222d33;
    }

    .r4-tooltip .tooltiptext:after {
        content: '';
        display: block;
        position: absolute;
        left: -9px;
        top: 12px;
        width: 0;
        height: 0;
        border-top: 9px solid transparent;
        border-bottom: 9px solid transparent;
        border-right: 9px solid #222d33;
    }

    .r4-tooltip-icon {
        border-radius: 50%;
        background: #777;
        width: 14px;
        height: 14px;
        display: inline-block;
        text-align: center;
        color: #000;
        text-transform: lowercase;
        cursor: pointer;
        font-family: monospace, monospace;
        font-size: 13px;
        margin: 8px;
    }

    /* !css */
    `);

    var tumbler;

    setup();

    function setSetting(name, value) {
        GM_setValue(name, value);
        console.debug(`Saved setting ${name}: ${JSON.stringify(value)}`);
    }

    function deleteSetting(name) {
        GM_deleteValue(name);
    }

    function getSetting(name) {
        let value = GM_getValue(name);
        if (value !== undefined) {
            console.debug(`Got setting ${name}: ${JSON.stringify(value)}`);
        } else {
            value = options.missingSettingHandler?.(name);
        }
        return value;
    }

    function setCongigSetting(config, option) {
        if (option.value !== undefined) {
            setSetting(config.name, option.value);
        } else {
            deleteSetting(config.name);
        }
    }

    function getConfigSetting(config) {
        return getSetting(config.name);
    }

    function getDefaultOption(config) {
        for (const tumblerOption of config.options) {
            if (tumblerOption.default === true) {
                return tumblerOption;
            }
        }
        return config.options[0];
    }

    function setBodyClass(config, option) {
        for (const tumblerOption of config.options) {
            if (tumblerOption.class) {
                document.body.classList.remove(tumblerOption.class);
            }
        }

        if (option?.class) {
            document.body.classList.add(option.class);
        }
    }

    function getNextOption(config, option) {
        let nextOptionIndex;
        if (option) {
            const currentOptionIndex = config.options.indexOf(option);
            if (currentOptionIndex < config.options.length - 1) {
                nextOptionIndex = currentOptionIndex + 1;
            } else {
                nextOptionIndex = 0;
            }
        } else {
            nextOptionIndex = 1;
        }
        return config.options[nextOptionIndex];
    }

    function getCurrentOption(config) {
        const currentSetting = getConfigSetting(config);

        for (const tumblerOption of config.options) {
            const optionSetting = tumblerOption.value;
            if (optionSetting === currentSetting) {
                return tumblerOption;
            }
        }

        const option = getDefaultOption(config);
        setCongigSetting(config, option);
        return option;
    }

    function rotateSetting(config) {
        const currentOption = getCurrentOption(config);
        const nextOption = getNextOption(config, currentOption);
        setCongigSetting(config, nextOption);
        setBodyClass(config, nextOption);
        if (nextOption.reload === true) {
            document.location.reload();
        }
        if (nextOption.start) {
            nextOption.start();
        }
        if (nextOption.end) {
            nextOption.end();
        }
    }

    function initSetting(config) {
        const currentOption = getCurrentOption(config);
        setBodyClass(config, currentOption);
        if (currentOption?.start) {
            currentOption.start();
        }
        if (currentOption?.end) {
            document.addEventListener("DOMContentLoaded", currentOption.end);
        }
    }

    function setup() {
        tumbler = buildTumbler({
            handler: toggle,
            name: "settings",
            classes: [],
            options: [
                {
                    class: null,
                },
                {
                    class: "r4-settings-active",
                },
            ],
        });
        tumbler.classList.add("r4-settings");
        tumbler.classList.add("pull-right");

        const dropdown = document.createElement("ul");
        tumbler.appendChild(dropdown);

        const item = document.createElement("div");
        item.classList.add("r4-setting-header");

        if (options.script_homepage) {
            const name = document.createElement("div");
            name.classList.add("r4-setting-label");
            name.innerHTML = `<a href="${options.script_homepage}" target="_blank">${GM_info.script.name}</a>`;
            item.appendChild(name);

            const feedback = document.createElement("div");
            feedback.classList.add("r4-setting-text-value");
            feedback.innerHTML = `<a href="${options.script_homepage}/feedback" target="_blank">${options.feedback_text || "Feedback"}</a>`;
            item.appendChild(feedback);

            GM_xmlhttpRequest({
                method: "GET",
                url: options.script_homepage,
                onload(response) {
                    console.debug(response);
                    if (response.status === 200) {
                        const patern =
                            /<a class="install-link" [^>]* data-script-version="(?<version>[^"]*)" [^>]* href="(?<href>[^"]*)"[^>]*>/;
                        const results = patern.exec(response.responseText);
                        if (results?.groups) {
                            if (results.groups.version != GM_info.script.version) {
                                const updateURL = results.groups.href;
                                const updateTumbler = document.createElement("a");
                                updateTumbler.href = updateURL;
                                updateTumbler.classList.add("r4-tumbler-settings-update");
                                updateTumbler.innerText = options.update_text || "Update";

                                tumbler.insertBefore(updateTumbler, tumbler.firstChild);
                            }
                        } else {
                            console.debug(`Failed to parse install link`);
                        }
                    }
                },
                onerror(e) {
                    console.debug(`Failed to request install link`);
                    console.debug(e);
                },
            });

        } else {

            const name = document.createElement("div");
            name.classList.add("r4-setting-label");
            name.innerHTML = GM_info.script.name;
            item.appendChild(name);

        }

        const version = document.createElement("div");
        version.classList.add("r4-setting-text-value");
        version.innerHTML = `${options.version_text || "Version"}: ${GM_info.script.version}`;
        item.appendChild(version);

        addElementSetting(item);

        document.addEventListener("click", close);
    }

    function toggle(event) {
        document.body.classList.toggle("r4-settings-active");
        event.stopPropagation();
        event.preventDefault();
    }

    function close(event) {
        if (!event.target.closest(".r4-settings")) {
            document.body.classList.remove("r4-settings-active");
        }
    }

    function findSubmenu(config) {
        const submenuAll = tumbler.querySelectorAll(".r4-setting-submenu");
        const submenuFiltered = Array.from(submenuAll).find(
            (el) => el.querySelector(".r4-setting-label").textContent === config.submenu
        );
        if (submenuFiltered) {
            return submenuFiltered.querySelector("ul");
        }
    }

    function createSubmenu(config) {
        const settingTextBlock = buildSettingTextBlock(config.submenu);
        const submenu = document.createElement("ul");
        submenu.addEventListener("click", (event) => {
            event.stopPropagation();
        });
        submenu.classList.add("hidden");
        const submenuArrow = document.createElement("span");
        submenuArrow.classList.add("r4-setting-submenu-arrow");
        const submenuElem = document.createElement("div");
        submenuElem.classList.add("r4-setting");
        submenuElem.classList.add("r4-setting-submenu");
        submenuElem.appendChild(settingTextBlock);
        submenuElem.appendChild(submenuArrow);
        submenuElem.appendChild(submenu);
        const submenuItem = document.createElement("li");
        submenuItem.appendChild(submenuElem);
        submenuItem.addEventListener("click", (event) => {
            submenu.classList.toggle("hidden");
        });
        const dropdown = tumbler.querySelector("ul");
        dropdown.appendChild(submenuItem);
        return submenu;
    }

    function addElementSetting(element, config) {
        let container;

        if (config?.submenu) {
            let submenu = findSubmenu(config);
            if (!submenu) {
                submenu = createSubmenu(config);
            }
            container = submenu;
        } else {
            const dropdown = tumbler.querySelector("ul");
            container = dropdown;
        }

        const item = document.createElement("li");
        item.appendChild(element);
        container.appendChild(item);
    }

    function buildTumbler(config) {
        const optionsLength = config.options.length;
        const tumblerClassName = `r4-tumbler-${config.name}`;

        GM_addStyle(`
        /* css */

        .${tumblerClassName} {
            width: ${optionsLength * 15 + optionsLength * 5}px !important;
        }

        /* !css */
        `);

        const tumblerWrapper = document.createElement("div");
        tumblerWrapper.classList.add("r4-tumbler-wrapper");

        const tumbler = document.createElement("div");
        tumbler.classList.add("r4-tumbler");
        tumbler.classList.add(tumblerClassName);
        tumbler.className += ` ${config.classes.join(" ")}`;

        tumbler.addEventListener("click", config.handler);

        for (let optionIndex = 0; optionIndex < optionsLength; optionIndex++) {
            const tumblerOption = config.options[optionIndex];
            const tumblerPoint = document.createElement("div");
            tumblerPoint.classList.add("r4-tumbler-point");
            tumbler.appendChild(tumblerPoint);

            if (tumblerOption.class !== null) {
                // Add dot move style for all points except initial
                const enabledClassName = tumblerOption.class;

                GM_addStyle(`
                /* css */

                .${enabledClassName} .${tumblerClassName} .r4-tumbler-dot {
                    transform: translateX(${optionIndex * 100}%);
                }

                /* !css */
                `);
            }
        }

        const tumblerDot = document.createElement("div");
        tumblerDot.classList.add("r4-tumbler-dot");
        tumbler.appendChild(tumblerDot);

        tumblerWrapper.appendChild(tumbler);

        return tumblerWrapper;
    }

    function buildSettingTextBlock(label) {
        const settingTextBlock = document.createElement("div");
        settingTextBlock.classList.add("r4-setting-text-block");

        const labelSpan = document.createElement("span");
        labelSpan.classList.add("r4-setting-label");
        labelSpan.innerHTML = label;

        settingTextBlock.appendChild(labelSpan);

        return settingTextBlock;
    }

    function buildTumblerSetting(config) {
        for (const tumplerOption of config.options) {
            if (tumplerOption.class === undefined && tumplerOption.value !== undefined && tumplerOption.value !== null) {
                tumplerOption.class = config.name + "-" + tumplerOption.value;
            }
            if (tumplerOption.value === undefined && tumplerOption.class !== undefined) {
                tumplerOption.value = tumplerOption.class;
            }
        }

        initSetting(config);
        const originalHandler = config.handler;
        config.handler = (event) => {
            rotateSetting(config);
            originalHandler?.(event);
        };

        const tumblerWrapper = buildTumbler(config);
        tumblerWrapper.classList.add("r4-setting");

        const settingClass = `r4-setting-${config.name}`;
        tumblerWrapper.classList.add(settingClass);

        const settingTextBlock = buildSettingTextBlock(config.label);

        const defaultSelectors = [];
        for (const tumplerOption of config.options) {
            if (tumplerOption.class !== null) {
                defaultSelectors.push(`body.${tumplerOption.class} .${settingClass} .r4-setting-text-value-1`);
            }
        }

        for (const tumplerOption of config.options) {
            const optionIndex = config.options.indexOf(tumplerOption);
            const textValueClass = `r4-setting-text-value-${optionIndex + 1}`;

            const textValueSpan = document.createElement("span");
            textValueSpan.classList.add("r4-setting-text-value");
            textValueSpan.classList.add(textValueClass);
            textValueSpan.innerHTML = tumplerOption.text;
            settingTextBlock.appendChild(textValueSpan);

            if (optionIndex == 0) {
                GM_addStyle(`
                /* css */

                ${defaultSelectors.join(",")} {
                    display: none !important;
                }

                /* !css */
                `);
            } else {
                const enabledClassName = tumplerOption.class;
                GM_addStyle(`
                /* css */

                body:not(.${enabledClassName}) .${settingClass} .${textValueClass} {
                    display: none !important;
                }

                /* !css */
                `);
            }
        }

        tumblerWrapper.appendChild(settingTextBlock);

        return tumblerWrapper;
    }

    function createTumblerSetting(config) {
        const tumblerSetting = buildTumblerSetting(config);
        addElementSetting(tumblerSetting, config);
    }

    return {
        tumbler,
        buildTumblerSetting,
        createTumblerSetting,
        addElementSetting,
        setSetting,
        getSetting,
    }
}