BlitzRhythm Editor Mod Loader

A BlitzRhythm Editor Mod Loader

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name        BlitzRhythm Editor Mod Loader
// @name:zh-CN  闪韵灵境谱面编辑器 模组加载器
// @namespace   cipher-editor-mods-loader
// @version     1.1.4
// @description A BlitzRhythm Editor Mod Loader
// @description:zh-CN  一款《闪韵灵境》谱面编辑器的Mod加载器
// @author      Moyuer
// @author:zh-CN 如梦Nya
// @license     MIT
// @grant       unsafeWindow
// @grant       GM_xmlhttpRequest
// @connect     beatsaver.com
// @connect     gitmirror.com
// @connect     githubusercontent.com
// @match       https://cipher-editor-cn.picovr.com/*
// @match       https://cipher-editor-va.picovr.com/*
// @icon        https://cipher-editor-va.picovr.com/favicon.ico
// ==/UserScript==

let htmlSrc = "https://raw.githubusercontent.com/CMoyuer/BlitzRhythm-Editor-Mod-Loader/main/ModLoaderDrawer/dist/index.html"

if (getLanguage() === "zh")
    htmlSrc = "https://raw.gitmirror.com/CMoyuer/BlitzRhythm-Editor-Mod-Loader/main/ModLoaderDrawer/dist/index.html"
if (GM_info.script.namespace.endsWith("-dev"))
    htmlSrc = "http://127.0.0.1"

/** @type {HTMLElement} */
let modloaderBox
/** @type {HTMLElement} */
let divMask
/** @type {HTMLElement} */
let iframe

function initModloaderBox() {
    if (modloaderBox) return
    modloaderBox = document.createElement("div")
    modloaderBox.style = "position:absolute;width:100%;height:100%;top:0;left:0;z-index:9001;pointer-events:none;"

    divMask = document.createElement("div")
    divMask.style = "width:100%;height:100%;background-color:#00000050;display:none;pointer-events:auto;"
    divMask.onclick = hideIframe
    modloaderBox.append(divMask)

    iframe = document.createElement("iframe")
    modloaderBox.id = "modloaderIframe"
    iframe.style = "box-shadow: 0 0 10px 0 black;border:none;width:360px;height:100vh;position:fixed;right:0;top:0;bottom:0;transform:translateX(100%);z-index:9999;transition: transform 0.3s ease-in-out;pointer-events: auto;"
    let loadHtml = () => {
        let url = htmlSrc + "?t=" + new Date().getTime()
        console.log("ModLoader loading html from:", url)
        GM_xmlhttpRequest({
            url,
            method: "GET",
            timeout: 10 * 1000,
            onload: res => {
                iframe.srcdoc = res.response
                console.log("ModLoader load html success!")
            },
            onerror: res => {
                console.error("ModLoader load html failed:", res)
                setTimeout(loadHtml, 1000)
            },
            ontimeout: res => {
                console.error("ModLoader load html timeout")
                loadHtml()
            }
        })
    }
    loadHtml()
    modloaderBox.append(iframe)
    document.body.append(modloaderBox)
}

function showIframe() {
    divMask.style.display = "block"
    iframe.style.transform = "translateX(0)"
}

function hideIframe() {
    divMask.style.display = "none"
    iframe.style.transform = "translateX(100%)"
}

function initShowButton() {
    let btnShow = document.createElement("div")
    btnShow.id = "btnModLoaderShow"
    btnShow.innerHTML = "M"
    btnShow.style = "position:absolute;transform:translate(-50%, -50%);left:calc(100vw - 50px);top:calc(100vh - 50px);width:50px;height: 50px;background-color:#2196F3;border-radius:25px;z-index:9000;font-size:1.5em;line-height:50px;text-align:center;color:white;font-family:Roboto,Helvetica,Arial,sans-serif;box-shadow: 0 0 6px 0 gray;user-select:none;"
    let info = {
        handle: 0,
        mousedown: false,
        dragging: false,
        canClick: true,
        rawPos: [0, 0],
        position: [0, 0],
    }

    function getMoveDistance() {
        return Math.abs(info.position[0] - info.rawPos[0]) + Math.abs(info.position[1] - info.rawPos[1])
    }

    btnShow.onmousedown = res => {
        btnShow.style.backgroundColor = "#1769AA"
        info.canClick = true
        info.mousedown = true
        info.handle = setTimeout(() => {
            btnShow.style.boxShadow = "0 0 6px 2px white"
            info.dragging = true
            info.handle = 0
        }, 100)
        if (btnShow.style.left && btnShow.style.left.startsWith("calc"))
            btnShow.style.left = btnShow.offsetLeft + "px"
        if (btnShow.style.top && btnShow.style.top.startsWith("calc"))
            btnShow.style.top = btnShow.offsetTop + "px"
        info.rawPos = [btnShow.offsetLeft, btnShow.offsetTop]
        info.position = [res.clientX, res.clientY]
    }
    btnShow.onmousemove = res => {
        if (!info.dragging) return
        let x = res.clientX
        let y = res.clientY
        let deltaX = x - info.position[0]
        let deltaY = y - info.position[1]
        let left = parseInt(btnShow.style.left || 0)
        let top = parseInt(btnShow.style.top || 0)
        btnShow.style.left = left + deltaX + 'px'
        btnShow.style.top = top + deltaY + 'px'
        info.position = [x, y]
    }
    btnShow.onmouseup = btnShow.onmouseleave = () => {
        btnShow.style.backgroundColor = "#2196F3"
        btnShow.style.boxShadow = "0 0 6px 0 gray"
        if (info.handle > 0) {
            clearTimeout(info.handle)
            info.handle = 0
        }
        info.canClick = !info.dragging
        info.mousedown = false
        info.dragging = false
    }
    btnShow.onclick = () => {
        if (!info.canClick) return
        showIframe()
    }

    window.onresize = () => {
        let left = parseInt(btnShow.style.left || 0)
        let top = parseInt(btnShow.style.top || 0)
        if (window.innerWidth < left + 50)
            btnShow.style.left = "calc(100vw - 50px)"
        if (window.innerHeight < top + 50)
            btnShow.style.top = "calc(100vh - 50px)"
    }

    document.body.appendChild(btnShow)
}

function getLanguage() {
    let language = localStorage.getItem("i18nextLng") ?? "en"
    if (/^zh-?/.test(language)) language = "zh"
    return language
}

(function () {
    'use strict'
    initModloaderBox()

    let handle = setInterval(() => {
        if (!unsafeWindow.modloader) return
        unsafeWindow.modloader.drawer = {
            methods: {
                show: showIframe,
                hide: hideIframe
            }
        }
        initShowButton()
        clearInterval(handle)
    }, 100)
})();