WME LevelReset +

Fork of the original script. The WME LevelReset tool, to make re-locking segments and POI to their appropriate lock level easy & quick. Supports major road types and custom locking rules for specific cities.

当前为 2023-08-28 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WME LevelReset +
// @version      2023.08.29.002
// @description  Fork of the original script. The WME LevelReset tool, to make re-locking segments and POI to their appropriate lock level easy & quick. Supports major road types and custom locking rules for specific cities.
// @author       Broos Gert '2015, madnut
// @match        https://beta.waze.com/*editor*
// @match        https://www.waze.com/*editor*
// @exclude      https://www.waze.com/*user/*editor/*
// @namespace    https://greasyfork.org/uk/users/160654-waze-ukraine
// @connect      google.com
// @connect      script.googleusercontent.com
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAA+VBMVEX///93PgHX19fTgQfFZgLLcwTrxYDDgA3nqBj5+fmwr6+Yl5f8/PzExMTl5eX114vv7+/e3t68vLzOzs6saRKARQSLTgeioqK2tbX72XfU1NT515fxz4b54b3RmySYWAv31aTpwIHgrn9/f3/75qPZsEvuuC/utx3psVP13KizbhXuuVj745bfoEzzwzDxwDXTjknpxqDPfhzWih7PhUaObErowqDJchrmqCfprRjbmUvblCLZjAv71WnhnyTfmA7hrmbjsm7qxpPv06vYljj305776MvLkD3XkjFwcHCMi4v6zk/6z1P2wVDYqzr3y3j2xWnrrl761X3u0VhGAAABv0lEQVQ4jZWTXXuaMBiGY7bZQUhIoBaKsIK0KkVqtd+2tJ2gnVJs9f//mAW78uHYwe6TXE+em/flJAD8D0RVdF3HTKqvGcaMAiAQVYd1vaEASikhhFKA1ZoeA8Iwct2lCAnAxl/zdcAMbeGipbtwMQM62xFEFUJtoWEIsbh0CVTF3QGqqrjax2cq4kkkFQFjTJD2eYeXBoa4uoEoBOU/RhBUWHWHJukUCZ9JQFCnWkVAQJRQniREyvGPANA/YzazRhBKwjSOg+DZmdoRZ+r8XAfxr5eo1AfzuW1HljXfYkX2zJ5b8TQXXtbWzPff38x2hvn27qf+zFrHubC39tppGoabjczZHIZpmra9/jgXTn2vnSTJaxgecsLwNRkmsueflgV5eLZarU4y+Lk6G9YIg8HxB4PBYEfY3woZQ0529rjQ3y+Evid3ez9K9LpmWTjqe2b3Ti5xlwlHhRDYzdvvFW5NOyiEAy48Pu2VeHps2sFBIUwi5/6hWeLh3okmhdCajJyLLxUunNGktS0lgdLW+agz/lZh3Bmdt6ggZS/NUBqX152brxVuOteXDZVRafsUrxq1XGHIBb6CwHoY4Tt+A1eiQ8S/AAv7AAAAAElFTkSuQmCC
// ==/UserScript==

/* jshint esversion: 11 */
/* global W */
/* global $ */
/* global require */

// initialize LevelReset and do some checks
function LevelReset_bootstrap() {
    if (W?.userscripts?.state.isReady) {
        LevelReset_init();
    } else {
        document.addEventListener("wme-ready", LevelReset_init, {
            once: true
        });
    }
}

function LevelReset_init() {
    // Setting up global variables
    const lrStyle = [
        '.tg { border-collapse: collapse; border-spacing: 0; margin: 0px auto; }',
        '.tg td { border-color: black; border-style: solid; border-width: 1px; overflow: hidden; padding: 2px 2px; word-break: normal; }',
        '.tg .tg-value { text-align: center; vertical-align: top }',
        '.tg .tg-header { background-color: #ecf4ff; border-color: #000000; font-weight: bold; text-align: center; vertical-align: top }',
        '.tg .tg-type { text-align: left; vertical-align: top }',
        ''];
    GM_addStyle(lrStyle.join('\n'));

    const UpdateObject = require("Waze/Action/UpdateObject");
    const VERSION = GM_info.script.version;
    const loader = 'data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPj4+Dg4OISEhAYGBiYmJtbW1qioqBYWFnZ2dmZmZuTk5JiYmMbGxkhISFZWVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAAFUCAgjmRpnqUwFGwhKoRgqq2YFMaRGjWA8AbZiIBbjQQ8AmmFUJEQhQGJhaKOrCksgEla+KIkYvC6SJKQOISoNSYdeIk1ayA8ExTyeR3F749CACH5BAAKAAEALAAAAAAQABAAAAVoICCKR9KMaCoaxeCoqEAkRX3AwMHWxQIIjJSAZWgUEgzBwCBAEQpMwIDwY1FHgwJCtOW2UDWYIDyqNVVkUbYr6CK+o2eUMKgWrqKhj0FrEM8jQQALPFA3MAc8CQSAMA5ZBjgqDQmHIyEAIfkEAAoAAgAsAAAAABAAEAAABWAgII4j85Ao2hRIKgrEUBQJLaSHMe8zgQo6Q8sxS7RIhILhBkgumCTZsXkACBC+0cwF2GoLLoFXREDcDlkAojBICRaFLDCOQtQKjmsQSubtDFU/NXcDBHwkaw1cKQ8MiyEAIfkEAAoAAwAsAAAAABAAEAAABVIgII5kaZ6AIJQCMRTFQKiDQx4GrBfGa4uCnAEhQuRgPwCBtwK+kCNFgjh6QlFYgGO7baJ2CxIioSDpwqNggWCGDVVGphly3BkOpXDrKfNm/4AhACH5BAAKAAQALAAAAAAQABAAAAVgICCOZGmeqEAMRTEQwskYbV0Yx7kYSIzQhtgoBxCKBDQCIOcoLBimRiFhSABYU5gIgW01pLUBYkRItAYAqrlhYiwKjiWAcDMWY8QjsCf4DewiBzQ2N1AmKlgvgCiMjSQhACH5BAAKAAUALAAAAAAQABAAAAVfICCOZGmeqEgUxUAIpkA0AMKyxkEiSZEIsJqhYAg+boUFSTAkiBiNHks3sg1ILAfBiS10gyqCg0UaFBCkwy3RYKiIYMAC+RAxiQgYsJdAjw5DN2gILzEEZgVcKYuMJiEAOwAAAAAAAAAAAA==';

    const defaultLocks = {
        Street: 1,
        Primary: 1,
        Minor: 2,
        Major: 3,
        Ramp: 4,
        Freeway: 4,
        POI: 1,
        Railroad: 1,
        Private: 1,
        Parking: 1,
        Offroad: 1,
        Narrow: 1,
        Camera: 4,
        RailroadCrossing: 3
    };
    const streets = {
        // fake element to show POI's locks
        90000: {
            typeName: "POI",
            scan: true
        },
        90001: {
            typeName: "Camera",
            scan: true
        },
        90002: {
            typeName: "RailroadCrossing",
            scan: true
        },
        // normal streets
        17: {
            typeName: "Private",
            scan: true
        },
        20: {
            typeName: "Parking",
            scan: true
        },
        8: {
            typeName: "Offroad",
            scan: true
        },
        1: {
            typeName: "Street",
            scan: true
        },
        2: {
            typeName: "Primary",
            scan: true
        },
        7: {
            typeName: "Minor",
            scan: true
        },
        6: {
            typeName: "Major",
            scan: true
        },
        4: {
            typeName: "Ramp",
            scan: true
        },
        3: {
            typeName: "Freeway",
            scan: true
        },
        18: {
            typeName: "Railroad",
            scan: true
        },
        22: {
            typeName: "Narrow",
            scan: true
        }
    };
    let relockObject = {};
    const userlevel = W.loginManager.getUserRank() + 1;
    //const userlevel = 6; // for testing purposes (NOTE: this does not enable you to lock higher!)

    const requestsTimeout = 20000; // in ms
    const rulesHash = "AKfycbzKgUQL7cY6XMBlykq0JzJAPc1B26sKEAnG1RJokA9Wpgf_W0oZJwMKcrhA13LrUbvj";
    let rulesDB = {};

    // Some functions
    function onScreen(obj) {
        if (obj.geometry) {
            return (W.map.getExtent().intersectsBounds(obj.geometry.getBounds()));
        }
        return (false);
    }

    function hasPendingUR(poi) {
        return (poi.attributes.venueUpdateRequests.length > 0);
    }

    function displayHtmlPage(res) {
        if (res.responseText.match(/Authorization needed/) || res.responseText.match(/ServiceLogin/)) {
            alert("LevelReset:\n" +
                "Authorization is required for using this script. This is one time action.\n" +
                "Now you will be redirected to the authorization page, where you'll need to approve request.\n" +
                "After confirmation, please close the page and reload WME.");
        }
        let w = window.open();
        w.document.open();
        w.document.write(res.responseText);
        w.document.close();
        w.location = res.finalUrl;
    }

    function sendHTTPRequest(url, callback) {
        GM_xmlhttpRequest({
            url: url,
            method: 'GET',
            timeout: requestsTimeout,
            onload: function (res) {
                callback(res);
            },
            onreadystatechange: function (res) {
                // fill if needed
            },
            ontimeout: function (res) {
                alert("LevelReset: Sorry, request timeout!");
            },
            onerror: function (res) {
                alert("LevelReset: Sorry, request error!");
            }
        });
    }

    function validateHTTPResponse(res) {
        let result = false,
            displayError = true;
        if (res) {
            switch (res.status) {
                case 200:
                    displayError = false;
                    if (res.responseHeaders.match(/content-type: application\/json/i)) {
                        result = true;
                    } else if (res.responseHeaders.match(/content-type: text\/html/i)) {
                        displayHtmlPage(res);
                    }
                    break;
                default:
                    displayError = false;
                    alert("LevelReset Error: unsupported status code - " + res.status);
                    console.log("LevelReset: " + res.responseHeaders);
                    console.log("LevelReset: " + res.responseText);
                    break;
            }
        } else {
            displayError = false;
            alert("LevelReset error: Response is empty!");
        }

        if (displayError) {
            alert("LevelReset: Error processing request. Response: " + res.responseText);
        }
        return result;
    }

    function getAllLockRules() {
        function requestCallback(res) {
            if (validateHTTPResponse(res)) {
                let out = JSON.parse(res.responseText);
                if (out.result == "success") {
                    initUI(out.rules);
                } else {
                    alert("LevelReset: Error getting locking rules!");
                }
            }
        }

        const url = 'https://script.google.com/macros/s/' + rulesHash + '/exec?func=getAllLockRules';
        sendHTTPRequest(url, requestCallback);
    }

    function initUI(rules) {
        let relockContent = document.createElement('div'),
            relockTitle = document.createElement('wz-overline'),
            relockSubTitle = document.createElement('wz-label'),
            rulesSubTitle = document.createElement('wz-label'),
            relockAllbutton = document.createElement('input'),
            relockSub = document.createElement('p'),
            versionTitle = document.createElement('wz-label'),
            resultsCntr = document.createElement('div'),
            rulesCntr = document.createElement('div'),
            alertCntr = document.createElement('div'),
            hidebutton = document.createElement('div'),
            dotscntr = document.createElement('div'),
            inputDiv1 = document.createElement('div'),
            inputDiv2 = document.createElement('div'),
            includeAllSegments = document.createElement('input'),
            includeAllSegmentsLabel = document.createElement('label'),
            respectRouting = document.createElement('input'),
            respectRoutingLabel = document.createElement('label'),
            percentageLoader = document.createElement('div');

        rulesDB = rules;

        // Begin building
        relockTitle.appendChild(document.createTextNode('Re-lock Segments & POI'));

        // fill tab
        relockSub.innerHTML = 'Your on-screen area is automatically scanned when you load or pan around. Pressing the lock behind each type will relock only those results, or you can choose to relock all.<br/><br/>You can only relock segments lower or equal to your current editor level. Segments locked higher than normal are left alone.';
        relockSub.style.cssText = 'font-size:85%;padding:15px;border:1px solid red;border-radius:5px;position:relative';
        relockSub.id = 'sub';
        hidebutton.style.cssText = 'cursor:pointer;width:16px;height:16px;position:absolute;right:3px;top:3px;background-image:url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAAWdEVYdENyZWF0aW9uIFRpbWUAMTEvMjAvMTVnsXrkAAADTUlEQVQ4jW2TW0xbZQCAv3ODnpYWegEGo1wKwzBcxAs6dONSjGMm3kjmnBqjYqLREE2WLDFTIBmbmmxRpzHy4NPi4zRLfNBlZjjtnCEaOwYDJUDcVqC3UzpWTkt7fp80hvk9f/nePkkIwWb+gA5jMXLQjK50Zc2cuKVp4wlX2UevtAYubnal/waWoTI1N38keu7ck2uTl335ZFJCkpE8XlGob4ibgeZvMl7P8MtdO6/dFohDe/Sn0LdzJ457MuHfUYqLkYtsSIqMJASyIiNv30Gm6+G1zNbqvpf6gqF/AwaUXx+/MDdz6KArH4ujVVRAbgPVroMsQz6P6nJiGUnUGj/pR/tTyx2dtW+11t2UAa5Pz34w//GHLitpsG1wkODp0xQ11GOZJpgmzq5uqo8ew76zAxFPUDJxscwzFR4BkGfh/tj58/3Zq9OoFZU0PHsAd00NnWNj6IEApd3duA48g2nXKenpQSl1oceWsUeuPfdp+M9GZf/zA5+lz3x9lxRbAUli+dIlKnt7Ud1uCk1NJH0+VnMmq6EQfw0NUzCSULBQfT4HVf4iNRO50VlIGSi6jup0sj5zlTO7d9N48iRLa2vkCwWsyTArbx/GAaSBm/MLyLm85OjZs0c2zawQsoRmt5NeXCRyeRLh9rBkGBSEwF6i09h+L96GemyAx2bDK4ENkGRJkbM2fVy4PRhT08RmZvH09VE29C6ixEFuahL3hklLby9PhEKUt7VRZln4kHD669Bqtl6Q7W07jqWL9FQiEkHTdUoGBsgXF5EPh0m8M8Tc62/CSoLSqmqaR4ZxaRpenxfbgw8lCy2Nx5Uv3xuNXEll7shO/HI38Rjr09NImkriyCgOy0JZTZM4+x3C7SY+epTaLZWsdwXJPNV/6jF/9ReSEIKzmcKWpbHPF9OHDxUr6xksoAiQJAmnpuEWAqeq4G9uRr7nPpZeeDG10NqybV+5Ly4DPGJXlsv79u51v38iK22/EwmwACEEIpdD2tjApmncan8A49XX4qtNgeC+cl/8tpm+jxoBY+K3N7I/jj+dvxKuIhZV7KpKWV295dy1K6YEg1/NO2wj+/210f+98R9+hub0wo1BOZnslRVV16orf0hVeD55HH7d7P4N0V1gY9/zcaEAAAAASUVORK5CYII=\');';
        hidebutton.onclick = function () {
            localStorage.msgHide = 1;
            $('#sub').hide('slow');
        };
        dotscntr.style.cssText = 'width:16px;height:16px;margin-left:5px;background:url("' + loader + '");vertical-align:text-top;display:none';
        dotscntr.id = 'dotscntr';
        relockSubTitle.innerHTML = 'Results';
        relockSubTitle.id = 'reshdr';
        rulesSubTitle.innerHTML = 'Active rules';
        versionTitle.innerHTML = 'Version ' + VERSION;
        relockAllbutton.id = 'rlkall';
        relockAllbutton.type = 'button';
        relockAllbutton.value = 'Relock All';
        relockAllbutton.style.cssText = 'margin: 10px 3px 0 0';
        relockAllbutton.onclick = function () {
            relockAll();
        };

        // Also reset higher locked segments?
        includeAllSegments.type = 'checkbox';
        includeAllSegments.name = "name";
        includeAllSegments.value = "value";
        includeAllSegments.checked = (localStorage.getItem('Relock_allSegments') == 'true');
        includeAllSegments.id = "_allSegments";
        includeAllSegments.onclick = function () {
            localStorage.setItem('Relock_allSegments', includeAllSegments.checked.toString());
            scanArea();
            relockShowAlert();
        };
        includeAllSegmentsLabel.htmlFor = "_allSegments";
        includeAllSegmentsLabel.innerHTML = 'Also reset higher locked objects';
        includeAllSegmentsLabel.style.cssText = 'font-size:95%;margin-left:5px;vertical-align:middle';

        // Respect routing road type
        respectRouting.type = 'checkbox';
        respectRouting.name = "name";
        respectRouting.value = "value";
        respectRouting.checked = (localStorage.getItem('Relock_respectRouting') == 'true');
        respectRouting.id = "_respectRouting";
        respectRouting.onclick = function () {
            localStorage.setItem('Relock_respectRouting', respectRouting.checked.toString());
            scanArea();
        };
        respectRoutingLabel.htmlFor = "_respectRouting";
        respectRoutingLabel.innerHTML = 'Respect routing road type';
        respectRoutingLabel.style.cssText = 'font-size:95%;margin-left:5px;vertical-align:middle';

        resultsCntr.style.cssText = 'margin-right:5px;';

        // add results empty list
        $.each(streets, function (key, value) {
            let __cntr = document.createElement('div'),
                __keyLeft = document.createElement('div'),
                __prntRight = document.createElement('div'),
                __cntRight = document.createElement('div'),
                __cleardiv = document.createElement("div"),
                __chkLeft = document.createElement('input'),
                __lblLeft = document.createElement('label');
            let idPrefix = 'Relock_' + value.typeName + '_';

            // Begin building
            __keyLeft.style.cssText = 'float:left';

            __chkLeft.type = 'checkbox';
            __chkLeft.checked = (localStorage.getItem(idPrefix + 'chk') == 'true');
            __chkLeft.id = idPrefix + 'chk';
            __chkLeft.onclick = function () {
                localStorage.setItem(idPrefix + 'chk', __chkLeft.checked.toString());
                scanArea();
            };
            __lblLeft.htmlFor = idPrefix + 'chk';
            __lblLeft.innerHTML = value.typeName;
            __lblLeft.style.cssText = 'margin-bottom:0px;font-weight:normal;';

            __keyLeft.appendChild(__chkLeft);
            __keyLeft.appendChild(__lblLeft);

            __cntRight.style.cssText = 'float:right';
            __cntRight.innerHTML = '-';

            __prntRight.id = idPrefix + 'value';
            __prntRight.style.cssText = 'float:right';
            __prntRight.appendChild(__cntRight);

            __cleardiv.style.cssText = 'clear:both;';

            // Add to stage
            __cntr.appendChild(__keyLeft);
            __cntr.appendChild(__prntRight);
            __cntr.appendChild(__cleardiv);
            resultsCntr.appendChild(__cntr);
        });

        // Alert box
        alertCntr.id = "alertCntr";
        alertCntr.style.cssText = 'border:1px solid #EBCCD1;background-color:#F2DEDE;color:#AC4947;font-weight:bold;font-size:90%;border-radius:5px;padding:10px;margin:5px 5px;display:none';
        alertCntr.innerHTML = 'Watch out for map exceptions, some higher locks are there for a reason!';

        // Rules table
        let rowElm;
        let colElm;

        let tableElm = document.createElement('table');
        tableElm.className = 'tg';

        let bodyElm = document.createElement('tbody');

        const topCountry = W.model.getTopCountry();
        const countryRules = rulesDB[topCountry.attributes.abbr];
        if (countryRules) {
            $.each(countryRules, function (key, value) {
                if (key == "CountryName") return false;

                rowElm = document.createElement('tr');
                rowElm.className = "tg-row";
                rowElm.dataset.name = parseInt(key) === 0 ? 'country' : value.CityName; // need to hard code 'country' to identify later

                colElm = document.createElement('td');
                colElm.className = "tg-header";
                colElm.innerHTML = parseInt(key) === 0 ? countryRules.CountryName : value.CityName;
                colElm.colSpan = 6;
                rowElm.appendChild(colElm);
                tableElm.appendChild(rowElm);

                const maxCol = 3;
                let colIndex = 0;
                rowElm = document.createElement('tr');
                $.each(value.Locks, function (k, v) {
                    if (v) {
                        rowElm.className = "tg-row";
                        rowElm.dataset.name = parseInt(key) === 0 ? 'country' : value.CityName; // need to hard code 'country' to identify later
                        if (colIndex < maxCol) {
                            colElm = document.createElement('td');
                            colElm.className = "tg-type";
                            colElm.innerHTML = k;
                            rowElm.appendChild(colElm);

                            colElm = document.createElement('td');
                            colElm.className = "tg-value";
                            colElm.innerHTML = v;
                            rowElm.appendChild(colElm);

                            colIndex++;
                            if (colIndex == maxCol) {
                                colIndex = 0;
                                tableElm.appendChild(rowElm);
                                rowElm = document.createElement('tr');
                            }
                        }
                    }
                });
                tableElm.appendChild(rowElm);
            });
        }

        tableElm.appendChild(bodyElm);
        rulesCntr.style.cssText = 'font-size:12px';
        rulesCntr.appendChild(tableElm);

        // add to stage
        relockContent.appendChild(relockTitle);
        relockContent.appendChild(versionTitle);

        // Loader bar
        percentageLoader.id = 'percentageLoader';
        percentageLoader.style.cssText = 'width:1px;height:10px;background-color:green;margin-top:10px;border:1px solid:#333333;display:none';

        // only show if user didn't hide it before
        if (localStorage.msgHide != 1) {
            relockSub.appendChild(hidebutton);
            relockContent.appendChild(relockSub);
        }

        inputDiv1.appendChild(respectRouting);
        inputDiv1.appendChild(respectRoutingLabel);
        inputDiv2.appendChild(includeAllSegments);
        inputDiv2.appendChild(includeAllSegmentsLabel);
        relockContent.appendChild(inputDiv1);
        relockContent.appendChild(inputDiv2);

        relockContent.appendChild(alertCntr);
        relockContent.appendChild(relockSubTitle);
        relockContent.appendChild(resultsCntr);
        relockContent.appendChild(relockAllbutton);
        relockContent.appendChild(dotscntr);
        relockContent.appendChild(percentageLoader);
        relockContent.appendChild(rulesSubTitle);
        relockContent.appendChild(rulesCntr);

        const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("sidepanel-relock");

        tabLabel.innerHTML = 'Re - <span class="fa fa-lock" id="lockcolor" style="color:green"></span>';
        tabLabel.title = "Relock segments";

        tabPane.appendChild(relockContent);

        // Do a default scan once at startup
        relockShowAlert();
        scanArea();
    }

    function relock(obj, key) {
        let objects = obj[key];
        let _i = 0;

        // update GUI
        function RunLocal() {
            W.model.actionManager.add(objects[_i]);
            _i++;

            if (_i < objects.length) {
                setTimeout(RunLocal, 1);
                let newWidth = (_i / objects.length) * $('#sidepanel-relockTab').css('width').replace('px', '');
                $('#percentageLoader').show();
                $('#percentageLoader').css('width', newWidth + 'px');
                $('#dotscntr').css('display', 'inline-block');
            } else {
                $('#dotscntr').css('display', 'none');
                $('#percentageLoader').hide();
            }
        }
        RunLocal();
    }

    function relockAll() {
        // only lock "all" until the current editors level is reached, then stop...
        $('#dotscntr').css('display', 'inline-block');

        $.each(relockObject, function (key, value) {
            if (value.length !== 0) {
                // loop trough each segmentType
                let _i = 0;
                let RunLocal5 = function () {
                    W.model.actionManager.add(value[_i]);
                    _i++;

                    // Did not iterate with $.each, so the GUI can update with larger arrays
                    if (_i < value.length) {
                        setTimeout(RunLocal5, 1);
                        let newWidth = (_i / value.length) * $('#sidepanel-relockTab').css('width').replace('px', '');
                        $('#percentageLoader').show();
                        $('#percentageLoader').css('width', newWidth + 'px');
                        $('#dotscntr').css('display', 'inline-block');
                    } else {
                        $('#dotscntr').css('display', 'none');
                        $('#percentageLoader').hide();
                    }
                };
                RunLocal5();
            }
        });
        scanArea();
        $('#dotscntr').hide('slow');
    }

    function relockShowAlert() {
        let includeAllSegments = document.getElementById('_allSegments');
        if (includeAllSegments && includeAllSegments.checked)
            $('#alertCntr').show("fast");
        else
            $('#alertCntr').hide("fast");
    }

    function hideInactiveCities() {
        $('tr.tg-row').each(function (i) {
            let isActive = false;
            for (let j in W.model.cities.objects) {
                if (W.model.cities.objects[j].attributes.name == $(this).data("name")) {
                    isActive = true;
                    break;
                }
            }
            if (isActive || $(this).data("name") == 'country') {
                $(this).show("fast");
            } else {
                $(this).hide("fast");
            }
        });
    }

    function setLockLevel(v, scanObj, desiredLockLevel) {
        let includeAllSegments = document.getElementById('_allSegments');
        let allSegmentsInclude = includeAllSegments.checked && userlevel > 4;
        if (userlevel > desiredLockLevel) {
            if ((v.attributes.lockRank < desiredLockLevel) ||
                (v.attributes.lockRank > desiredLockLevel && allSegmentsInclude)) {
                relockObject[scanObj].push(new UpdateObject(v, {
                    lockRank: desiredLockLevel
                }));
                return true;
            }
        }
        return false;
    }

    function scanArea() {
        let respectRouting = document.getElementById('_respectRouting');
        let relockSubTitle = document.getElementById('reshdr');
        let relockAllbutton = document.getElementById('rlkall');

        if (!(relockSubTitle && relockAllbutton && respectRouting))
            return;

        hideInactiveCities();

        // Object with array of road types, to collect each wrongly locked segment, for later use
        $.each(defaultLocks, function (k, v) {
            relockObject[k] = [];
        });

        let foundBadlocks = false;
        let respectRoutingRoadType = respectRouting.checked;
        let count = 0;

        // Choose country lock settings. If country selection fails
        // or country isn't in this list, WME default values are used.
        let topCountry = W.model.getTopCountry();
        let ABBR = rulesDB[topCountry.attributes.abbr] ? rulesDB[topCountry.attributes.abbr][0].Locks : defaultLocks;
        console.log("LevelReset: ", ABBR);

        // Do a count on how many segments are in need of a correct lock (limit to 150 to save CPU)
        // Count also depends on the users editor level
        const limitCount = 150;
        relockSubTitle.innerHTML = 'Results (limit: ' + limitCount + ')';

        // disable unchecked road types
        $.each(streets, function (key, value) {
            let idPrefix = 'Relock_' + value.typeName + '_chk';
            let chk = document.getElementById(idPrefix);
            value.scan = (chk && chk.checked);
        });

        // ============== POI ===========================
        if (streets["90000"].scan) {
            let scanObj = "POI";
            $.each(W.model.venues.objects, function (k, v) {
                if (count < limitCount && v.type == "venue" && onScreen(v) && v.isGeometryEditable() && !hasPendingUR(v) /*&& !v.isResidential()*/) {

                    let strt = v.attributes.streetID ? W.model.streets.objects[v.attributes.streetID] : null;
                    let cityID = strt ? strt.cityID : null;

                    let desiredLockLevel = (cityID && rulesDB[topCountry.attributes.abbr] && rulesDB[topCountry.attributes.abbr][cityID]) ? rulesDB[topCountry.attributes.abbr][cityID].Locks[scanObj] : ABBR[scanObj];
                    desiredLockLevel--;

                    if (setLockLevel(v, scanObj, desiredLockLevel)) {
                        foundBadlocks = true;
                        count++;
                    }
                }
            });
        }
        // ============== Cameras ===========================
        if (streets["90001"].scan) {
            let scanObj = "Camera";
            $.each(W.model.cameras.objects, function (k, v) {
                if (count < limitCount && v.type == "camera" && onScreen(v) && v.isGeometryEditable()) {

                    let desiredLockLevel = ABBR[scanObj];
                    desiredLockLevel--;

                    if (setLockLevel(v, scanObj, desiredLockLevel)) {
                        foundBadlocks = true;
                        count++;
                    }
                }
            });
        }
        // ============== Railroad Crossings ===========================
        if (streets["90002"].scan) {
            let scanObj = "RailroadCrossing";
            $.each(W.model.railroadCrossings.objects, function (k, v) {
                if (count < limitCount && v.type == "railroadCrossing" && onScreen(v) && v.isGeometryEditable()) {

                    let desiredLockLevel = ABBR[scanObj];
                    desiredLockLevel--;

                    if (setLockLevel(v, scanObj, desiredLockLevel)) {
                        foundBadlocks = true;
                        count++;
                    }
                }
            });
        }
        // ============== Segments ===========================
        $.each(W.model.segments.objects, function (k, v) {
            if (count < limitCount && v.type == "segment" && onScreen(v) && v.isGeometryEditable()) {
                let curStreet = streets[v.attributes.roadType];
                // for changed routing respect the routing type (if enabled)
                if (v.attributes.routingRoadType && respectRoutingRoadType) {
                    curStreet = streets[v.attributes.routingRoadType];
                }
                if (curStreet && curStreet.scan) {
                    let strt = W.model.streets.getObjectById(v.attributes.primaryStreetID);
                    let cityID = strt ? strt.attributes.cityID : null;

                    let stLocks = (cityID && rulesDB[topCountry.attributes.abbr] && rulesDB[topCountry.attributes.abbr][cityID]) ? rulesDB[topCountry.attributes.abbr][cityID].Locks : ABBR;
                    let desiredLockLevel = stLocks[curStreet.typeName] - 1;

                    if (setLockLevel(v, curStreet.typeName, desiredLockLevel)) {
                        foundBadlocks = true;
                        count++;
                    }
                }
            }
        });

        // Build results to users tab panel
        $.each(relockObject, function (key, value) {
            let __lckRight = document.createElement('div');
            let __cntRight = document.createElement('div');
            let idPrefix = 'Relock_' + key + '_value';

            // Begin building
            let __prntRight = document.getElementById(idPrefix);
            __prntRight.innerHTML = '';

            __cntRight.style.cssText = 'float:right';
            __lckRight.style.cssText = 'width:15px;float:right;padding:2px 0 0 8px;cursor:pointer;';

            if (value.length !== 0) {
                __cntRight.innerHTML = '<b>' + value.length + '</b>';
                __lckRight.className = 'fa fa-lock';
                __lckRight.style.cssText += 'color:red;';
                __lckRight.onclick = function () {
                    relock(relockObject, key);
                };
                __prntRight.appendChild(__lckRight);
            } else {
                __cntRight.innerHTML = '-';
            }

            __prntRight.appendChild(__cntRight);
        });

        // Color the small lock icon red, if errors are found, so people can decide what to do...
        if (foundBadlocks) {
            relockAllbutton.removeAttribute('disabled');
            $('#lockcolor').css('color', 'red');
        } else {
            relockAllbutton.setAttribute('disabled', true);
            $('#lockcolor').css('color', 'green');
        }
    }

    getAllLockRules();

    // Register some event listeners
    W.map.events.register("moveend", null, scanArea);
    W.model.actionManager.events.register("afteraction", null, scanArea);
    W.model.actionManager.events.register("afterundoaction", null, scanArea);
    W.model.actionManager.events.register("noActions", null, scanArea);
}

LevelReset_bootstrap();