BlitzRhythm Editor Mod Loader

A BlitzRhythm Editor Mod Loader

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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