BlitzRhythm Editor Mod Loader

A BlitzRhythm Editor Mod Loader

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

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