LV WME Helper

Miscelannia

目前為 2022-01-21 提交的版本,檢視 最新版本

// ==UserScript==
// @name                LV WME Helper
// @namespace           https://dev.laacz.lv/
// @description         Miscelannia
// @include             https://www.waze.com/*/editor*
// @include             https://www.waze.com/editor*
// @include             https://beta.waze.com/*
// @exclude             https://www.waze.com/*user/*editor/*
// @require             https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
// @require             https://cdnjs.cloudflare.com/ajax/libs/jsts/2.0.6/jsts.min.js
// @license             CC-BY-4.0; https://creativecommons.org/licenses/by/4.0/
// @version             2.05
// @white
// @connect             waze.dev.laacz.lv
// @grant               GM.xmlHttpRequest
// ==/UserScript==
// noinspection DuplicatedCode

/* global W */
/* global WazeWrap */
/* global axios */

(function () {

    /**
     * @typedef Address
     * @property code {number}
     * @property name {string}
     * @property waze_house_number {string}
     * @property waze_name {string}
     * @property iela_name {string}
     * @property ciems_name {string}
     * @property pilseta_name {string}
     * @property pagasts_name {string}
     * @property novads_name {string}
     * @property full_name {string}
     * @property parent_code {number}
     * @property geom {object}
     * @property lat {float}
     * @property lng {float}
     * @property Point {Point}
     */

    /**
     * Fetched and mutated address list.
     * @type {Address[]}
     */
    let addresses = [];

    // Reassigned by requure()
    let UpdateObject = null;
    let Landmark = null;
    let AddLandmark = null;
    let DeleteSegment = null;

    // Regluar expressions for validating stuff
    const reStreetNames = /(^.+(aleja|apvedceļš|bulvāris|ceļš|dambis|gatve|iela|krastmala|laukums|līnija|prospekts|šķērslīnija|šoseja) (\d+.+$))/;
    const reHN = /^(\d+[a-zA-Z]*)( k-\d+)?$/;
    // Game loop's interval
    let updateTimeout = undefined;
    // Indicates that REST request is in progress
    let wmelvLoadingIndicator = true;

    function wmelvLoading(value) {
        if (value !== undefined) {
            wmelvLoadingIndicator = value;
            qs('#wmelv-loading-indicator').style.visibility = value ? 'visible' : 'hidden';
        }
        return wmelvLoadingIndicator;
    }

    /**
     * Stores last clicked point on the map
     * @type {Point|null}
     */
    let lastClick = null;

    /**
     * Settings object with getters, setters, and default values.
     */
    const settings = {
        configuration: {
            minHNZoomLevel: 16,
            hlIncorrectAddress: true,
            hlSmallArea: true,
            hlLowerCaseHN: true,
            hlNoHN: true,
            hlNameHNMismatch: true,
            addressFixer: false,
            hlDupes: true,
            hlIela: true,
            autoHN: false,
            // autoHNSegmentSelection: true,
        }, get: function (key, def) {
            return typeof this.configuration[key] !== 'undefined' ? this.configuration[key] : def;
        }, set: function (key, value) {
            this.configuration[key] = value;
            this.save();
        }, save() {
            if (localStorage) {
                localStorage.setItem("_wmelv3_settings", JSON.stringify(this.configuration));
            }
        }, load() {
            let loadedSettings = JSON.parse(localStorage.getItem("_wmelv3_settings"));
            this.config = Object.assign(this.configuration, loadedSettings ? loadedSettings : {});
        }
    };

    /**
     * querySelectorAll shorthand
     * @param selector
     * @returns {*[]}
     */
    function qsa(selector) {
        return Array.from(document.querySelectorAll(selector), e => e);
    }

    /**
     * querySelector shorthand.
     * @param selector
     * @returns {*}
     */
    function qs(selector) {
        return document.querySelector(selector);
    }

    /**
     * Shamelessly taken from WME PlaceNames Russian:
     * https://gf.qytechs.cn/en/scripts/15310-wme-placenames-russian/code
     */
    function fixPlaceArea(place) {
        let requiredArea = 516,
            oldGeometry = place.geometry.clone(),
            newGeometry = place.geometry.clone(),
            centerPT = newGeometry.getCentroid(),
            oldArea = oldGeometry.getGeodesicArea(W.map.getProjectionObject()),
            scale = Math.sqrt(requiredArea / oldArea);

        newGeometry.resize(scale, centerPT);

        let wazeActionUpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry"),
            action = new wazeActionUpdateFeatureGeometry(place, W.model.venues, oldGeometry, newGeometry);

        place.attributes.fixArea = false;

        W.model.actionManager.add(action);
    }

    function fixCurrentlySelectedArea(e) {
        if (e) e.preventDefault();

        if (!W.selectionManager.hasSelectedFeatures()
            || W.selectionManager.getSelectedFeatures()[0].model.type !== "venue"
            || !W.selectionManager.getSelectedFeatures()[0].model.isGeometryEditable()) {
            return;
        }

        fixPlaceArea(W.selectionManager.getSelectedFeatures()[0].model);
        wmelvUpdate();
    }

    /**
     * Colorful logger ;)
     * @type {{warn(...[*]): void, debug(...[*]): void, crit(...[*]): void, log(*, ...[*]): void, info(...[*]): void}}
     */
    const Logger = {
        log(style, ...args) {
            console.log("%c●%c", style, 'color: #000; background-color: #fff;', ...args)
        }, debug(...args) {
            Logger.log('background-color: gray; color: #fff; font-weight: bold; padding: .2em .5em;', ...args)
        }, info(...args) {
            Logger.log('background-color: navy; color: #fff; font-weight: bold; padding: .2em .5em;', ...args)
        }, crit(...args) {
            Logger.log('background-color: maroon; color: #fff; font-weight: bold; padding: .2em .5em;', ...args)
        }, warn(...args) {
            Logger.log('background-color: orange; color: #fff; font-weight: bold; padding: .2em .5em;', ...args)
        },
    }

    /**
     * Returns closest address to a given point on the map.
     * @return Address|null
     */
    function getClosestAddress(venue) {
        const centroid = venue.geometry.getCentroid();
        centroid.transform('EPSG:900913', "EPSG:4326");
        return addresses.length ? addresses.reduce((prev, curr) => {
            if (!prev) return curr;
            return prev.Point.distanceTo(centroid) < curr.Point.distanceTo(centroid) ? prev : curr;
        }) : null;
    }

    /**
     * Helper to build a bbox string from the map's extent.
     * @returns {string}
     */
    function wmelvBBOX() {
        const bounds = W.map.getExtent();
        bounds.transform("EPSG:900913", "EPSG:4326");
        return bounds.toBBOX();
    }

    /**
     * Adds stuff to addresses which cannot be added in the backend.
     * @param addresses
     * @returns {*}
     */
    function wmelvMutateAddresses(addresses) {
        if (addresses && addresses.length) {
            addresses = addresses.map((ads) => {
                ads.Point = new OpenLayers.Geometry.Point(ads.lng, ads.lat);
                return ads;
            })
        }
        return addresses;
    }

    /**
     * Creates a new place, given a geometry and attributes, selects it and registers in the actionmanager's history.
     * @param geometry
     * @param params
     * @returns {*}
     */
    function wmelvCreateNewPlace(geometry, params) {
        let place = new Landmark();
        if (!params) params = {};
        if (!params.categories || !params.categories.length) params.categories = ["OTHER"];

        place.geometry = geometry;
        place.attributes.name = params.name ? params.name : '';
        place.attributes.lockRank = params.lockRank ? params.lockRank : 2;
        params.categories.forEach(cat => {
            place.attributes.categories.push(cat);
        });

        place.attributes.entryExitPoints.push(new NavigationPoint(place.geometry.getCentroid()));

        W.model.actionManager.add(new AddLandmark(place));
        W.selectionManager.setSelectedModels([place]);

        if (params.address && Object.keys(params.address).length) {
            W.model.actionManager.add(new UpdateObject(place, params.address));
        }

        return place;
    }

    /**
     * Game loop as they say.
     */
    function wmelvUpdate() {
        if (updateTimeout) {
            clearTimeout(updateTimeout);
        }
        if (!wmelvLoading()) {
            Object.keys(W.model.venues.objects).forEach((k) => {
                // Object.values(W.model.venues.objects).forEach((v) => {
                const venue = W.model.venues.objects[k];
                const el = qs('#' + venue.geometry.id);

                if (!el) {
                    return
                }
                if ('attributes' in venue) {
                    let v = venue.attributes;
                    // Logger.info(v.attributes.name, 'closest address is', closest.full_name)
                    let hn = v.houseNumber;
                    let street = {name: ''};
                    let city = {name: ''};
                    if (v.streetID) {
                        street = W.model.streets.objects[v.streetID];
                    }
                    if (street) {
                        city = W.model.cities.objects[street.cityID].attributes
                    }
                    const full_address = `${hn}, ${street.name}, ${city.name}`;

                    const area = W.model.venues.objects[k].geometry.toString().indexOf('POLYGON') === 0
                        ? W.model.venues.objects[k].geometry.getGeodesicArea(W.map.getProjectionObject())
                        : false;

                    W.model.venues.objects[k].attributes.fixLowerCase =
                        (v.houseNumber && v.houseNumber.toUpperCase() !== v.houseNumber) ||
                        (v.houseNumber && v.name && v.name.toUpperCase() === v.houseNumber.toUpperCase() && v.name !== v.houseNumber.toUpperCase()) ||
                        (v.name && v.name.replace(/ k-\d+$/, '').match(/^\d+[a-z]+/) && v.name.replace(/ k-\d+$/, '').toUpperCase() !== v.name.replace(/ k-\d+$/, ''));

                    W.model.venues.objects[k].attributes.fixNameHNMismatch =
                        !v.residential &&
                        (
                            (v.houseNumber && !v.name) ||
                            (
                                v.houseNumber &&
                                v.name &&
                                v.name.match(reHN) &&
                                v.houseNumber.toLowerCase() !== v.name.toLowerCase().replace(' k-', '-'))
                        );

                    W.model.venues.objects[k].attributes.fixNoHN =
                        !v.houseNumber &&
                        v.streetID && W.model.streets.objects[v.streetID] &&
                        W.model.streets.objects[v.streetID].name &&
                        W.model.streets.objects[v.streetID].cityID &&
                        W.model.cities.objects[W.model.streets.objects[v.streetID].cityID] &&
                        W.model.cities.objects[W.model.streets.objects[v.streetID].cityID].attributes.name &&
                        v.categories.indexOf('PARKING_LOT') === -1 &&
                        v.categories.indexOf('TAXI_STATION') === -1 &&
                        v.categories.indexOf('PARK') === -1;


                    W.model.venues.objects[k].attributes.fixIela = v.name.toLowerCase().match(reStreetNames);
                    W.model.venues.objects[k].attributes.fixArea = !v.residential && area !== false && area < 550 ? area : false;

                    // wmelvCounts.fixArea += !!W.model.venues.objects[k].attributes.fixArea;
                    // wmelvCounts.fixLowerCase += !!W.model.venues.objects[k].attributes.fixLowerCase;
                    // wmelvCounts.fixIela += !!W.model.venues.objects[k].attributes.fixIela;

                    const closest = getClosestAddress(venue);
                    if (closest) {
                        W.model.venues.objects[k].attributes.fixAddress =
                            closest.waze_name !== full_address &&
                            v.categories.indexOf('PARKING_LOT') === -1 &&
                            v.categories.indexOf('TAXI_STATION') === -1 &&
                            v.categories.indexOf('PARK') === -1;
                        W.model.venues.objects[k].attributes.ads = closest;
                    }

                    // if (full_address !== closest.waze_name) {
                    //     Logger.warn(`[${full_address}] is not at [${closest.waze_name}]`)
                    //     el.setAttribute('stroke', '#ff0000');
                    //     el.setAttribute("stroke-width", "4");
                    //     el.setAttribute("stroke-dash-array", "none");
                    // }
                } else {
                    // Logger.warn(v.attributes.name, 'has no closest address')
                }
            });
            wmelvDraw();
        }
        updateTimeout = setTimeout(wmelvUpdate, 345);
    }

    /**
     * Updates visualizations.
     */
    function wmelvDraw() {
        Object.values(W.model.venues.objects).forEach((v) => {
            let el = qs('#' + v.geometry.id), hled = false;
            if (el) {
                if (!el.getAttribute('ogAttributes')) {
                    el.setAttribute('ogAttributes', {
                        'stroke': el.getAttribute('stroke'),
                        "stroke-width": el.getAttribute("stroke-width"),
                        "stroke-dash-array": el.getAttribute("stroke-dash-array"),
                    });
                }
                if (!hled && settings.get('hlIncorrectAddress') && v.attributes.fixAddress) {
                    el.setAttribute('stroke', '#ff00ff');
                    el.setAttribute("stroke-width", "4");
                    el.setAttribute("stroke-dash-array", "none");
                    hled = true;
                }
                if (!hled && ((settings.get('hlLowerCaseHN') && v.attributes.fixLowerCase) || (settings.get('hlNoHN') && v.attributes.fixNoHN) || (settings.get('hlIela') && v.attributes.fixIela))) {
                    el.setAttribute('stroke', '#ff0000');
                    el.setAttribute("stroke-width", "4");
                    el.setAttribute("stroke-dash-array", "none");
                    hled = true;
                }
                if (!hled && settings.get('hlSmallArea') && el && v.attributes.fixArea) {
                    el.setAttribute('stroke', '#0000ff');
                    el.setAttribute("stroke-width", "4");
                    el.setAttribute("stroke-dash-array", "none");
                    hled = true;
                }
                if (!hled && settings.get('hlNameHNMismatch') && el && v.attributes.fixNameHNMismatch) {
                    el.setAttribute('stroke', '#ffff00');
                    el.setAttribute("stroke-width", "4");
                    el.setAttribute("stroke-dash-array", "none");
                    hled = true;
                }
                if (!hled && settings.get('hlDupes') && el && v.attributes.fixDupes) {
                    el.setAttribute('stroke', '#00ffff');
                    el.setAttribute("stroke-width", "4");
                    el.setAttribute("stroke-dash-array", "none");
                    hled = true;
                }
                // const shouldRevert = !hled && el.getAttribute('ogAttributes').all((k, v) => el.getAttribute(k) === v);
                // if (!hled) {
                //     Logger.info('Reverting HL');
                //     const attrs = el.getAttribute('ogAttributes');
                //     el.setAttribute('stroke', attrs['stroke']);
                //     el.setAttribute("stroke-width", attrs["stroke-width"]);
                //     el.setAttribute("stroke-dash-array", attrs["stroke-dash-array"]);
                // }
            }
        });
    }

    /**
     * Fetches VZD addresses from an API. Restricts to viewport plus a tiny buffer.
     */
    function wmelvLoadAddresses() {
        if (!settings.get('autoHN') || W.map.getZoom() < 16) {
            if (W.map.getLayersByName("pointLayer").length) {
                W.map.removeLayer(W.map.getLayersByName("pointLayer")[0]);
            }
            return;
        }

        const request = {
            bounds: wmelvBBOX(),
        }

        wmelvLoading(true);
        GM.xmlHttpRequest({
            method: 'POST', url: '//waze.dev.laacz.lv/api/2/addresses', data: JSON.stringify(request), headers: {
                'Content-Type': 'application/json'
            }, onload: function (response) {
                addresses = JSON.parse(response.responseText);
                addresses = wmelvMutateAddresses(addresses)
                if (W.map.getLayersByName("pointLayer").length) {
                    W.map.removeLayer(W.map.getLayersByName("pointLayer")[0]);
                }
                const pointLayer = new OpenLayers.Layer.Vector("pointLayer");
                const proj = new OpenLayers.Projection("EPSG:4326");
                const features = [];

                for (const addr of addresses) {
                    const point = new OpenLayers.Geometry.Point(addr.lng, addr.lat).transform(proj, W.map.getProjectionObject());
                    const ft = new OpenLayers.Feature.Vector(point, null, null);
                    ft.style = {
                        label: addr.name,
                        pointRadius: 15,
                        fillColor: addr.color,
                        fillOpacity: 0.8,
                        strokeColor: "#cc6633",
                        strokeWidth: 2,
                        strokeOpacity: 0.8,
                        fontColor: "black",
                        labelOutlineColor: "white",
                        labelOutlineWidth: 3,
                    };
                    features.push(ft);
                }
                pointLayer.addFeatures(features);
                W.map.addLayer(pointLayer);
                wmelvLoading(false);
                wmelvUpdate();
            },
            onerror: () => {
                wmelvLoading(false);
            },
            onabort: () => {
                wmelvLoading(false);
            },
            ontimeout: () => {
                wmelvLoading(false);
            },
        });
    }

    /**
     * Triggered when selection changes (user selects or deselects a WME feature)
     * @param e {Event}
     */
    function wmeSelectionChanged(e) {
        if (e &&
            W.selectionManager.hasSelectedFeatures() &&
            W.selectionManager.getSelectedFeatures().length === 1 &&
            W.selectionManager.getSelectedFeatures()[0].model.type === "venue") {

            const venue = W.selectionManager.getSelectedFeatures()[0].model.attributes;

            let warnings = []
            let html = `<div class="action-buttons"><div class="alert addresses"></div>`;

            if (venue.fixAddress) warnings.push('Ēkas adrese, iespējams, nav korekta (tuvākā ir ' + venue.ads.waze_name + '; <a href="#" id="fixCurrentlySelectedVenueAddress">salabot</a>)');
            if (venue.fixLowerCase) warnings.push('Ēkas numurs satur mazo burtu (<a href="" id="fixThisLowerCase">salabot</a>)');
            if (venue.fixNameHNMismatch) warnings.push('Ēkas numurs nesakrīt ar numuru nosaukumā<br/>' + '(<a href="#" id="fixNameHNMismatchToAddress">pareiza adrese</a>, <a href="" id="fixNameHNMismatchToName">pareizs nosaukums</a>)');
            if (venue.fixIela) warnings.push('Vietas nosaukums satur "' + venue.name.match(reStreetNames)[0] + '" (<a href="#" id="fixIelaInName">salabot</a>)');
            if (venue.fixNoHN) warnings.push('Adrese ir, bet ēkas numura nav');
            if (venue.fixArea) warnings.push('Laukuma platība ' + Math.floor(venue.fixArea) + 'm² ir mazāka par 550m² (<a href="#" id="fixThisArea">salabot</a>)');

            html += warnings.map(warning => `<div class="alert alert-danger">${warning}</div>`).join('');

            html += '</div>';

            /**
             * Update landmark edit tab.
             */
            if (!qs('#wmelv-landmark-edit')) {
                let div = document.createElement('div');
                div.id = 'wmelv-landmark-edit';
                qs('#venue-edit-general').insertBefore(div, qs('#venue-edit-general').firstChild);
            }
            qs('#wmelv-landmark-edit').innerHTML = html;

            if (qs('#fixCurrentlySelectedVenueAddress')) {
                qs('#fixCurrentlySelectedVenueAddress').addEventListener('click', (e) => {
                    e.preventDefault();
                    let venue = W.selectionManager.getSelectedFeatures()[0];
                    let venue_id = venue.model.attributes.id;
                    const closest = getClosestAddress(venue);
                    // wmllvSetAddress(venue, closest);
                    const street = Object.values(W.model.streets.objects).find((street) => street.name === closest.iela_name);
                    const changes = {
                        name: closest.name.replace(' k-', '-'),
                        houseNumber: closest.iela_name ? closest.waze_house_number : null,
                        streetID: street ? street.id : null,
                    };

                    if (changes) W.model.actionManager.add(new UpdateObject(venue.model, changes));

                    W.selectionManager.unselectAll();
                    W.selectionManager.setSelectedModels([W.model.venues.objects[venue_id]]);
                })
            }

            if (qs('#fixNameHNMismatchToAddress')) {
                qs('#fixNameHNMismatchToAddress').addEventListener('click', (e) => {
                    e.preventDefault();
                    let venue = W.selectionManager.getSelectedFeatures()[0],
                        changes = {
                            name: venue.model.attributes.houseNumber.replace('-', ' k-'),
                        };

                    if (changes) W.model.actionManager.add(new UpdateObject(venue.model, changes));

                })
            }
            if (qs('#fixNameHNMismatchToName')) {
                qs('#fixNameHNMismatchToName').addEventListener('click', (e) => {
                    e.preventDefault();
                    let venue = W.selectionManager.getSelectedFeatures()[0],
                        changes = {
                            houseNumber: venue.model.attributes.name.replace(' k-', '-'),
                        };

                    if (changes) W.model.actionManager.add(new UpdateObject(venue.model, changes));

                })
            }

            if (qs('#fixThisArea')) {
                qs('#fixThisArea').addEventListener('click', fixCurrentlySelectedArea);
            }
            if (qs('#fixIelaInName')) {
                qs('#fixIelaInName').addEventListener('click', (e) => {
                    e.preventDefault();
                    let venue = W.selectionManager.getSelectedFeatures()[0],
                        name = venue.model.attributes.name,
                        changes = {
                            name: name.replace(reStreetNames, '$3'),
                        };

                    if (changes) W.model.actionManager.add(new UpdateObject(venue.model, changes));

                });

            }
            if (qs('#fixThisLowerCase')) {
                qs('#fixThisLowerCase').addEventListener('click', (e) => {
                    e.preventDefault();
                    let changes = {};
                    if (venue.attributes.houseNumber && venue.attributes.houseNumber.toUpperCase() !== venue.attributes.houseNumber) {
                        changes.houseNumber = venue.attributes.houseNumber.toUpperCase();
                    }
                    if (venue.attributes.name && venue.attributes.name.match(/^\d+[a-z][^a-z]*/)) {
                        changes.name = venue.attributes.name.toUpperCase().replace(' K-', ' k-');
                        if (venue.attributes.houseNumber) changes.houseNumber = venue.attributes.houseNumber.toUpperCase();
                    }
                    if (changes) W.model.actionManager.add(new UpdateObject(venue, changes));
                    wmelvUpdate();
                });
            }

        }
    }

    /**
     * Creates WME tab and registers all corresponding event listeners on inputs.
     */
    function wmelvRegisterTab() {
        let style = document.getElementById('wmelv-style');
        if (!style) {
            style = document.createElement('style');
            style.id = 'wmelv-style';
            document.head.appendChild(style);
        }
        style.innerText = `
        #wmelv-loading-indicator {
            text-align: center;
            display: inline-block;
        }

        #wmelv-loading-indicator path,
        #wmelv-loading-indicator rect {
            fill: #FF6700;
        }

        #sidepanel-wmelv hr {
            height: 1px;
            width: 100%;
            border-top: 1px solid #ccc;
        }

        #sidepanel-wmelv label {
            white-space: normal;
        }
        .waze-btn:disabled {
            cursor: no-drop;
        }

        #applyAllAddresses {
            text-align: center;
        }

        .count {
            background-color: #009900;
            color: #fff;
            font-size: 90%;
            border-radius: 25%;
            padding-left: .5rem;
            margin-left: 1rem;
            padding-right: .5rem;
        }


        #wmelv-landmark-edit .alert {
            margin-bottom: .25rem;
            text-transform: none;
            font-size: 12px;
        }

        #wmelv-landmark-edit .alert-danger {
            border: 1px solid #ed503b;
        }

        .alert .waze-btn.waze-btn-blue {
            box-shadow: none;
        }

        #zoom-level-warning {
            color: red;
            font-weight: bold;
        }

        .wme-lv-details {
            font-weight: bold;
            color: green;
        }

        #wmelv-convert-to-area {
            margin-top: .25rem;
        }

        #wmelv-venues-list ul {
            margin: 1rem;
            padding: 0;
        }

        #wmelv-venues-list ul li {
            list-style-type: none;
            margin: 0;
            padding: 0;
        }
        #wmelv-venues-list [data-type]:before {
            content: "";
            display: inline-block;
            position: relative;
            top: 1px;
            background-image: url(//editor-assets.waze.com/production/img/buttons756c103910d73f4d45328e08f6016871.png);
            width: 11px;
            height: 11px;
            margin-right: 5px;
        }
        #wmelv-venues-list [data-type="point"]:before {
            background-position: -49px -58px;
        }

        #wmelv-venues-list [data-type="area"]:before {
            background-position: -25px -58px;
        }

        #wmelv-venues-list li a {
            text-decoration: none;
        }

        .wmelv-lock-rank {
            box-shadow: 0 4px 4px 0 #def7ff;
            color: #fff;
            background: #32a852;
            height: 23px;
            line-height: 23px;
            margin-right: 1px;
            text-align: center;
            font-weight: normal;
            font-size: 13px;
            padding-left: 8px;
            padding-right: 8px;
            border-radius: 13px;
        }

        [data-lock-rank="0"] .wmelv-lock-rank,
        [data-lock-rank="1"] .wmelv-lock-rank {
            background: #ff7f00;
        }

        `;
        settings.load();

        const html = `
        <div class="form-group">
            <span id="zoom-level-warning" style="display: none;">Zoom par mazu. Funkcionalitāte atslēgta.</span>
        </div>
        <div class="form-group">
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlSmallArea"
                   id="hlSmallArea"
                   ${settings.get('hlSmallArea') ? 'checked' : ''}
                   ><label for="hlSmallArea"> Vietas, kuras mazākas par 550m² (zils)</label>
            </div>
        </div>
        <div class="form-group">
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlIncorrectAddress"
                   id="hlIncorrectAddress"
                   ${settings.get('hlIncorrectAddress') ? 'checked' : ''}
                   ><label for="hlIncorrectAddress"> Vietas, kurām adrešu reģistra adrese atšķiras no Waze (lillā)</label>
            </div>
        </div>
        <div class="form-group">
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlNoHN"
                   id="hlNoHN"
                   ${settings.get('hlNoHN') ? 'checked' : ''}
                   ><label for="hlNoHN"> Adresē ir iela, pilsēta, bet nav ēkas numura (sarkans)</label>
            </div>
        </div>
        <div class="form-group">
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlIela"
                   id="hlIela"
                   ${settings.get('hlIela') ? 'checked' : ''}
                   ><label for="hlIela"> Vietas nosaukumā ir vārds "iela" (sarkans)</label>
            </div>
        </div>
        <div class="form-group">
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlLowerCaseHN"
                   id="hlLowerCaseHN"
                   ${settings.get('hlLowerCaseHN') ? 'checked' : ''}
                   ><label for="hlLowerCaseHN"> Ēkas numurā ir mazie burti (sarkans)</label>
            </div>
        </div>
        <div class="form-group">
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlNameHNMismatch"
                   id="hlNameHNMismatch"
                   ${settings.get('hlNameHNMismatch') ? 'checked' : ''}
                   ><label for="hlNameHNMismatch"> Ēkas numurs adresē un numurs nosaukumā nesakrīt (dzeltens)</label>
            </div>
        </div>
        <div class="form-group" data-hide>
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="hlDupes"
                   id="hlDupes"
                   ${settings.get('hlDupes') ? 'checked' : ''}
                   ><label for="hlDupes"> Identiski objekti, viens uz otra (gaiši zils)</label>
            </div>
        </div>
        <!--
        <div class="form-group" data-api-required>
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="addressFixer"
                   id="addressFixer"
                   ${settings.get('addressFixer') ? 'checked' : ''}
                   ><label for="addressFixer"> Adrešu labotājs</label>
            </div>
        </div>
        -->
        <div class="form-group" data-api-required>
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="autoHN"
                   id="autoHN"
                   ${settings.get('autoHN') ? 'checked' : ''}
                   ><label for="autoHN"> Ielādēt adreses automātiski</label>
            </div>
        </div>

        <!--
        <div class="form-group" data-api-required>
            <div class="controls-container">
            <input type="checkbox"
                   class="settings-input"
                   name="autoHNSegmentSelection"
                   id="autoHNSegmentSelection"
                   ${settings.get('autoHNSegmentSelection') ? 'checked' : ''}
                   ><label for="autoHNSegmentSelection"> Iezīmējot segmentu, rādīt tikai tās ielas HN</label>
            </div>
        </div>
        -->

        <!--
        <div id="wmelv-place-info" data-api-required>
        <hr>
            <p id="wmlelv-place-info">Noklikšķini kartē un tadā.</p>
            <div class="action-buttons">
                <button class="waze-btn waze-btn-smaller" type="submit" id="wmelvCreatePlace" data-action="create-place"    data-ratio="1:1">Izveidot 1:1</button>
                <button class="waze-btn waze-btn-smaller" type="submit" id="wmelvCreatePlace2" data-action="create-place" data-ratio="2:1">Izveidot 2:1</button>
            </div>
        </div>
        -->
        <hr>

        <p>
            Tu esi ${W.loginManager.user.userName} (level ${W.loginManager.user.rank + 1}), es esmu skripts.
        </p>

        <hr>

        <div class="form-group" data-api-required>
            <label class="control-label">Funkcionalitāte sāk darboties, ja zoom ir vismaz:</label>
            <div class="controls">
                <div class="form-control waze-radio-container" style="display: block;">
                    <input type="radio" name="minHNZoomLevel" value="3" id="minHNZoomLevel-3"  ${parseInt(settings.get('minHNZoomLevel')) === 3 ? 'checked' : ''}>
                    <label for="minHNZoomLevel-3">3 (drosmīgajiem)</label>
                    <input type="radio" name="minHNZoomLevel" value="4" id="minHNZoomLevel-4"  ${parseInt(settings.get('minHNZoomLevel')) === 4 ? 'checked' : ''}>
                    <label for="minHNZoomLevel-4">4</label>
                    <input type="radio" name="minHNZoomLevel" value="5" id="minHNZoomLevel-5"  ${parseInt(settings.get('minHNZoomLevel')) === 5 ? 'checked' : ''}>
                    <label for="minHNZoomLevel-5">5</label>
                    <input type="radio" name="minHNZoomLevel" value="6" id="minHNZoomLevel-6"  ${parseInt(settings.get('minHNZoomLevel')) === 6 ? 'checked' : ''}>
                    <label for="minHNZoomLevel-6">6</label>
                </div>
            </div>
        </div>

        <!--
        <div class="form-group">
            <label class="control-label" for="segmentToHouseWidth">Ēkas platums, veidojot to no segmenta</label>
                <div class="controls">
                <input class="form-control settings-input" name="segmentToHouseWidth" type="number" id="segmentToHouseWidth" value="${settings.get('segmentToHouseWidth')}">
            </div>
        </div>
        -->
        <hr>

        <dl>
            <dt>2.05</dt>
            <dd>Minimālais <em>area</em> izmērs palielināts uz 550m²</dd>
            <dt>2.04</dt>
            <dd>Adreses maiņas rezultātā tā nomainās arī saskarnē.</dd>
            <dt>2.03</dt>
            <dd>Iespēja koriģēt kļūda (laukumu, adreses, utt) ar vienu pogas klikšķi.</dd>
            <dd>Paplašinājums vairs nesalauž Google StreetView.</dd>
            <dd>Vēl kārtīgi jātestē, jo daudz kas nestrādā.</dd>
            <dd>Ielādes indikācija blakus cilnes nosaukumam.</dd>
            <dt>2.02</dt>
            <dd>Salasāmāki māju numuri un nosaukumi.</dd>
            <dt>2.01</dt>
            <dd>Labota kļūda ar ķekškastīti pie VZD adrešu neatbilstību krāsošanas</dd>
            <dt>2.00</dt>
            <dd>Pirmā publiskā versija</dd>
        </dl>

        <hr>
        `;

        //<div class="loader loader--style3" id="wmelv-loading-indicator" title="2"><svg version="1.1" id="loader-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="40px" height="40px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve"><path fill="#000" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"><animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"/></path></svg></div>
        new WazeWrap.Interface.Tab('WME LV', html, () => {

            Array.from(document.querySelectorAll("#user-tabs > ul > li")).map((el) => {
                const a = el.querySelector('a')
                if (a && a.innerText === 'WME LV') {
                    a.innerHTML = `
                    WME LV
                    <svg id="wmelv-loading-indicator" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="10px" height="10px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve"><path fill="#000" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"><animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"/></path></svg>

                    `
                }
            })

            // Attach settings change handler to corresponding inputs
            qsa('.settings-input').forEach(element => {
                element.addEventListener('change', event => {
                    settings.set(event.target.name, event.target.type === 'checkbox' ? event.target.checked : event.target.value);
                    wmelvUpdate();
                    wmelvLoadAddresses();
                })
            });

            qsa('[name="minHNZoomLevel"]').forEach(element => {
                element.addEventListener('change', event => {
                    settings.set('minHNZoomLevel', parseInt(event.target.value));
                });
            });

            // qsa('[data-action="create-place"]').forEach(el => {
            //     el.addEventListener('click', (e) => {
            //         log.info(e.target)
            //         const vertex = 22.57;
            //
            //         let place = new Landmark()
            //         let poly = new OpenLayers.Geometry.LinearRing([
            //             new OpenLayers.Geometry.Point(lastClick.lon - vertex, lastClick.lat - (e.target.dataset.ratio === '2:1' ? vertex / 2 : vertex)),
            //             new OpenLayers.Geometry.Point(lastClick.lon - vertex, lastClick.lat + (e.target.dataset.ratio === '2:1' ? vertex / 2 : vertex)),
            //             new OpenLayers.Geometry.Point(lastClick.lon + vertex, lastClick.lat + (e.target.dataset.ratio === '2:1' ? vertex / 2 : vertex)),
            //             new OpenLayers.Geometry.Point(lastClick.lon + vertex, lastClick.lat - (e.target.dataset.ratio === '2:1' ? vertex / 2 : vertex)),
            //             new OpenLayers.Geometry.Point(lastClick.lon - vertex, lastClick.lat - (e.target.dataset.ratio === '2:1' ? vertex / 2 : vertex)),
            //         ]);
            //         poly.rotate(10, poly.getCentroid());
            //
            //         place = wmelvCreateNewPlace(new OpenLayers.Geometry.Polygon([poly]), {});
            //
            //         const closest = getClosestAddress(lastClick)
            //         if (closest) {
            //             setVenueAddress(place.attributes.id, 0);
            //             fixPlaceArea(place);
            //         }
            //     });
            // });
        });
        //
        // update();
    }

    /**
     * Registers global handlers which apply to W and W.map.
     */
    function wmelvRegisterGlobalHandlers() {
        W.map.events.register('zoomend', this, () => {
            // updateVenuesFilterList();
            // updateAddressSuggestions();
        });

        W.map.events.register('moveend', this, () => {
            wmelvLoadAddresses();
        });

        W.selectionManager.events.register('selectionchanged', this, wmeSelectionChanged);

        /**
         * Registers click handler to the main map. That's me - being a smartass.
         */
        // W.map.events.register('mousedown', (e) => {
        //     console.log(e.target);
        //     if (e && e.xy) {
        //         lastClick = W.map.getLonLatFromViewPortPx(e.xy).clone();
        //         const point = (new OpenLayers.Geometry.Point(lastClick.lon, lastClick.lat)).transform('EPSG:900913', 'EPSG:4326');
        //         const closest = getClosestAddress(point);
        //         console.log(closest);
        //         qs('#wmlelv-place-info').innerHTML = closest
        //             ? `Tuvākā vieta: <strong>${closest.waze_name}<strong>`
        //             : 'Noklikšķini adreses tuvumā kartē un tadā.';
        //     }
        // }, true)
    }

    /**
     * Initializes application.
     */
    function init() {
        Logger.info('WMELV initializing...');
        UpdateObject = require("Waze/Action/UpdateObject");
        Landmark = require("Waze/Feature/Vector/Landmark");
        AddLandmark = require("Waze/Action/AddLandmark");
        DeleteSegment = require("Waze/Action/DeleteSegment");

        wmelvRegisterGlobalHandlers();
        wmelvRegisterTab();
        wmelvLoadAddresses();
        wmelvUpdate();
        Logger.info('WMELV initialized.');
    }

    /**
     * Starts waiting for W to load. Max 1000 times (100 seconds)
     * @param tries {number}
     */
    function bootstrap(tries = 1) {
        if (W &&
            W.map &&
            W.model &&
            W.loginManager.user &&
            typeof W.selectionManager !== 'undefined' &&
            typeof WazeWrap !== "undefined" &&
            WazeWrap.Ready
        ) {
            init();
        } else if (tries < 1000) {
            setTimeout(() => {
                bootstrap(tries + 1);
            }, 200);
        }
    }

    Logger.info('WMELV waiting...');
    bootstrap();
}());
/* end ======================================================================= */

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址