wplace - simple map browser

use openstreetmap to navigate wplace faster! press M to pull up the map, and right click to visit that place

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         wplace - simple map browser
// @namespace    http://pawing.cv
// @version      v1.2
// @description  use openstreetmap to navigate wplace faster! press M to pull up the map, and right click to visit that place
// @author       cv
// @match        https://wplace.live/*
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

	// i promise the comment hiding the actual html is eye candy and not done with malicious intent 😇 (i mean, you can see the exact code it's inserting below)
    function openmap() {
        let popupcode = `<!--wplace.live-map----------------------------------------------------------------------------------->
<!DOCTYPE html>
<html lang="en">
<head>
    <title>OpenStreetMap</title>
    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy" content="default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;">
    <style>
        @import url("https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap");
        html, body {
            height: 100%; line-height: 1; margin: 0; padding: 0;
            font-family: "Geist", ui-sans-serif, system-ui, sans-serif !important;
        }

        #loading {
            position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background: #f2f7fe; padding: 20px; border-radius: 32px;
            font-size: 16px; font-weight: 500; color: #394e6a;
            box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); z-index: 1000;
        }

        .map {
            height: 100%; width: 100%; display: none;
        }

        a, span, p, li, input, ul {
            font-family: Geist, ui-sans-serif, system-ui, sans-serif !important;
            user-select: none;
        }
        .coords, .small {user-select: all !important}

        .info {
            display: flex; flex-direction: row; position: fixed;
            justify-content: center; align-items: center;
            top: 1em; left: 50%; transform: translateX(-50%);
            background: #f2f7fe; padding: 10px; border-radius: 32px;
            font-size: 13.5px; font-weight: 700; z-index: 1000;
            box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
            pointer-events: none; user-select: none; display: none;
        }
        .info svg {
            padding-left: 3px; padding-right: 2px; width: 15px;
        }
        .coords {
            display: flex; flex-direction: column; position: fixed; width: 300px;
            bottom: 1em; left: 50%; transform: translateX(-50%);
            background: #f2f7fe; display: none;
            padding: 20px; border-radius: 32px;
            font-size: 18px; z-index: 1000; font-weight: 500;
            box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
        }
        .small {
            font-size: 13px; color: #394e6a; opacity: 0.5;
        }

        /* Leaflet control styling */
        div.leaflet-control-container > div.leaflet-top.leaflet-left > div {
            border: none;
        }
        div.leaflet-control-container > div.leaflet-top.leaflet-left > div > a {
            display: flex; flex-direction: column; background: #f2f7fe;
            border-radius: 32px; margin-bottom: 3px; font-size: 13px;
            box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
        }

        div.leaflet-top.leaflet-right * { border: none; }
        div.leaflet-top.leaflet-right > div > button,
        div.leaflet-top.leaflet-right > div {
            display: flex; flex-direction: column; background: #f2f7fe;
            border-radius: 32px; margin-bottom: 3px; color: #394e6a;
        }
        div.leaflet-top.leaflet-right > div > button { display: none; }
        div.leaflet-top.leaflet-right > div > div.leaflet-control-geocoder-form {
            display: inline-block; width: 200px; padding: 10px;
            box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1);
        }
        div.leaflet-top.leaflet-right > div > .leaflet-control-geocoder-alternatives {
            border-radius: 1px; padding: 10px;
        }
        div.leaflet-top.leaflet-right > div:has(.leaflet-control-geocoder-alternatives:not([style*="display: none"])) > .leaflet-control-geocoder-form {
            box-shadow: none !important;
        }
        body > div.map.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom > div.leaflet-control-container > div.leaflet-bottom.leaflet-right {
            display: none !important;
        }
        .leaflet-control-geocoder-form-no-error {
            text-align: center; color: rgb(179, 97, 97) !important;
            padding-bottom: 10px;
        }
    </style>
</head>
<body>
    <div id="loading">Loading map...</div>
    <div class="info">Choose a location with <svg alt="Right Click" fill="#394e6a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path d="M38 0h4v6h-4V0Zm9 1 4 2c-2 4-2 4-7 5 1-5 1-5 3-7ZM36 5c8 6 9 12 9 22v6c-2 10-5 14-14 19-10 1-16 1-23-6-4-7-5-11-5-19v-6C5 4 21-3 36 5ZM9 17l-1 9h14V7c-7 0-10 5-13 10ZM8 30c2 9 3 13 11 17 11 0 14-1 20-10l1-7H8Zm38-20h6v4h-6v-4Z"/></svg>!</div>
    <div class="coords"></div>
    <div class="map" style="width:100%;height:100%"></div>

    <script>
        function newcss(url) {
            return new Promise((resolve, reject) => {
                const link = document.createElement('link');
                link.rel = 'stylesheet';
                link.href = url;
                link.onload = resolve;
                link.onerror = reject;
                document.head.appendChild(link);
            });
        }
        function newjs(url) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = url;
                script.onload = resolve;
                script.onerror = reject;
                document.head.appendChild(script);
            });
        }

        async function bootmap() {
            try {
                await newcss('https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css');
                await newjs('https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js');
                await newcss('https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css');
                await newjs('https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js');

                document.getElementById('loading').style.display = 'none';
                document.querySelector('.map').style.display = 'block';
                document.querySelector('.info').style.display = 'flex';
                document.addEventListener("contextmenu", function(e) {e.preventDefault()});

                var map = L.map(document.querySelector(".map"), {
                    worldCopyJump: false,
                    continuousWorld: false
                }).setView([0, 0], 1);

                map.setMaxBounds([[-85.05112878, -180], [85.05112878, 180]]);
                L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {noWrap: true}).addTo(map);

                map.on('drag', function() {
                    if (!map.getBounds().intersects(map.options.maxBounds)) {
                        map.panInsideBounds(map.options.maxBounds, { animate: false });
                    }
                });

                L.Control.geocoder({
                    geocoder: L.Control.Geocoder.nominatim()
                }).addTo(map);

                var lastlatlng = null;

                map.on("mousemove", function(e) {
                    lastlatlng = e.latlng;
                    var lat = e.latlng.lat;
                    var lng = e.latlng.lng;
                    var zoom = map.getZoom();

                    var tile_count = 2048;
                    var tile_size = 1000;
                    var pixel_count = tile_count * tile_size;

                    var x = (lng + 180) / 360;
                    var sinlat = Math.sin(lat * Math.PI / 180);
                    var y = 0.5 - Math.log((1 + sinlat) / (1 - sinlat)) / (4 * Math.PI);

                    x = Math.min(1, Math.max(0, x));
                    y = Math.min(1, Math.max(0, y));

                    var px = x * (pixel_count - 1);
                    var py = y * (pixel_count - 1);
                    var t_x = Math.floor(px / tile_size);
                    var t_y = Math.floor(py / tile_size);
                    var p_x = Math.floor(px % tile_size);
                    var p_y = Math.floor(py % tile_size);

                    document.querySelector(".coords").innerHTML =
                        lat.toFixed(6) + ", " + lng.toFixed(6) + " [x" + zoom + "] " +
                        "<br><span class='small'>" + t_x + ", " + t_y + " // " + p_x + ", " + p_y + "</span>";

                    var coords = document.querySelector(".coords");
                    if (coords.innerHTML.trim() !== "") {
                        coords.style.display = "flex";
                    } else {
                        coords.style.display = "none";
                    }
                });

                document.querySelector(".map").addEventListener("contextmenu", function(ev) {
                    ev.preventDefault();
                    if (lastlatlng) {
                        var osmzoom = map.getZoom();
                        var wplacezoom = Math.max(1, Math.min(19, osmzoom));

                        var wplaceUrl = 'https://wplace.live/?lat=' + lastlatlng.lat + '&lng=' + lastlatlng.lng + '&zoom=' + wplacezoom;
                        window.open(wplaceUrl, '_blank');
                        window.close();
                    }
                });

            } catch (error) {
                document.getElementById('loading').textContent = 'Error loading map: ' + error.message;
                console.error('map load error!', error);
            }
        }
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', bootmap);
        } else {bootmap()}
    </script>
</body>
</html>`;

	// the ENTIRE page has to fit inside a data url, since:
	// - blob:https://wplace.live inherits wplace.live's csp
	// - about:blank, uh, doesn't work either, fuck
        if (typeof GM_openInTab !== 'undefined') {
            const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(popupcode);
            GM_openInTab(dataUrl, { active: true });
        } else {
            const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(popupcode);
            const newTab = window.open(dataUrl, '_blank');

            if (!newTab) {
                alert("please allow popups for wplace.live..");
            }
        }
    }
	// that isn't an absurd workaround, is it? no? thank god

    function cleanlink() {
        const cleanlink = new URL(window.location.href);
        cleanlink.searchParams.delete('lat');
        cleanlink.searchParams.delete('lng');
        cleanlink.searchParams.delete('zoom');
        window.history.replaceState({}, '', cleanlink.pathname + cleanlink.search + cleanlink.hash);
    }
    window.addEventListener('DOMContentLoaded', function() {
        const url = new URL(window.location.href);
        if (url.searchParams.has('lat') || url.searchParams.has('lng') || url.searchParams.has('zoom')) {
            cleanlink();
        }
    });
    document.addEventListener('keydown', function(e) {
        if (e.key === 'M' || e.key === 'm') {
            openmap();
        }
    });
})();