BlitzRhythm Editor Mod Loader

A BlitzRhythm Editor Mod Loader

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

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

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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)
})();