wplace - simple map browser

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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