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 `;
+ 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 ';
- 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