Greasy Fork 还支持 简体中文。

R4 Settings

R4 Settings Library

目前為 2023-12-13 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://updategf.qytechs.cn/scripts/482052/1294691/R4%20Settings.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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,
    }
}