From a7de885905ea4654b58f0f5f9f7cb84013e5e029 Mon Sep 17 00:00:00 2001 From: lol2302 <61654742+lol2302v0@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:12:57 +0700 Subject: [PATCH 1/2] Update wplace++.user.js --- wplace++.user.js | 1000 +++++++++++++++++++++++++++------------------- 1 file changed, 597 insertions(+), 403 deletions(-) diff --git a/wplace++.user.js b/wplace++.user.js index c0c1a7a..417b570 100644 --- a/wplace++.user.js +++ b/wplace++.user.js @@ -1,9 +1,9 @@ // ==UserScript== -// @name wPlace++ +// @name wPlace++ // @namespace https://rooot.gay -// @version 0.1.4 -// @description fixes the map not loading, and adds a couple other map related QoL features :3 -// @author rooot +// @version 0.1.5 +// @description Fix map not loading, QoL + safe UI panel (no fullscreen overlay), save prefs, avoid paint blackout & script conflicts. +// @author rooot + fix:grok.com // @updateURL https://github.com/RoootTheFox/wplace-plusplus/raw/refs/heads/main/wplace++.user.js // @downloadURL https://github.com/RoootTheFox/wplace-plusplus/raw/refs/heads/main/wplace++.user.js // @match https://wplace.live/* @@ -12,458 +12,652 @@ // @run-at document-start // ==/UserScript== -// util funcs taken from another project of mine +/*** --- Utils --- ***/ function mk_log(level) { + try { let c = "black"; let dbg = false; switch (level) { - case "err": c = "red"; break; - case "inf": c = "lime"; break; - case "wrn": c = "yellow"; break; - case "dbg": c = "orange"; dbg = true; break; + case "err": c = "red"; break; + case "inf": c = "lime"; break; + case "wrn": c = "yellow"; break; + case "dbg": c = "orange"; dbg = true; break; } if (dbg && !unsafeWindow.mk_enable_dbg) return; - let base_stuff = ["%c[wplace++] %c[" + level + "]", "color: pink", "color: " + c]; - let stuff = [...base_stuff, ...arguments]; - stuff.splice(base_stuff.length, 1); - console.log.apply("%c[wplace++]", stuff); + const base = ["%c[wplace++] %c[" + level + "]", "color:pink", "color:" + c]; + const stuff = [...base, ...arguments]; + // remove duplicated "level" from arguments + stuff.splice(base.length, 1); + // IMPORTANT: apply to console, not a string + console.log.apply(console, stuff); + } catch (_) { + // never let logging break + } } -function mk_update_visibility() { - mk_log("dbg", "updating visibility!"); - - if (!document.getElementById("mk_menu")) { - mk_log("err", "mk_update_visibility: menu MISSING"); - return; - } - let mk_menu = document.getElementById("mk_menu"); - if (!document.getElementById("mk_btn")) { - mk_log("err", "mk_update_visibility: button MISSING"); +/*** --- App --- ***/ +(function () { + const usw = unsafeWindow; + + // --- Map safety wrappers --- + // Wrap Map methods to avoid throwing when layers don't exist. + function wrapMapSafety() { + try { + const MapProto = (unsafeWindow && unsafeWindow.mapboxgl && unsafeWindow.mapboxgl.Map) ? unsafeWindow.mapboxgl.Map.prototype : (unsafeWindow && unsafeWindow.Map ? unsafeWindow.Map.prototype : null); + if (!MapProto) { + // fallback: if _map exists later, we'll wrap its instance methods + if (unsafeWindow._map && !unsafeWindow._map.__meow_safe_wrapped) { + const _m = unsafeWindow._map; + ['setLayoutProperty','setPaintProperty','moveLayer','setStyle','addLayer'].forEach(fn => { + if (typeof _m[fn] === 'function') { + const orig = _m[fn].bind(_m); + _m[fn] = function() { + try { + if (['setLayoutProperty','setPaintProperty'].includes(fn)) { + const layer = arguments[0]; + if (!this.getLayer || !this.getLayer(layer)) return; + } + if (fn === 'moveLayer') { + const layer = arguments[0]; + const before = arguments[1]; + if (!this.getLayer || !this.getLayer(layer)) return; + // Additional check for before layer if specified + if (before && (!this.getLayer(before))) return; + } + return orig.apply(this, arguments); + } catch (e) { console.warn('[wplace++] safeMap.'+fn+' suppressed', e); } + }; + } + }); + unsafeWindow._map.__meow_safe_wrapped = true; + } return; - } - let mk_btn = document.getElementById("mk_btn"); - if (unsafeWindow._meow_ui) { - mk_log("dbg", "mk_update_visibility: menu open TRUE"); - mk_menu.style.display = "unset"; - } else { - mk_log("dbg", "mk_update_html: menu open FALSE"); - mk_menu.style.display = "none"; - } -} - -function mk_menu_create_category(name) { - let cat = document.createElement("div"); - cat.className = "mk_menu_category"; - let cat_title = document.createElement("h4"); - cat_title.innerHTML = name; - cat_title.className = "mk_menu_category-title"; - cat.appendChild(cat_title); - let cat_content = document.createElement("div"); - cat_content.className = "mk_menu_category-content"; - cat.appendChild(cat_content); - cat.content = cat_content; // ref for easy access :3c - - document.getElementById("mk_menu").appendChild(cat); - return cat; + } + if (MapProto.__meow_safe_wrapped) return; + ['setLayoutProperty','setPaintProperty','moveLayer','addLayer'].forEach(fn => { + if (typeof MapProto[fn] === 'function') { + const orig = MapProto[fn]; + MapProto[fn] = function() { + try { + if (['setLayoutProperty','setPaintProperty'].includes(fn)) { + const layer = arguments[0]; + if (!this.getLayer || !this.getLayer(layer)) return; // skip silently + } + if (fn === 'moveLayer') { + const layer = arguments[0]; + const before = arguments[1]; + if (!this.getLayer || !this.getLayer(layer)) return; + // Additional check for before layer if specified + if (before && (!this.getLayer(before))) return; + } + return orig.apply(this, arguments); + } catch (e) { + console.warn('[wplace++] safeMap.'+fn+' suppressed', e); + } + }; + } + }); + MapProto.__meow_safe_wrapped = true; + } catch (e) { console.warn('[wplace++] wrapMapSafety failed', e); } + } + // attempt early wrap + try { wrapMapSafety(); } catch(e) { console.warn('[wplace++] initial wrap failed', e); } + + const LEFT_SIDEBAR_SELECTOR = ".absolute.left-2.top-2.z-30.flex.flex-col.gap-3"; + const LS_KEYS = { + UI_OPEN: "meow_ui_open", + THEME: "meow_theme", + UI_THEME: "meow_ui_theme", + HIDE_PREFIX: "meow_hideElement_", // + element id + }; + + /* ---- Themes ---- */ + usw._meow_themes = { + liberty: { path: "/styles/liberty" }, + dark: { path: "/styles/dark" }, + bright: { path: "/styles/bright" }, + positron: { path: "/styles/positron" }, + fiord: { path: "/styles/fiord" }, + }; + + usw._meow_ui_themes = { + default: { display: "default (light)", css: "" }, + "ctp-mocha": { + display: "catppuccin mocha (dark) [beta]", + css: ` +:root { + --color-base-100:#1e1e2e; + --color-base-content:white; + --color-base-200:#181825; + --color-base-300:#11111b; + --fx-noise:; } -function mk_menu_create_button(category, title, onclick) { - let button = document.createElement("button"); - button.classList.add("btn"); - button.innerHTML = title; - button.onclick = () => { onclick(button) }; - category.content.appendChild(button); - return button; +[data-rich-colors="true"][data-sonner-toast][data-type="error"], +[data-rich-colors="true"][data-sonner-toast][data-type="error"] [data-close-button]{ + --error-bg:var(--color-base-100); + --error-border:var(--color-base-100); + --error-text:#f38ba8; } - -/// START OF ACTUAL USERSCRIPT /// -(function() { - const usw = unsafeWindow; - - const LEFT_SIDEBAR_SELECTOR = ".absolute.left-2.top-2.z-30.flex.flex-col.gap-3"; - - // theming stuff :3 - - /// THEMES ARE DEFINED HERE /// - usw._meow_themes = { - "liberty": { // light, default theme - path: "/styles/liberty" - }, - "dark": { // dark, maybe hard to read - path: "/styles/dark" - }, - "bright": { - path: "/styles/bright" - }, - "positron": { - path: "/styles/positron" - }, - "fiord": { - path: "/styles/fiord" - } - }; - - usw._meow_ui_themes = { - "default": { - display: "default (light)" - }, - "ctp-mocha": { - display: "catppuccin mocha (dark) [beta]", - css: ` - :root { - --color-base-100: #1e1e2e; - --color-base-content: white; - --color-base-200: #181825; - --color-base-300: #11111b; - - /* fix buttons looking janky */ - --fx-noise:; +`, + }, + }; + + function getTheme() { + let id = localStorage.getItem(LS_KEYS.THEME) || "liberty"; + if (!usw._meow_themes.hasOwnProperty(id)) { + mk_log("wrn", "Unknown theme", id, "→ fallback liberty"); + id = "liberty"; } - [data-rich-colors="true"][data-sonner-toast][data-type="error"], [data-rich-colors="true"][data-sonner-toast][data-type="error"] [data-close-button] { - /* popups/toasts */ - --error-bg: var(--color-base-100); - --error-border: var(--color-base-100); - --error-text: #f38ba8; + const t = { ...usw._meow_themes[id], name: id }; + return t; + } + + // New function to ensure theme applies after load (moved outside of getTheme) + function applySavedTheme() { + try { + const themeId = localStorage.getItem(LS_KEYS.THEME) || null; + if (!themeId) return; + const theme = usw._meow_themes[themeId] || getTheme(); + const host = theme.host || "ofm.rooot.gay"; + // If map available, set style and wait for style to load before trying further styling. + const doSet = (map) => { + try { + map.setStyle("https://" + host + theme.path); + // ensure safety wrappers for instance as well + if (!map.__meow_safe_wrapped) { + ['setLayoutProperty','setPaintProperty','moveLayer','addLayer'].forEach(fn => { + if (typeof map[fn] === 'function') { + const orig = map[fn].bind(map); + map[fn] = function() { + try { + if (['setLayoutProperty','setPaintProperty'].includes(fn)) { + const layer = arguments[0]; + if (!this.getLayer || !this.getLayer(layer)) return; + } + if (fn === 'moveLayer') { + const layer = arguments[0]; + const before = arguments[1]; + if (!this.getLayer || !this.getLayer(layer)) return; + // Additional check for before layer if specified + if (before && (!this.getLayer(before))) return; + } + return orig.apply(this, arguments); + } catch (e) { console.warn('[wplace++] safeMap.'+fn+' suppressed', e); } + }; + } + }); + map.__meow_safe_wrapped = true; + } + } catch(e) { console.warn('[wplace++] applySavedTheme.setStyle failed', e); } + }; + if (usw._map && usw._map.setStyle) doSet(usw._map); + else { + const iv = setInterval(() => { + if (usw._map && usw._map.setStyle) { + clearInterval(iv); + doSet(usw._map); + } + }, 300); + } + } catch (e) { mk_log('err','applySavedTheme failed',e); } + } + + function getUITheme() { + let id = localStorage.getItem(LS_KEYS.UI_THEME) || "default"; + if (!usw._meow_ui_themes.hasOwnProperty(id)) { + mk_log("wrn", "Unknown UI theme", id, "→ fallback default"); + id = "default"; } - ` - } - }; - - usw._meow_ui = false; - - // in global context for now - usw.setTheme = function setTheme(theme) { - localStorage.setItem("meow_theme", theme); - if (usw._map && usw._map.setStyle) { - usw._map.setStyle("https://ofm.rooot.gay"+usw._meow_themes[theme].path); - usw._map.fire("style.load"); - } else { - usw.location.reload(); - } - }; - - function getTheme() { - let current_theme_id = localStorage.getItem("meow_theme"); - if (current_theme_id == undefined) current_theme_id = "liberty"; // default theme - - // just in case, so we dont end up with an empty map! - if (!usw._meow_themes.hasOwnProperty(current_theme_id)) { - mk_log("err", "THEME "+current_theme_id+" DOES NOT EXIST! falling back to liberty"); - current_theme_id = "liberty"; + const t = { ...usw._meow_ui_themes[id], name: id, css: usw._meow_ui_themes[id].css || "" }; + return t; + } + + usw.setTheme = function setTheme(theme) { + try { + localStorage.setItem(LS_KEYS.THEME, theme); + if (usw._map && usw._map.setStyle) { + const t = usw._meow_themes[theme]; + const host = t.host || "ofm.rooot.gay"; + usw._map.setStyle("https://" + host + t.path); + usw._map.fire("style.load"); + } else { + applySavedTheme(); + } + } catch (e) { + mk_log("err", "setTheme failed", e); + } + }; + + usw.setUITheme = function setUITheme(theme) { + localStorage.setItem(LS_KEYS.UI_THEME, theme); + const el = document.getElementById("meow_ui_theme"); + if (el) el.textContent = getUITheme().css; + }; + + /* ---- Persist UI open state ---- */ + usw._meow_ui = (localStorage.getItem(LS_KEYS.UI_OPEN) === "true"); + + function setUIOpen(state) { + usw._meow_ui = !!state; + localStorage.setItem(LS_KEYS.UI_OPEN, usw._meow_ui ? "true" : "false"); + mk_update_visibility(); + } + + usw._meow_ui = localStorage.getItem("meow_ui_open") === "true"; + // toggle + function toggleUI(state) { + usw._meow_ui = state; + localStorage.setItem("meow_ui_open", state); + mk_update_visibility(); + } + /* ---- Safe fetch patch (conflict-aware) ---- */ + if (!usw.fetchIsPatched) { + try { + usw.patches_orig = usw.patches_orig || {}; + usw.patches_orig.fetch = usw.fetch; + usw.originalFetch = window.fetch; // raw window fetch + + const patchedFetch = async function (req, ...args) { + try { + let url = (typeof req === "string") ? req : req.url; + const parsed = new URL(url, location.origin); + const isMap = parsed.host === "maps.wplace.live" || parsed.host === "tiles.openfreemap.org"; + + if (isMap) { + parsed.host = "ofm.rooot.gay"; + } + + const theme = getTheme(); + if (isMap && parsed.pathname === "/styles/liberty") { + parsed.pathname = theme.path; + if (theme.host) parsed.host = theme.host; + } + + // minor cache-buster (kept as in original) + parsed.pathname = parsed.pathname.replace("/ ", "//"); + + // replace request + req = (typeof req === "string") + ? parsed.toString() + : new Request(parsed.toString(), req); + + // BlueMarble compat + if (typeof usw.bmfetchPatch === "function") { + mk_log("dbg", "BlueMarble compat in use"); + return await usw.bmfetchPatch(usw.originalFetch, req, ...args); + } + return usw.originalFetch(req, ...args); + } catch (e) { + mk_log("err", "patchedFetch error", e); + // hard fallback to native if anything goes wrong + return usw.originalFetch(req, ...args); } - - let current_theme = usw._meow_themes[current_theme_id]; - current_theme.name = current_theme_id; - return current_theme; + }; + + usw.fetch = patchedFetch; + window.fetch = patchedFetch; + usw.fetchIsPatched = true; + window.fetchIsPatched = true; + } catch (e) { + mk_log("err", "Failed to patch fetch", e); } - - function getUITheme() { - let current_theme_id = localStorage.getItem("meow_ui_theme"); - if (current_theme_id == undefined) current_theme_id = "default"; - - if (!usw._meow_ui_themes.hasOwnProperty(current_theme_id)) { - mk_log("err", "UI THEME "+current_theme_id+" DOES NOT EXIST! falling back to default"); - current_theme_id = "default"; + } else { + mk_log("dbg", "fetch already patched – skipping"); + } + + /* ---- Capture map Promise (as-is but safe) ---- */ + try { + if (!usw.__meowPromisePatched) { + usw.patches_orig = usw.patches_orig || {}; + usw.patches_orig.Promise = usw.Promise; + class PawsomePromise extends Promise { + constructor(exec) { + super(exec); + try { + if (typeof exec === "function" && exec.toString().includes("maps.wplace.live")) { + this.then((map) => { + mk_log("inf", "map exposed"); + usw._map = map; + // restore Promise to original after capture + usw.Promise = usw.patches_orig.Promise; + usw.__meowPromisePatched = false; + }); + } + } catch { /* ignore */ } } - - let current_theme = usw._meow_ui_themes[current_theme_id]; - current_theme.name = current_theme_id; - if (current_theme.css == undefined) current_theme.css = ""; - return current_theme; + } + usw.Promise = PawsomePromise; + usw.__meowPromisePatched = true; } + } catch (e) { + mk_log("wrn", "Promise patch failed", e); + } - usw.setUITheme = function setUITheme(theme) { - localStorage.setItem("meow_ui_theme", theme); - document.getElementById("meow_ui_theme").innerHTML = getUITheme().css; - }; + /* ---- UI injection ---- */ - /// FIXES BELOW /// - usw.patches_orig = {}; - - // hook fetch :3 - usw.patches_orig.fetch = usw.fetch; - usw.originalFetch = window.fetch; // different context - let patchedFetch = async function(req, ...args) { - let url; - let req_is_string = typeof req == "string"; - if (req_is_string) { - url = req; - } else { - url = req.url; - } + function mk_update_visibility() { + const menu = document.getElementById("mk_menu"); + const btn = document.getElementById("mk_btn"); + if (!menu || !btn) return; - let new_url = new URL(url); - let is_map_request = new_url.host == "maps.wplace.live" || new_url.host == "tiles.openfreemap.org"; - if (is_map_request) { - new_url.host = "ofm.rooot.gay"; - mk_log("dbg", "this request is now fetching from a different instance like a good girl >~<"); - } + if (usw._meow_ui) { + menu.style.display = "block"; + menu.style.pointerEvents = "auto"; + } else { + menu.style.display = "none"; + menu.style.pointerEvents = "none"; + } + } - let theme = getTheme(); - if (is_map_request && new_url.pathname == "/styles/liberty") { - new_url.pathname = theme.path; - new_url.host = theme.host == undefined ? new_url.host : theme.host; - } + function mk_menu_create_category(name) { + const cat = document.createElement("div"); + cat.className = "mk_menu_category"; + const title = document.createElement("h4"); + title.textContent = name; + title.className = "mk_menu_category-title"; + cat.appendChild(title); + const content = document.createElement("div"); + content.className = "mk_menu_category-content"; + cat.appendChild(content); + cat.content = content; + document.getElementById("mk_menu").appendChild(cat); + return cat; + } - new_url.pathname = new_url.pathname.replace("/ ", "//"); // annoy cf cache a bit + function mk_menu_create_button(category, title, onclick) { + const button = document.createElement("button"); + button.type = "button"; + button.classList.add("btn"); + button.textContent = title; + button.addEventListener("click", () => onclick(button)); + category.content.appendChild(button); + return button; + } - // replace with our "fixed" url - if (req_is_string) { - req = new_url.toString(); - } else { - req = new Request(new_url.toString(), req); - } + function createButton() { + if (document.getElementById("mk_btn")) return; + const left = document.querySelector(LEFT_SIDEBAR_SELECTOR); + if (!left) return; // wait until it exists - // if map loading breaks with Blue Marble, its most likely NOT skipping map requests - if (usw.bmfetchPatch != undefined) { // blue marble compat ??? - mk_log("dbg", "ATTEMPTING BM COMPAT"); - return await usw.bmfetchPatch(usw.originalFetch, req, ...args); - } else { - // we use this fetch here because the original fetch on the unsafe Window (actual window) causes - // illegal invokation on chrom(e|ium) - on ff its fine but oh well, this works. - return usw.originalFetch(req, ...args); - } - }; + mk_log("inf", "creating meow button"); + const container = document.createElement("div"); + container.classList.add("max-sm"); + left.appendChild(container); - usw.fetch = patchedFetch; - window.fetch = patchedFetch; + const button = document.createElement("button"); + button.classList.add("btn", "btn-sm", "btn-circle"); + button.id = "mk_btn"; + button.title = "wPlace++"; + button.addEventListener("click", () => setUIOpen(!usw._meow_ui)); + container.appendChild(button); - // BM compat - usw.fetchIsPatched = true; - window.fetchIsPatched = true; + // icon + button.innerHTML = ``; + } - // get map instance (thanks @cgytrus <3) - usw.patches_orig.Promise = usw.Promise; - let patchedPromise = class PawsomePromise extends Promise { - constructor(exec) { - super(exec); - if (exec.toString().includes("maps.wplace.live")) { - mk_log("inf", "caught map promise >:3c"); - this.then((map) => { - mk_log("inf", "map exposed !! >:3"); - usw._map = map; - usw.Promise = usw.patches_orig.Promise; - }); - } - } - } - usw.Promise = patchedPromise; - - /// load UI themes /// - let ui_style = document.createElement("style"); - ui_style.id = "meow_ui_theme"; - ui_style.innerHTML = getUITheme().css; - document.body.appendChild(ui_style); - - function createButton() { - if (document.getElementById("mk_btn")) return; - - mk_log("inf", "creating button"); - let left_sidebar = document.querySelector(LEFT_SIDEBAR_SELECTOR) - let button_container = document.createElement("div"); - button_container.classList.add("max-sm"); - left_sidebar.appendChild(button_container); - - let button = document.createElement("button"); - button.classList.add("btn", "btn-sm", "btn-circle"); - button.id = "mk_btn"; - button.onclick = () => { - usw._meow_ui = !usw._meow_ui; - mk_update_visibility(); - } - button_container.appendChild(button); - // sparkles icon - let svg = ``; - button.innerHTML = svg; - } + function injectUI() { + mk_log("inf", "injecting UI"); - function injectUI() { - mk_log("inf", "injecting ui bwawawa :3"); + createButton(); - createButton(); + // ESC closes menu & save + document.addEventListener("keydown", (e) => { + if (e.key === "Escape") setUIOpen(false); + }, { capture: true }); - // close UI on ESC - document.body.addEventListener('keydown', function(e) { - if (e.key == "Escape") { - usw._meow_ui = false; - mk_update_visibility(); - } + // build panel (NOT fullscreen overlay) + if (!document.getElementById("mk_menu")) { + const menu = document.createElement("div"); + menu.id = "mk_menu"; + menu.style.display = "none"; + document.body.appendChild(menu); + + const title = document.createElement("h3"); + title.className = "mk_menu-title"; + title.innerHTML = `wPlace++ v0.1.5-fix by rooot`; + menu.appendChild(title); + + const cat_wplace = mk_menu_create_category("wplace"); + const cat_other = mk_menu_create_category("other"); + const cat_misc = mk_menu_create_category("misc"); + + // Map theme + const spanTheme = document.createElement("span"); + spanTheme.textContent = "map theme: "; + const selectTheme = document.createElement("select"); + Object.keys(usw._meow_themes).forEach((id) => { + const opt = document.createElement("option"); + opt.value = id; + opt.textContent = id; + selectTheme.appendChild(opt); + }); + selectTheme.value = getTheme().name; + selectTheme.addEventListener("change", (e) => usw.setTheme(e.target.value)); + cat_wplace.content.append(spanTheme, selectTheme); + + // UI theme + cat_wplace.content.appendChild(document.createElement("br")); + const spanUITheme = document.createElement("span"); + spanUITheme.textContent = "ui theme: "; + const selectUI = document.createElement("select"); + Object.keys(usw._meow_ui_themes).forEach((id) => { + const opt = document.createElement("option"); + opt.value = id; + opt.textContent = usw._meow_ui_themes[id].display; + selectUI.appendChild(opt); + }); + selectUI.value = getUITheme().name; + selectUI.addEventListener("change", (e) => usw.setUITheme(e.target.value)); + cat_wplace.content.append(spanUITheme, selectUI); + + function createElementToggleButton(cat, text, elementId) { + const lsKey = LS_KEYS.HIDE_PREFIX + elementId; + const rule = `#${CSS.escape(elementId)}{display:none !important}`; + let styleEl = document.getElementById(lsKey); + + const ensureStyle = () => { + if (!styleEl) { + styleEl = document.createElement("style"); + styleEl.id = lsKey; + styleEl.textContent = rule; + } + return styleEl; + }; + + mk_menu_create_button(cat, text, () => { + const exists = document.getElementById(elementId) !== null; + if (!exists) { + mk_log("wrn", "toggle target not found yet:", elementId); + // still allow persisting preference; style will apply when element appears + } + if (document.getElementById(lsKey)) { + // show + document.getElementById(lsKey).remove(); + localStorage.setItem(lsKey, "false"); + mk_log("dbg", "showing element", elementId); + } else { + // hide + document.body.appendChild(ensureStyle()); + localStorage.setItem(lsKey, "true"); + mk_log("dbg", "hiding element", elementId); + } }); - // build the UI (this will be hidden by default) - let meow_menu = document.createElement("div"); - meow_menu.id = "mk_menu"; - meow_menu.style.display = "none"; - document.body.appendChild(meow_menu); - - let meow_menu_title = document.createElement("h3"); - meow_menu_title.className = "mk_menu-title"; - meow_menu_title.innerHTML = "wPlace++ " + "v0.1.4" + ' by rooot'; - meow_menu.appendChild(meow_menu_title); - - let cat_wplace = mk_menu_create_category("wplace"); - let cat_other_scripts = mk_menu_create_category("other"); - let cat_misc = mk_menu_create_category("misc"); - - // add theming settings :3 - let bwa = document.createElement("span"); - bwa.innerText = "set map theme: "; - let meow_menu_themeselect = document.createElement("select"); - for (let theme of Object.keys(usw._meow_themes)) { - console.log(theme); - let theme_option = document.createElement("option") - theme_option.value = theme; - theme_option.innerText = theme; - meow_menu_themeselect.appendChild(theme_option); - } - meow_menu_themeselect.onchange = (v) => { usw.setTheme(v.srcElement.value) }; - meow_menu_themeselect.value = getTheme().name; // make sure we have the current active theme selected - cat_wplace.appendChild(bwa); - cat_wplace.appendChild(meow_menu_themeselect); - - // ui themes - let mrrp = document.createElement("br"); - cat_wplace.appendChild(mrrp); - let bwaa = document.createElement("span"); - bwaa.innerText = "set ui theme: "; - let meow_menu_ui_themeselect = document.createElement("select"); - for (let theme of Object.keys(usw._meow_ui_themes)) { - console.log(theme); - let theme_option = document.createElement("option") - theme_option.value = theme; - theme_option.innerText = usw._meow_ui_themes[theme].display; - meow_menu_ui_themeselect.appendChild(theme_option); - } - meow_menu_ui_themeselect.onchange = (v) => { usw.setUITheme(v.srcElement.value) }; - meow_menu_ui_themeselect.value = getUITheme().name; - cat_wplace.appendChild(bwaa); - cat_wplace.appendChild(meow_menu_ui_themeselect); - - function createElementToggleButton(cat, text, sel) { - let lsKeyHidden = `meow_hideElement_${sel}`; - let hideCss = `#${sel} { display: none !important; }`; - let hider = document.createElement("style"); - hider.id = lsKeyHidden; - hider.innerHTML = hideCss; - - mk_menu_create_button(cat, text, function () { - mk_log("inf", "toggling element!"); - if (document.getElementById(sel) == undefined) { - mk_log("err", "element not found!"); - localStorage.setItem(lsKeyHidden, false); - return; - } - - let existingHider = document.getElementById(lsKeyHidden); - if (existingHider) { - mk_log("dbg", "showing element!"); - existingHider.parentNode.removeChild(existingHider); - localStorage.setItem(lsKeyHidden, false); - } else { - mk_log("dbg", "hiding element!"); - document.body.appendChild(hider); - localStorage.setItem(lsKeyHidden, true); - } - }); - - if (localStorage.getItem(lsKeyHidden) == "true") document.body.appendChild(hider); + // apply persisted state + if (localStorage.getItem(lsKey) === "true") { + document.body.appendChild(ensureStyle()); } - - createElementToggleButton(cat_other_scripts, "toggle Blue Marble visibility", "bm-n"); - createElementToggleButton(cat_other_scripts, "toggle Overlay Pro visibility", "overlay-pro-panel"); - - mk_menu_create_button(cat_misc, "CLOSE THIS MENU", function () { - mk_log("inf", "closing~"); - usw._meow_ui = false; - mk_update_visibility(); - }); - - /// INJECT MENU STYLESHEET INTO DOCUMENT /// - let style = document.createElement("style"); - style.innerHTML = ` -:root { - --mk-accent: #f5c2e7; - --mk-crust-raw: 17, 17, 27; - --mk-crust: rgb(var(--mk-crust-raw)); - --mk-mantle: #181825; - --mk-base: #1e1e2e; - --mk-text: #cdd6f4; - --mk-surface: #313244; - - --meow-padding: 12px; + } + + // Common other tools + createElementToggleButton(cat_other, "toggle Blue Marble visibility", "bm-n"); + createElementToggleButton(cat_other, "toggle Overlay Pro visibility", "overlay-pro-panel"); + + mk_menu_create_button(cat_misc, "Close menu", () => setUIOpen(false)); + + // Panel CSS (no fullscreen, no screen-dimming) + const style = document.createElement("style"); + style.textContent = ` +:root{ + --mk-accent:#f5c2e7; + --mk-crust-raw:17,17,27; + --mk-crust:rgb(var(--mk-crust-raw)); + --mk-mantle:#181825; + --mk-base:#1e1e2e; + --mk-text:#cdd6f4; + --mk-surface:#313244; + --meow-padding:12px; } -/* yippie menu */ +/* compact floating panel */ #mk_menu { position: fixed; - width: 100vw; - height: 100vh; - top: 0; - left: 0; - padding-top: 6px; - background-color: rgba(var(--mk-crust-raw), 0.5); - backdrop-filter: blur(4px); - + right: 10px; + top: 60px; + width: 300px; + max-height: 80vh; + overflow-y: auto; + background-color: rgba(var(--mk-crust-raw), 0.95); + backdrop-filter: blur(6px); + border-radius: 12px; z-index: 10000; - color: var(--mk-text); + display: none; /* ẩn mặc định */ + color:var(--mk-text); } -.mk_menu-title { - font-size: x-large; - font-weight: bold; - color: var(--mk-accent); - margin-left: var(--meow-padding); +#mk_menu.hidden { + display: none !important; } -.mk_menu-dev { - color: var(--mk-accent); - text-decoration: underline; +.mk_menu-title{ + font-size:18px; + font-weight:700; + color:var(--mk-accent); + margin:6px 8px 10px 8px; } -.mk_menu_category { - backdrop-filter: blur(4px); - padding-top: 8px; - padding-bottom: 8px; - padding-left: var(--meow-padding); - border-radius: var(--meow-padding); - background-color: rgba(var(--mk-crust-raw), 0.5); - - margin-bottom: 6px; - margin-left: 6px; - margin-right: 6px; +.mk_menu-dev{ + color:var(--mk-accent); + text-decoration:underline; } -.mk_menu_category-title { - font-size: large; - font-weight: bold; - color: var(--mk-accent); +.mk_menu_category{ + backdrop-filter:blur(4px); + padding:8px 10px; + border-radius:12px; + background-color:rgba(var(--mk-crust-raw),.6); + margin:8px; } -/* fix wacky button */ -.mk_menu_category-content button { - margin-right: var(--meow-padding); +.mk_menu_category-title{ + font-size:15px; + font-weight:700; + color:var(--mk-accent); + margin-bottom:6px; +} + +.mk_menu_category-content .btn{ + margin-right:8px; + margin-top:6px; +} + +/* when hidden, do not capture input */ +#mk_menu[style*="display: none"]{ + pointer-events:none !important; } `; - document.body.appendChild(style); + document.body.appendChild(style); - // something *really* likes getting rid of our button - setInterval(() => { - createButton(); - }, 150); + // Inject UI theme