WME Closure Helper

A script to help out with WME closure efforts! :D

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 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         WME Closure Helper
// @namespace    https://greasyfork.org/en/users/673666-fourloop
// @version      2025.02.14.01
// @description  A script to help out with WME closure efforts! :D
// @author       fourLoop & maintained by jm6087 and fuji2086
// @match        https://beta.waze.com/*editor*
// @match        https://www.waze.com/*editor*
// @exclude      https://www.waze.com/*user/*editor/*
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @connect      api.timezonedb.com
// @grant        GM.xmlHttpRequest

// ==/UserScript==

/* global W */
/* global toastr */
/* global $ */
/* global settings */
/* global WazeWrap */
/* global OpenLayers */
/* global GM_xmlhttpRequest */
/* global xmlHttpRequest */

var G_AMOUNTOFPRESETS = 100;

(function() {
    'use strict';

    var customCSmin;
    var settings = {};
    var DateFormat = "";
    let Lang;
    var dateSeparator;
    let radio = "";

    //Bootstrap
    function bootstrap(tries = 1) {
        if (typeof W === 'object' && W.userscripts?.state?.isReady && W.map &&
            W.model && W.loginManager.user && WazeWrap.Ready) {
            log("Here we go!!! Starting program!");
            init();
        } else if (tries < 1000) {
            setTimeout(function() { bootstrap(tries++); }, 200);
        }
    }

    function init() {
        Lang = I18n.currentLocale()
        let lang_mmddyyyy = ["en-US", "en", "no", "zh"]; // mm/dd/yyyy
        let lang_ddmmyyyy = ["es-419", "es", "en-GB", "en-AU", "af", "ca", "el", "fr", "gl", "id", "it", "ms", "pt-BR", "pt-PT", "th"]; // dd/mm/yyyy
        let lang_ddmmyyyy2 = ["bg", "cs", "da", "de", "et", "fi", "hr", "lv", "pl", "ro", "ru", "sk", "sl", "tr", "uk"]; // dd.mm.yyyy
        let lang_yyyymmdd = ["eu", "lt", "sv"]; // yyyy-mm-dd
        let lang_ddmmyyyy3 = ["nl"]; // dd-mm-yyyy
        let lang_yyyymmdd2 = ["zh-TW"]; // yyyy/mm/dd
        let lang_yyyymmdd3 = ["hu"]; // yyyy.mm.dd
        if (lang_mmddyyyy.indexOf(Lang) != -1) {
            DateFormat = "mmddyyyy";
            dateSeparator = "/";
        }
        if (lang_ddmmyyyy.indexOf(Lang) != -1) {
            DateFormat = "ddmmyyyy";
            dateSeparator = "/";
        }
        if (lang_ddmmyyyy2.indexOf(Lang) != -1){
            DateFormat = "ddmmyyyy";
            dateSeparator = ".";
        }
        if (lang_yyyymmdd.indexOf(Lang) != -1){
            DateFormat = "yyyymmdd";
            dateSeparator = "-";
        }
        if (lang_ddmmyyyy3.indexOf(Lang) != -1){
            DateFormat = "ddmmyyyy"
            dateSeparator = "-";
        }
        if (lang_yyyymmdd2.indexOf(Lang) != -1){
            DateFormat = "yyyymmdd"
            dateSeparator = "/";
        }
        if (lang_yyyymmdd3.indexOf(Lang) != -1){
            DateFormat = "yyyymmdd"
            dateSeparator = ".";
            //        dateseparator2 = ".";
        }
        if (DateFormat == ""){
            DateFormat = "mmddyyyy";
            dateSeparator = "/";
        }

        var $section = $("<div>");
        var formString = '';
        var preset = 0;
        for (preset = 1; preset < (G_AMOUNTOFPRESETS + 1); preset++) {
            formString += '<div class="wmech_presetdiv" id="wmech_presetrow' + preset + '"><label class="wmech_presetlabel" style="text-align: center; width: 100%; margin-left: 0; margin-bottom: 0;" for="wmech_preset' + preset + 'name">Preset ' + preset + '</label>' +
                '<input id="wmech_preset' + preset + 'name" type="text" placeholder="Name" class="wmech_input wmech_inputpreset wmech_namepreset">' +
                '<input id="wmech_preset' + preset + 'reason" type="text" placeholder="Description" class="wmech_input wmech_inputpreset">' +
                '<input id="wmech_preset' + preset + 'timeString" type="text" placeholder="Time String (default U: 23:59)" class="wmech_input wmech_inputpreset">' +
                '<label class="wmech_presetlabel" for="wmech_preset' + preset + 'permanent">Make permanent:</label>' +
                '<i class="waze-tooltip wmech_presetpermatooltip" data-original-title="This feature will check the HOV / Service Road adjacent checkbox, meaning the closure will not listen to traffic."></i>' +
                '<input class="wmech_checkbox wmech_presetcheckbox wmech_presetsetting wmech_presetpermanent" title="Enable permanent closures by default" id="wmech_preset' + preset + 'permanent" type="checkbox">' +
                '<br><label class="wmech_presetlabel" for="wmech_preset' + preset + 'nodes">Node closures:</label>' +
                '<select class="wmech_presetsetting wmech_presetdropdown wmech_presetnodes" id="wmech_preset' + preset + 'nodes">' +
                '<option>Middle</option>' +
                '<option>All</option>' +
                '<option>Ends</option>' +
                '<option>None</option>' +
                '</select><br><label class="wmech_presetlabel" for="wmech_preset' + preset + 'direction">Direction:</label>' +
                '<select class="wmech_presetsetting wmech_presetdropdown wmech_presetdirection" id="wmech_preset' + preset + 'direction">' +
                '<option>Two Way</option>' +
                '<option>A --> B</option>' +
                '<option>B --> A</option>' +
                '</select><div><label class="wmech_presetlabel" for="wmech_preset' + preset + 'mteString">MTE:</label>' +
                '<input style="width: 50%;" id="wmech_preset' + preset + 'mteString" type="text" placeholder="MTE Search" class="wmech_input wmech_inputpreset wmech_presetsetting wmech_presetmteString"></div>' +
                '<div><label class="wmech_presetlabel" for="wmech_preset' + preset + 'color">Color:</label>' +
                '<input class="wmech_colorinput wmech_presetsetting wmech_presetcolor" type="color" id="wmech_preset' + preset + 'color"></div>' +
                '<button class="wmech_closurebutton wmech_presetdeletebutton" style="background-color: red; color: white;">Delete Preset</button>' +
                '</div>';
        }
        var tabString = '<ul class="nav nav-tabs"><li class="active"><a data-toggle="tab" href="#wmech-tab-presets">Presets</a></li>' +
            '<li><a data-toggle="tab" href="#wmech-tab-settings">Settings</a></li>' +
            '<li><a data-toggle="tab" href="#wmech-tab-format">Formatting</a></li>' +
            '<li><a data-toggle="tab" href="#wmech-tab-about">About</a></li>' +
            '</ul>';
        var settingsString = '<div class="wmech-tab-pane" id="wmech-tab-settings"><h2><center>Settings</center></h2><div id="wmech-main-settings"><div id="wmech-settings-boxes"></div></div><div id="wmech-quicksearch-settings"></div></div>';
        var formatString = '<div class="wmech-tab-pane" id="wmech-tab-format">' +
            '<h2><center>Formatting</center></h2>' +
            '<h3>Formatting Time Strings</h3>' +
            '<ul>' +
            '<li><b>No Flag:</b> Sets the closure for a specified duration. Duration string only <ul><li>"1m" = 1 Minute</li><li>"1d4h" = 1 Day, 4 Hours</li><li>"1o4d32m" = 1 Month, 4 Days, 32 Minutes</li><li>"1y" = 1 Year</ul></li>' +
            '<li><b>U Flag:</b> Sets the closure until a specified time, with an optional additional duration string.<ul>' +
            '<li>"U: 06:00" = Sets closure until the next instance of 6AM</li><li>"U: 23:59" = Sets closure until the next instance of 11:59PM</li><li>"U: 08:46, 1d" = Sets closure until 1 day after the next instance of 8:46AM' +
            '<li>"U: 12:45, 1y3m" = Sets closure until 1 year, 3 months after the next instance of 12:45PM</li>' +
            '<li>"U: 12:45, Mon" = Sets closure until 12:45PM on the next Monday (which, if today was Monday before 12:45PM, would be today)</li>' +
            '<li>"U: 12:45, Mon, 1y2o" = Sets closure until 12:45PM on the next Monday (which, if today was Monday before 12:45PM, would be today) and adds 1 year, 2 months</li></ul>' +
            '</li><li><b>D Flag:</b> Sets the closure until a specified date and time, in calendar format.' +
            '<ul><li>"D: 2024-09-30 06:00" = Sets the closure until 2024-09-30 at 6:00AM.</li><li>"D: 2020-07-08 14:15" = Sets closure until 2020-07-08 at 14:15.</li></ul></li>' +
            '</ul>' +
            '<h3>Duration Strings</h3>' +
            '<ul><li>"m" = Minute</li><li>"h" = Hour</li><li>"d" = Day</li><li>"o" = Month</li><li>"y" = Year</li><li><b>Note: </b> Weeks and other date/times are not supported</li></ul>' +
            '<h3>Name String</h3>' +
            '<ul>' +
            '<li><b>{{type}}</b> = The type of the segment</li>' +
            '<li><b>{{firstSegName}}</b> = The name of the first selected segment, in order of click</li>' +
            '<li><b>{{lastSegName}}</b> = The name of the last selected segment, in order of click</li>' +
            '</ul>' +
            '</div>';
        var aboutString = '<div class="wmech-tab-pane" id="wmech-tab-about"><h2><center>About</center></h2>' +
            '<ul>' +
            '<li>' + GM_info.script.version + '</li>' +
            '<li>Made by ' + GM_info.script.author + '</li>' +
            '<li>Documentation: <a href="https://docs.google.com/document/d/1mPE8qKezU720VCgrVCKpury7fkW5y5FbDrXzYbBpQK4/edit?usp=sharing" target="_blank">Here</a>' +
            '<li>Thanks to all of you amazing editors who make the map better every day <3' +
            '</ul>' +
            '</div>';
        formString = '<div class="wmech-tab-pane active" id="wmech-tab-presets"><label for="wmech_presetchooser">Choose a preset:</label><br><select id="wmech_presetchooser"></select>' + formString +
            '<button class="wmech_closurebutton wmech_presetsavebutton" style="background-color: blue; color: white;">Save Presets</button></div>' +
            '<div class="wmech-alert" id="wmech-save-notice">Save Successful</div>';
        $section.html(tabString + "<div class='tab-content'>" + formString + settingsString + formatString + aboutString + "</div>");

        setTimeout(function() {
            WazeWrap.Interface.Tab('CH', $section.html(), initializeSettings, 'CH');
            $(".wmech_presetdiv").hide();
            $("#wmech_presetrow1").show();
            $("#wmech_presetchooser").change(function() {
                var sel = parseInt($(this).children("option:selected").val()) + 1;
                if (sel != -1) {
                    $(".wmech_presetdiv").hide();
                    $("#wmech_presetrow" + sel).show();
                }
            });
        }, 1000);
    }

    function prepareSettings() {
        addSettingsCheckbox("Set segment list on closures to default collapsed", "wmech_settingseglistcollapse");
        addSettingsCheckbox("Direction click-saver buttons do not use directional cursors", "wmech_settingdircsdircur");
        addSettingsHeader("Time Zone Settings");
        addSettingsCheckbox("Enable time zone warning", "wmech_settingtimezonewarn");
        addSettingsInput("timezonedb.com/api Personal Key", "wmech_settingtimezoneapi");
        addSettingsHeader("Custom Minutes - Enter number of minutes (numbers only)");
        addSettingsInput("Custom time clicksaver - Enter number of minutes", "wmech_settingcustomcs");
        //         addSettingsCheckbox("Minutes", "wmech_settingcustomcsMin");
        $("#wmech_settingtimezonewarn").change(function() {
            if (!this.checked) {
                $("#wmech_settingtimezoneapi").prop('disabled', true);
            } else {
                $("#wmech_settingtimezoneapi").prop('disabled', false);
            }
        });
    }

    function addSettingsCheckbox(text, id) {
        $("#wmech-settings-boxes").append("<div class='controls-container'><input class='wmech_checkbox wmech_settingscheckbox' id='" + id + "' type='checkbox'><label class='wmechSettingsLabel' for='" + id + "'>" + text + "</label></div><br>");
    }

    function addSettingsHeader(text) {
        $("#wmech-settings-boxes").append("<p class='wmech_settingsheader'>" + text + "</p>");
    }

    function addSettingsInput(placeholder, id) {
        $("#wmech-settings-boxes").append("<input type='text' id=\"" + id + "\" placeholder='" + placeholder + "' class='wmech_input wmech_inputpreset wmech_settingsinput'>");
    }

    function initializeSettings() {
        prepareSettings();
        setUpSavePresetButton();
        setUpDeletePresetButton();
        attachObserver();

        $(".wmech_inputpreset").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = this.value;
        });
        $(".wmech_namepreset").on('input', function() {
            var curVal = $("#wmech_presetchooser").val();
            $("#wmech_presetchooser").children().eq(curVal).text("Preset " + (parseInt(curVal) + 1) + " - " + $(this).val());
            loadDropdown();
        });
        $(".wmech_presetcheckbox").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = this.checked;
        });
        $(".wmech_presetcolor").on("change", function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = this.value;
        });
        $(".wmech_presetdropdown").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = $(this).val();
        });
        $(".wmech_settingscheckbox").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_setting(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var settingName = harvestIdInfo[1];
            if (!settings.settingsCheckboxes) {
                settings.settingsCheckboxes = {};
            }
            settings.settingsCheckboxes[settingName] = $(this).is(":checked");
            saveSettings();
        });
        $(".wmech_settingsinput").on('change paste keyup input', function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_setting(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var settingName = harvestIdInfo[1];
            if (!settings.settingsInputs) {
                settings.settingsInputs = {};
            }
            settings.settingsInputs[settingName] = $(this).val();
            saveSettings();
        });
        //Added save button to eliminate the need to save settings every 6 seconds.
        /*var settingsSaver = setInterval(function() {
            saveSettings();
            // log("Save settings ran.");
        }, 60000);*/

        // Enable tooltips
        $(".wmech_presetpermatooltip").tooltip();

        setTimeout(loadSettings, 2500);
        log("Settings initialized.");
    }

    function setUpSavePresetButton() {
        $(".wmech_presetsavebutton").click(async function() {
            saveSettings();
            $('#wmech-save-notice').css("display", "block");
            setTimeout(function() { $('#wmech-save-notice').css("display", "none"); }, 5000);
        });
    }

    function setUpDeletePresetButton() {
        $(".wmech_presetdeletebutton").click(async function() {
            // Find max preset value
            var maxValue = 0;
            $("#wmech_presetchooser").find("option").each(function() {
                var curVal = $(this).val();
                if (curVal > maxValue) {
                    maxValue = curVal;
                }
            });

            // Clear information about the current preset and the last preset
            clearPreset($(this).parent());
            var curId = $("#wmech_presetchooser").val();
            $("#wmech_presetchooser").val(curId - 1).change();
            settings.presets.splice(curId, 1);

            await saveSettings();
            clearPreset($("#wmech_presetrow" + maxValue));
            if (curId == 0) {
                $("#wmech_presetrow1").show();
            }
        });
    }

    function clearPreset(el) {
        var preset = el;
        preset.find(".wmech_inputpreset").val("").change();
        preset.find(".wmech_presetpermanent").prop("checked", false).change();
        preset.find(".wmech_presetnodes").val("Middle").change();
        preset.find(".wmech_presetdirection").val("Two Way").change();
        preset.find(".wmech_presetcolor").val("#000000").change();
    }

    async function saveSettings() {
        log("Saved the settings. :D");
        if (localStorage) {
            localStorage.setItem("wmech_Settings", JSON.stringify(settings));
        }
// COMMENTED OUT BECAUASE OF WW ISSUES
//        await saveToServer();
// COMMENTED OUT BECAUASE OF WW ISSUES
        setTimeout(loadSettings, 100);
    }

    async function saveToServer() {
        // log("Attempting to save to the WazeDev server.");
        var res = await WazeWrap.Remote.SaveSettings(GM_info.script.name, settings);
        if (res == false) {
            error("Error saving settings to the WazeDev server.");
        } else if (res == null) {
            // log("Tried to save settings to WazeDev server, but you don't have a PIN set.")
        } else {
            // log("Saved settings to WazeDev server.");
        }
    }

    async function loadSettings() {
        var loadedSettings = $.parseJSON(localStorage.getItem("wmech_Settings"));
        var serverSettings = await WazeWrap.Remote.RetrieveSettings(GM_info.script.name);
        var defaultSettings = {
            enabled: true,
            presets: [{
                name: "Your first preset...",
                reason: "This is where your closure reason goes...",
                timeString: "And this is where your time string goes!",
                permanent: false,
                nodes: "Middle",
                direction: "Two Way",
                mteString: "And the MTE name",
                mteMatchIndex: 0,
                color: "#ffffff"
            }]
        };
// COMMENTED OUT BECAUASE OF WW ISSUES
//        if (serverSettings != null && serverSettings.hasOwnProperty("enabled")) {
            // log("Using settings from WazeDev server.");
//            settings = serverSettings;
//        } else
// COMMENTED OUT BECAUASE OF WW ISSUES
        if (loadedSettings != null && loadedSettings.hasOwnProperty("enabled")) {
            // log("Using settings from local settings.");
            settings = loadedSettings;
        } else {
            // log("Looks like you don't have settings yet. Using the default settings.");
            settings = defaultSettings;
        }

        // Set up presets
        var presets = settings.presets;
        for (var i = 0; i < presets.length; i++) {
            var preset = presets[i];
            if (preset == null || preset.name == "") {
                settings.presets.splice(i, 1);
                saveSettings();
            }
            for (var key in preset) {
                if (preset.hasOwnProperty(key)) {
                    // If preset has value
                    if (key == "color") {
                        $("#wmech_preset" + (i + 1) + key).val(preset[key]);
                    } else if (key == "permanent") {
                        if (preset[key]) {
                            $("#wmech_preset" + (i + 1) + key).attr("checked", "checked");
                        }
                    } else if (key == "nodes" || key == "direction") {
                        $("#wmech_preset" + (i + 1) + key).val(preset[key]);
                    } else {
                        $("#wmech_preset" + (i + 1) + key).val(preset[key]);
                    }
                }
            }
        }
        if (settings.settingsCheckboxes) {
            var settingsCBs = settings.settingsCheckboxes;
            for (var cbKey in settingsCBs) {
                if (settingsCBs[cbKey]) {
                    $("#wmech_setting" + cbKey).attr("checked", "checked");
                }
            }
        }
        if (settings.settingsInputs) {
            var settingsInputs = settings.settingsInputs;
            for (var key in settingsInputs) {
                $("#wmech_setting" + key).val(settingsInputs[key]);
            }
            if (settings.settingsInputs.hasOwnProperty('customcs')) {
                customCSmin = settings.settingsInputs.customcs;
            }
        }
        initCSS();
        loadDropdown();
    }

    function loadDropdown() {
        $("#wmech_presetchooser").find('option').remove();
        var newPresetIndex = 0,
            visibleIndex = 0;
        $(".wmech_namepreset").each(function(i, e) {
            var val = $(e).val();
            if (val.length > 0) {
                $("#wmech_presetchooser").append($('<option>', {
                    value: i,
                    text: "Preset " + (i + 1) + " - " + val
                }));
                newPresetIndex = i;
            }
            if ($("#wmech_presetchooser").children().length < 1) {
                $("#wmech_presetchooser").append($('<option>', {
                    value: 0,
                    text: "Preset 1 - "
                }));
            }
            if ($(this).is(":visible")) {
                visibleIndex = i;
            }
        });
        $("#wmech_presetchooser").append($('<option>', {
            value: newPresetIndex + 1,
            text: "Preset " + (newPresetIndex + 1 + 1) + " - Add an option..."
        }));
        $("#wmech_presetchooser").val(visibleIndex);
    }

    var observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            // Mutation is a NodeList and doesn't support forEach like an array
            for (var i = 0; i < mutation.addedNodes.length; i++) {
                var addedNode = mutation.addedNodes[i];

                // Only fire up if it's a node
                if (addedNode.nodeType === Node.ELEMENT_NODE && addedNode.className=='segment-feature-editor') {
                    var closuresPanel = addedNode.querySelector('.closures');
                    if (closuresPanel) {
                        setup();
                    }
                }
            }
        });
    });

    function attachObserver() {
        log("Observing...");
        WazeWrap.Events.unregister("selectionchanged", null, attachObserver);
        if (document.querySelector("#edit-panel")) {
            observer.observe(document.querySelector("#edit-panel"), { childList: true, subtree: true });
            // setup();
        } else {
            WazeWrap.Events.register("selectionchanged", null, attachObserver);
        }
    }

    function setup() {
        addClosureButtons();
        addPanelWatcher();
        addClosureCounter();
        formatClosureList();
//         $(".toggleHistory").click(function() {
//             setTimeout(addEnhancedClosureHistory, 1000);
//         });
    }

    function addClosureCounter() {
        // TODO In future, make this sorted by active/scheduled
        var num = $(".closure-item").length;
        var msg = "Road Closures (" + num.toString() + ")";
        if ($("#wmech-counter").length == 0)
            $(".closures-tab").append("<div id='wmech-counter'></div>");
        $("#wmech-counter").text(msg);
        setTimeout(function() {
            if ($(".closure-list").hasClass("active")) {
                addClosureCounter();
            }
        }, 1000);
    }

    function addClosureButtons() {
        var tmpButtonClicks = 0;
        var first = null;
        //alert("Appending node.");
        $(".closures-list").append("<div id='wmech-container' style='margin-top: 10px;'></div>");
        var presetCount = 1;
        for (presetCount = 1; presetCount < G_AMOUNTOFPRESETS; presetCount++) {
            var nameInput = $("#wmech_preset" + presetCount + "name").val();
            var timeInput = $("#wmech_preset" + presetCount + "timeString").val();
            var color = $(".wmech_colorinput").eq(presetCount - 1).val();
            var textColor = getTextContrastColor(color);
            if (nameInput) {
                $("#wmech-container").append(
                    $('<button>', {
                        id: ('wmechButton' + presetCount),
                        class: 'wmech_closurebutton',
                        style: ('background-color: ' + color + '; color:' + textColor)
                    }).text(nameInput).attr("data-preset-val", presetCount - 1).on("click", function() {
                        clickClosure($(this), false);
                        radio = "no";
                    }));
            }
        }
    }

    function getTextContrastColor(hex) {
        var match = hex.match(/#(.{2})(.{2})(.{2})/);
        var red = parseInt(match[1], 16);
        var green = parseInt(match[2], 16);
        var blue = parseInt(match[3], 16);
        var calc = (red * 0.299 + green * 0.587 + blue * 0.114);
        return (calc > 150) ? "black" : "white";
    }

    function formatClosureList() {
        $(".details").css("padding", "0 25px 0 15px");
        $(".direction .dir-label").css("margin", "0").css("padding", "0");
        $(".closures-list .direction").css("line-height", "15px").css("height", "20px").css("margin-left", "6px");
        $(".closure-item").css("margin-bottom", "5px").css("padding", "0");
        $(".section").css("padding", "0");
        $(".dates").css("margin-left", "10px");
        $(".closure-title").css("padding", "0").css("min-height", "19px");
        $(".buttons").css("top", "0px");
        //        $("#sidebar .tab-content").css("overflow", "visible").css("overflow-x", "visibile");
        //        $(".closures-list-items").css({"overflow-y": "visible", "padding": 0});
    }

    function addClosureCheckboxes(reason = "addPanelWatcher()") {
        makeBulkButtons();
        $("li.closure-item").each(function() {
            var $checkboxDiv = $("<div />");
            var $checkbox = $("<input />", { type: "checkbox", "class": "wmech_bulkCheckbox" }).css("height", "100%").css("margin-top", "0");
            $checkboxDiv.css("vertical-align", "middle").css("position", "relative").css("margin-left", "4px");
            $checkboxDiv.append($checkbox);
            if ($( this ).find(".wmech_bulkCheckbox").length == 0) {
                $( this ).css("display", "flex").css("margin-bottom", "5px");
                $( this ).wrapInner("<div style='margin-left: 4px; width: 90%;'></div>");
                $( this ).prepend($checkboxDiv);
            }
        });
        $(":checkbox.wmech_bulkCheckbox").click(function(e) {
            toggleBulkButtons();
            e.stopPropagation();
        });

        // Add select all closures checkbox
        //         if ($("#wmech_selectAllDiv").length > 0) {
        //             let cccc = $("#wmech_selectAllDiv").length;

        if ($(".closure-item").length > 1) {
            var holderDiv = $("<div />", { id: "wmech_selectAllDiv" }).css("margin-bottom", "4px");
            holderDiv.append(
                $("<input />", { type: "checkbox", id: "wmech_selectAllCheckbox" }).click(function() {
                    $(".wmech_bulkCheckbox").prop("checked", this.checked);
                    toggleBulkButtons();
                }));
            holderDiv.append($("<p />", { id: "wmech_selectAllText" }).text("Select all closures"));
            $(".full-closures").prepend(holderDiv);
        }
        //        }
    }

    function toggleBulkButtons() {
        if ($(":checkbox.wmech_bulkCheckbox:checked").length == 0)
            hideBulkButtons();
        else
            showBulkButtons();
        if ($(":checkbox.wmech_bulkCheckbox:checked").length == 0) {
            $("#wmech_selectAllCheckbox").prop("checked", false);
        } else if ($(":checkbox.wmech_bulkCheckbox:checked").length == $(":checkbox.wmech_bulkCheckbox").length) {
            $("#wmech_selectAllCheckbox").prop("checked", true);
        }
    }

    function makeBulkButtons() {
        var $buttonDiv = $("<div />", { id: "wmech_bulkButtonDiv" }).css("margin-bottom", "10px");
        var $deleteAllButton = $("<button />", { id: "wmech_bulkDeleteAll", "class": "wmech_closurebutton" }).css("background-color", "red").css("color", "white").text("Delete Selected Closures");
        var $xButton = $("<button />", { id: "wmech_bulkX", "class": "wmech_closurebutton" }).css("background-color", "black").css("color", "red").css("float", "right").css("width", "10%").text("X");
        var $cloneButton = $("<button />", { id: "wmech_bulkClone", "class": "wmech_closurebutton" }).css("background-color", "green").css("color", "white").text("Simple Clone");
        // var $propertiesButton = $("<button />", { id: "wmech_bulkProperties", "class": "wmech_closurebutton" }).css("background-color", "orange").css("color", "white").text("Edit Properties");
        $buttonDiv.append($xButton);
        $buttonDiv.append($deleteAllButton);
        $buttonDiv.append($cloneButton);
        // $buttonDiv.append($propertiesButton);
        $(".closures-list").prepend($buttonDiv);
        $buttonDiv.hide();
        $("#wmech_bulkX").click(function() {
            hideBulkButtons();
            $(".wmech_bulkCheckbox").prop("checked", false);
            $('#wmech_selectAllCheckbox').prop("checked", false);
        });
        $("li.closure-item, .add-closure-button").click(function() {
            $("#wmech_bulkButtonDiv").remove();
        });
        $("#wmech_bulkDeleteAll").click(deleteAllClosures);
        $("#wmech_bulkClone").click(simpleCloneClosure);
    }

    function showBulkButtons() {
        $("#wmech_bulkButtonDiv").show();
    }

    function hideBulkButtons() {
        $("#wmech_bulkButtonDiv").hide();
    }

    function harvestCloneInfo(si) {
        $(".closure-item").eq(si).click();
        var title = $("#closure_reason").val();
        var dir = $("#closure_direction").val();
        var startDate = $("#closure_startDate").val();
//        var startTime = $("#closure_startTime").val();
        var startTime = $("#edit-panel div.closures div.form-group.start-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        var endDate = $("#closure_endDate").val();
//        var endTime = $("#closure_endTime").val();
        var endTime = $("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        var waitForMTE = setInterval(function() {
            // Every 100 seconds check for late info!
            if ($(".wmech_mtelabel").length > 0) {
                clearInterval(waitForMTE);
                var mte = $(".wmech_mtelabelselected").prev().data("mte-val");
                var permanentChecked = $("#closure_permanent").attr("checked");
                var nodes = []
                $(".fromNodeClosed").each(function() {
                    if ($(this).attr("checked") == "checked") {
                        nodes.push(true);
                    } else {
                        nodes.push(false);
                    }
                });

                // Now, time to add a new closure!
                $(".cancel-button").click();
                $(".add-closure-button").click();
                $("#closure_direction wz-option[value=" + dir +"]").click();
                $("#closure_reason").val(title).change();
                changeDateField("#closure_startDate", startDate);
//                $("#closure_startDate").val(startDate).change();
                changeTimeField($("#edit-panel div.closures div.form-group.start-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"),startTime);
//                $("#closure_startTime").val(startTime).change();
                $(".fromNodeClosed").each(function(i, e) {
                    if (nodes[i]) {
                        $(e).attr("checked", "checked");
                    }
                });
                if (permanentChecked) {
                    $("#closure_permanent").attr("checked", "checked").change();
                }
                addToEndStartDate(0, 1, 0, "start");
                if (mte == "") {
                    $("#closure_eventId").val("").change();
                    setTimeout(function() {
                        $("#closure_eventId").removeAttr("value");
                    }, 10);
                } else {
                    $("#closure_eventId").val(mte).change();
                }
                setTimeout(function() {
                    // Wait for default end date/time adjustment
                    changeDateField("#closure_endDate", endDate);
//                    $("#closure_endDate").val(endDate).change();
                    changeTimeField($("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"),endTime);
//                    $("#closure_endTime").val(endTime).change();
                    addToEndStartDate(0, 1, 0);
                    addPanelWatcher();
                }, 100);
            }
        }, 100);
    }

    function chooseMTE(name) {
        if ($(".wmech_mtelabel").length > 0) {
            $("label:contains('" + name + "')").click();
        } else {
            setTimeout(function() {
                chooseMTE(name);
            }, 100);
        }
    }

    function simpleCloneClosure() {
        log("Starting simple clone.");
        var checked = getIndexOfSelectedCheckboxes();
        if (checked.length != 1) {
            return WazeWrap.Alerts.error(GM_info.script.name, "Currently, simple clone only allows you to clone one segment at a time.");
        }
        var si = checked[0];
        harvestCloneInfo(si);
        return;
    }

    function waitForPermaAndNodes() {
        if ($("#closure_permanent").length > 1 && $(".fromNodeClosed").length > 1) {
            var permanentChecked = ($("#closure_permanent").attr("checked") == "checked");
            var nodes = [];
            $(".fromNodeClosed").each(function() {
                if ($(this).attr("checked") == "checked") {
                    nodes.push(true);
                } else {
                    nodes.push(false);
                }
            });
            return [permanentChecked, nodes];
        } else {
            setTimeout(waitForPermaAndNodes, 100);
        }
    }

    function parseClosureListingDate(dateString) {
        var pattern = new RegExp(/(.{3}) (.{3}) ([0-9]{2}) ([0-9]{4})/);
        var matches = dateString.match(pattern);
        var dayOfWeek = matches[1];
        var shortMonth = matches[2];
        var day = matches[3];
        var year = matches[4];
        var monthNum = 0;
        switch (shortMonth) {
            case "Jan":
                monthNum = 1;
                break;
            case "Feb":
                monthNum = 2;
                break;
            case "Mar":
                monthNum = 3;
                break;
            case "Apr":
                monthNum = 4;
                break;
            case "May":
                monthNum = 5;
                break;
            case "Jun":
                monthNum = 6;
                break;
            case "Jul":
                monthNum = 7;
                break;
            case "Aug":
                monthNum = 8;
                break;
            case "Sep":
                monthNum = 9;
                break;
            case "Oct":
                monthNum = 10;
                break;
            case "Nov":
                monthNum = 11;
                break;
            case "Dec":
                monthNum = 12;
                break;
        }
        return {
            'month': formatTimeProp(monthNum.toString()),
            'day': formatTimeProp(day.toString()),
            'year': year,
        };
    }

    function getIndexOfSelectedCheckboxes() {
        var checked = [];
        $(".wmech_bulkCheckbox").each(function(i) {
            if ($(this).is(":checked")) { checked.push(i); }
        });
        return checked;
    }

    function deleteAllClosures() {
        var checked = getIndexOfSelectedCheckboxes();
        $("wz-menu-item.delete").on('click.wmech_bulk', function(e) {
            e.stopImmediatePropagation();
        });

        //            var _confirm = window.confirm;
        //    window.confirm = function(msg)
        //    {
        //       var cm_delete_confirm = I18n.lookup("closures.delete_confirm").split('"')[0].trimRight(1);

        //       if(msg.indexOf(cm_delete_confirm) != -1)
        //       {
        //          uroAddLog('intercepted closure delete confirmation...');
        //          if(uroConfirmClosureDelete)
        //          {
        //             return _confirm(msg);
        //          }
        //          else
        //          {
        //             return true;
        //          }
        //       }
        //       else if(typeof(msg) == 'undefined')
        //       {
        //          uroAddLog('Intercepted blank confirmation...');
        //          return true;
        //       }
        //       else
        //       {
        //          return _confirm(msg);
        //       }
        //    };
        // Override window.confirm
        var _confirm = window.confirm;
        var oldConfirm = window.confirm;
        var msg = "Delete Closure?";
        window.confirm = function(msg) {
            log(msg);
            if (msg.indexOf("Delete closure") != -1) {
                return true;
            } else {
                return oldConfirm(msg);
            }
        };
        $("wz-menu-item.delete").each(function(i) {
            if (checked.includes(i)) {
                $(this).click();
            }
        });
        $("wz-menu-item.delete").off('click.wmech_bulk');
        setTimeout(addPanelWatcher, 3000);
    }

    function timeZoneCompare() {
        if ($("#wmech_settingtimezonewarn").is(":checked")) {
            var apiVal = $("#wmech_settingtimezoneapi").val();
            var center = W.map.getCenter();
            var actualCenter = WazeWrap.Geometry.ConvertTo4326(center.lon, center.lat);
            var d = new Date();
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://api.timezonedb.com/v2.1/get-time-zone?key=" + apiVal + "&format=json&by=position&lat=" + actualCenter.lat + "&lng=" + actualCenter.lon,
                responseType: "json",
                onload: resp => {
                    var newD = new Date(resp.response.formatted);
                    var diff = Math.round((newD - d) / (1000 * 60 * 60));
                    var timeZone = resp.response.abbreviation;
                    if (diff < 0) {
                        var msg = (-1 * diff) + " hour" + (diff != -1 ? "s" : "") + " behind.  Make sure to adjust your start time if you want the closure to go live now."
                        } else {
                            var msg = diff + " hour" + (diff != 1 ? "s" : "") + " ahead."
                            }
                    if (diff != 0) {
                        $(".edit-closure > form > div:nth-child(4)").after("<div class='wmech_timezonewarnmessage'><span>Warning, the times for the closure you are adding is " + msg + "</span></div>");
                    }
                }
            });
        }
    }

    function addPanelWatcher() {
        $("li.closure-item, .add-closure-button").off();
        $("li.closure-item, .add-closure-button").click(function() {
            setTimeout(addNodeClosureButtons, 5);
            setTimeout(addDirectionCS, 5);
            setTimeout(addClosureSegInfo, 5);
            setTimeout(addClosureLengthValue, 5);
            setTimeout(addMTERadios, 5);
            setTimeout(addLengthExtenders, 5);
            setTimeout(checkIfNeedToAddPanelWatcher, 5);
            setTimeout(removeClosureLines, 5);
            setTimeout(timeZoneCompare, 5);
            setTimeout(function() {
                $('.edit-closure > form > div.action-buttons > wz-button.cancel-button').click(function() {
                    $('.edit-closure > form > div.action-buttons > wz-button.cancel-button').off();
                    $('.edit-closure [class^="wmech"]').remove();
                    $('.edit-closure [id^="wmech"]').remove();
                    setTimeout(function() { setup(); }, 50);
                })
            }, 20);
        });
        formatClosureList();
        addClosureCheckboxes();
    }

    function numOfSegsSelected() {
        return W.selectionManager.getSegmentSelection().segments.length;
    }

    function getAllStreets() {
        var res1 = W.selectionManager.getSegmentSelection();
        var finalRes = [];
        for (var i = 0; i < res1.segments.length; i++) {
            var seg = res1.segments[i];
            var pID = seg.attributes.primaryStreetID;
            var pS = W.model.streets.getObjectById(pID);
            var name = pS.name;
            finalRes.push((name == null ? "No Name" : name));
        }
        return combineStreets(finalRes);
    }

    function combineStreets(arr) {
        var a = [],
            b = [],
            prev;

        arr.sort();
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] !== prev) {
                a.push(arr[i]);
                b.push(1);
            } else {
                b[b.length - 1]++;
            }
            prev = arr[i];
        }

        var res = [];
        for (i = 0; i < a.length; i++) {
            res.push(a[i] + " (" + b[i] + ")");
        }
        return res;
    }

    function removeClosureLines() {
        $(".form-group").css("border-top", "0");
    }

    function addClosureSegInfo() {
        var segsLength = $(".length-attribute .value").text();
        segsLength = segsLength.replace(/m/, "m / ");
        var numOfSegs = numOfSegsSelected();
        var segLabel = numOfSegs + " segs (" + segsLength + ")";
        $(".edit-closure form").prepend('<div class="form-group">' +
                                        '<span><i class="fa fa-fw fa-chevron-down wmech_seglistchevron"></i></span>' +
                                        '<label id="wmech_seginfolabel" class="control-label" for="closure_reason" style="margin-bottom: 0;">Segments</label>' +
                                        '<label id="wmech_seginfolabel" class="control-label" style="font-weight: normal;">' + segLabel + '</label>' +
                                        '<div class="controls"><ul id="wmech_seginfonames">' + '</ul></div></div>');
        $(".edit-closure form .form-group").first().click(collapseSegList);
        if ($("#wmech_settingseglistcollapse").prop("checked")) {
//            radio = "checked"
            collapseSegList();
        }
        var streets = getAllStreets();
        for (var i = 0; i < streets.length; i++) {
            $("#wmech_seginfonames").append("<li>" + streets[i] + "</li>");
        }
    }

    function collapseSegList() {
        $(".edit-closure form .form-group").first().find("ul").toggle();
        $(".wmech_seglistchevron").toggleClass("fa-chevron-down fa-chevron-up");
    }

    function updateClosureLength() {
        $("#wmech_closurelengthval").text(closureLength());
    }

    function addClosureLengthValue() {
        $(".form-group.end-date-form-group").after('<div class="form-group">' +
                                                   '<label class="control-label" for="closure_reason">Closure Length</label>' +
                                                   '<div class="controls" style="text-align: center;">' +
                                                   '<span id="wmech_closurelengthval"></span>' +
                                                   '</div></div>');
        $("#wmech_closurelengthval").text(closureLength());
        $("#closure_startDate, " +
          "#closure_endDate, " +
          ".time-picker-input").on('change paste keyup input', function() {
            setTimeout(updateClosureLength,50);
        });
    }

    function closureLength() {
        var startDate = $("#closure_startDate").val();
//        var startTime = $("#closure_startTime").val();
        var startTime = $("#edit-panel div.closures div.form-group.start-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        var endDate = $("#closure_endDate").val();
//        var endTime = $("#closure_endTime").val();
        var endTime = $("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        //        var regex = /(.*)(\-|\.|\/)(.*)(\-|\.|\/)(.*)/;
        var regex = /(\d*)(\-|\.|\/)(\d*)(\-|\.|\/)(\d*)(.*)/;
        var startDateResult = regex.exec(startDate);
        var endDateResult = regex.exec(endDate);
        if (DateFormat == "ddmmyyyy"){
            var startYear = startDateResult[5];
            var startMonth = startDateResult[3];
            var startDay = startDateResult[1];
            var endYear = endDateResult[5];
            var endMonth = endDateResult[3];
            var endDay = endDateResult[1];
        }else{
            if (DateFormat == "yyyymmdd"){
                var startYear = startDateResult[1];
                var startMonth = startDateResult[3];
                var startDay = startDateResult[5];
                var endYear = endDateResult[1];
                var endMonth = endDateResult[3];
                var endDay = endDateResult[5];
            }else{
                var startYear = startDateResult[5];
                var startMonth = startDateResult[1];
                var startDay = startDateResult[3];
                var endYear = endDateResult[5];
                var endMonth = endDateResult[1];
                var endDay = endDateResult[3];
            }}
        var regex2 = /(.*):(.*)/;
        var startTimeResult = regex2.exec(startTime);
        var startHour = startTimeResult[1];
        var startMin = startTimeResult[2];
        var endTimeResult = regex2.exec(endTime);
        var endHour = endTimeResult[1];
        var endMin = endTimeResult[2];
        var d1 = new Date(startYear, parseInt(startMonth) - 1, startDay, startHour, startMin, 0, 0);
        var d2 = new Date(endYear, parseInt(endMonth) - 1, endDay, endHour, endMin, 0, 0);
        if (d2 - d1 < 0) {
            endDateBeforeStartDate();
            return "End date is before start date!";
        } else {
            endDateAfterStartDate();
        }
        var dif = dateDiff(d1, d2);
        var finalString = [];
        if (dif['year'] > 0) {
            finalString.push(dif['year'] + " year" + (dif['year'] != 1 ? "s" : ""));
        }
        if (dif['month'] > 0) {
            finalString.push(dif['month'] + " month" + (dif['month'] != 1 ? "s" : ""));
        }
        if (dif['week'] > 0) {
            finalString.push(dif['week'] + " week" + (dif['week'] != 1 ? "s" : ""));
        }
        if (dif['day'] > 0) {
            finalString.push(dif['day'] + " day" + (dif['day'] != 1 ? "s" : ""));
        }
        if (dif['hour'] > 0) {
            finalString.push(dif['hour'] + " hour" + (dif['hour'] != 1 ? "s" : ""));
        }
        if (dif['minute'] > 0) {
            finalString.push(dif['minute'] + " minute" + (dif['minute'] != 1 ? "s" : ""));
        }
        return finalString.join(", ");
    }

    function endDateBeforeStartDate() {
        $("#wmech_closurelengthval").css("color", "red");
        $(".edit-closure").css("background-color", "#f7b0b0");
        $(".edit-closure").find("input, select").css("background-color", "#ffd1d1");
        $(".closure-node-item").css("background-color", "#ffd1d1");
    }

    function endDateAfterStartDate() {
        $("#wmech_closurelengthval").css("color", "black");
        $(".edit-closure").css("background-color", "#eeeeee");
        $(".edit-closure").find("input, select").css("background-color", "#fff");
        $(".closure-node-item").css("background-color", "#f2f4f7");
    }

    function dateDiff(d1, d2) {
        // Thank you for this code RienNaVaPlus (https://stackoverflow.com/a/32514236)!
        var d = Math.abs(d2 - d1) / 1000; // delta
        var r = {}; // result
        var s = { // structure
            year: 31536000,
            month: 2592000,
            week: 604800, // uncomment row to ignore
            day: 86400, // feel free to add your own row
            hour: 3600,
            minute: 60,
            second: 1
        };

        Object.keys(s).forEach(function(key) {
            r[key] = Math.floor(d / s[key]);
            d -= r[key] * s[key];
        });

        return r;
    };

    function addDirectionCS() {
        var DirLen = W.selectionManager.getSelectedWMEFeatures().length
        var segDir;
        if (DirLen > 1) {
            segDir = 3
            $("#closure_direction wz-option[value='3']").click();
        }else{
            segDir = $("#closure_direction").val();
        }

        var directionalCursors = $("#wmech_settingdircsdircur").is(":checked");
        $("#closure_direction").after("<div id='wmech_dBAB' class='wmech_closureButton wmech_dirbutton'>A → B</div>" +
                                      "<div id='wmech_dBBA' class='wmech_closureButton wmech_dirbutton'>B → A</div>" +
                                      "<div id='wmech_dBTW' class='wmech_closureButton wmech_dirbutton'>Two way (⇆)</div>");
        var permDir = "";
        if ($(".heading").length > 0 && numOfSegsSelected() <= 1) {
            if ($(".letter-circle:eq(0)").text() == "A") {
                var dir = $(".heading:eq(0)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(0)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            } else {
                var dir = $(".heading:eq(0)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(1)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            }
            if ($(".letter-circle:eq(2)").text() == "A") {
                var dir = $(".heading:eq(1)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(0)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            } else if ($(".letter-circle:eq(2)").text() == "B") {
                var dir = $(".heading:eq(1)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(1)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            }
        }
        $("#wmech_dBAB").click(function() {
            $("#closure_direction wz-option[value='1']").click();
            $("#wmech_dBAB").css('background-color', '#26bae8');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#ddd');
        });
        $("#wmech_dBBA").click(function() {
            $("#closure_direction wz-option[value='2']").click();
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#26bae8');
            $("#wmech_dBTW").css('background-color', '#ddd');
        });
        $("#wmech_dBTW").click(function() {
            $("#closure_direction wz-option[value='3']").click();
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#26bae8');
        });
        if (segDir == 1) {
            // Segment direction is A --> B
            $("#wmech_dBBA, #wmech_dBTW").remove();
            $("#wmech_dBAB").css('background-color', '#26bae8');
        } else if (segDir == 2) {
            // Segment direction is B --> A
            $("#wmech_dBAB, #wmech_dBTW").remove();
            $("#wmech_dBBA").css('background-color', '#26bae8');
        }
        $("#wmech_dBTW").css("cursor", (directionalCursors ? "pointer" : determineCursorDouble(permDir)));
        $("#wmech_dBTW").css('background-color', '#26bae8');
    }

    function determineCursor(dir) {
        if (dir == "south") return "s-resize";
        if (dir == "north") return "n-resize";
        if (dir == "east") return "e-resize";
        if (dir == "west") return "w-resize";
        if (dir == "southeast") return "se-resize";
        if (dir == "northwest") return "nw-resize";
        if (dir == "northeast") return "ne-resize";
        if (dir == "southwest") return "sw-resize";
        if (dir.length < 1) return "help";
    }

    function determineCursorDouble(dir) {
        if (dir == "east" || dir == "west") return "ew-resize";
        if (dir == "north" || dir == "south") return "ns-resize";
        if (dir == "northeast" || dir == "southwest") return "nesw-resize";
        if (dir == "northwest" || dir == "southeast") return "nwse-resize";
    }

    function addLengthExtenders() {
        var $html = [
            '<span id="wmech_lEB1m" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #f5ffba;">+1m</span>',
            '<span id="wmech_lEB15m" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #f5ffba;">+15m</span>',
            '<span id="wmech_lEB1h" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #c9ffba;">+1h</span>',
            '<span id="wmech_lEB2h" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #c9ffba;">+2h</span>',
            '<span id="wmech_lEB1d" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #bafff7;">+1d</span>',
            '<span id="wmech_lEB1w" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #bdbaff;">+1w</span>',
            '<span id="wmech_lEB1mo" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #ffbaf9;">+1mo</span>',
            '<span id="wmech_lEBcustomMin" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #ffffff;">custom</span>',
        ].join("\n");
        $("#wmech_closurelengthval").after("<div id='wmech_timeExtenderDiv'></div>");
        $("#wmech_timeExtenderDiv").append($html);
        if (customCSmin == "") {
            $("#wmech_lEBcustomMin").css('visibility', 'hidden');
        } else {
            $("#wmech_lEBcustomMin").text(customCSmin + "m");
        }
        $("#wmech_lEB1m").click(function() { addToEndStartDate(0, 0, 1); });
        $("#wmech_lEB15m").click(function() { addToEndStartDate(0, 0, 15); });
        $("#wmech_lEB1h").click(function() { addToEndStartDate(0, 0, 60); });
        $("#wmech_lEB2h").click(function() { addToEndStartDate(0, 0, 120); });
        $("#wmech_lEB1d").click(function() { addToEndStartDate(0, 1, 0); });
        $("#wmech_lEB1w").click(function() { addToEndStartDate(0, 7, 0); });
        $("#wmech_lEB1mo").click(function() { addToEndStartDate(1, 0, 0); });
        $("#wmech_lEBcustomMin").click(function() { addToEndStartDate(0, 0, customCSmin); });
    }

    function addToEndStartDate(o, d, m, type = "end") {
        var LY;
        var finalDate;
        var finalTime;
        var endDate = $("#closure_" + type + "Date").val();
        var endTime = $("#edit-panel div.closures div.form-group." + type + "-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val();
//        var endTime = $("#closure_" + type + "Time").val();
        //            var regex = /(.*)\/(.*)\/(.*)/;
        //        var regex = /(.*)(\-|\.|\/)(.*)(\-|\.|\/)(.*)/;
        var regex = /(\d*)(\-|\.|\/)(\d*)(\-|\.|\/)(\d*)(.*)/;
        var endDateResult = regex.exec(endDate);
        if (DateFormat == "ddmmyyyy"){
            var endYear = endDateResult[5];
            var endMonth = endDateResult[3];
            var endDay = endDateResult[1];
        }else{
            if (DateFormat == "yyyymmdd"){
                var endYear = endDateResult[1];
                var endMonth = endDateResult[3];
                var endDay = endDateResult[5];
            }else{
                var endYear = endDateResult[5];
                var endMonth = endDateResult[1];
                var endDay = endDateResult[3];
            }}
        // fix for last day of month and adding 1 month with clicksaver to ensure it is actually last day of following month.
        if (endYear == "2024" || endYear == "2028" || endYear == "2032" || endYear == "2036" || endYear == "2040" || endYear == "2044" || endYear == "2048") LY = "yes";
        if (o == 1){
            if (endMonth == "04" || endMonth == "06" || endMonth == "07" || endMonth == "09" || endMonth == "11" || endMonth == "12"){
                if (endDay == "30"){
                    o = 0;
                    d = 31;
                }
            }else{
                if (endMonth == "03" || endMonth == "05" || endMonth == "08" || endMonth == "10"){
                    if (endDay == "31"){
                        o = 0;
                        d = 30;
                    }
                }else{
                    if (endMonth == "02" && endDay > "27"){
                        o = 0;
                        if (LY == "yes"){
                            d = 31;
                        }else{
                            d = 30;
                        }
                    }else{
                        if (endMonth == "01" && endDay > "27"){
                            o = 0;
                            if (LY == "yes"){
                                d = 31 - (endDay - 29);
                            }else{
                                d = 31 - (endDay - 28);
                            }
                        }
                    }
                }
            }
        }

        var regex2 = /(.*):(.*)/;
        var endTimeResult = regex2.exec(endTime);
        var endHour = endTimeResult[1];
        var endMin = endTimeResult[2];
        var res = new Date(endYear, parseInt(endMonth) - 1, endDay, endHour, endMin, 0, 0);
        res.setTime(res.getTime() + (m * 60 * 1000));
        res.setDate(res.getDate() + d);
        res.setMonth(res.getMonth() + o);
        if (DateFormat == "ddmmyyyy"){
            finalDate = formatTimeProp(formatTimeProp(res.getDate())) + dateSeparator + (parseInt(res.getMonth()) + 1) + dateSeparator + res.getFullYear();
        }else{
            if (DateFormat == "yyyymmdd"){
                finalDate = res.getFullYear() + dateSeparator + (parseInt(res.getMonth()) + 1) + dateSeparator + formatTimeProp(res.getDate());
            }else{
                finalDate = formatTimeProp(parseInt(res.getMonth()) + 1) + dateSeparator + formatTimeProp(res.getDate()) + dateSeparator + res.getFullYear();
            }}
        finalTime = formatTimeProp(res.getHours()) + ":" + formatTimeProp(res.getMinutes());
//        $("#closure_" + type + "Date").val(finalDate).change();
        changeDateField("#closure_" + type + "Date", finalDate);
//        $("#closure_" + type + "Time").val(finalTime).change();
        changeTimeField($("#edit-panel div.closures div.form-group." + type + "-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"),finalTime);
    }

    function changeDateField(element, newDate) {
        const newDateObj = $(element).data('daterangepicker')
        newDateObj.setStartDate(newDate)
        $(element).trigger(
            'apply.daterangepicker',
            [newDateObj]
        )
    }

    function changeTimeField($element, newtime) {
         $element.timepicker('setTime',newtime);
    }

    function formatTimeProp(num) {
        return ("0" + num).slice(-2);
    }

    function addNodeClosureButtons() {
        $(".closure-nodes.form-group > wz-label").after("<span id='wmech_nCBNone' class='wmech_closureButton  wmech_nodeClosureButton'>None</span>" +
                                                        "<span id='wmech_nCBAll' class='wmech_closureButton wmech_nodeClosureButton'>All</span>" +
                                                        "<span id='wmech_nCBMiddle'class='wmech_closureButton wmech_nodeClosureButton'>Middle</span>" +
                                                        "<span id='wmech_nCBEnds'class='wmech_closureButton wmech_nodeClosureButton'>Ends</span>");
        $(".wmech_nodeClosureButton").unbind();
        $("#wmech_nCBNone").click(toggleNoNodes);
        $("#wmech_nCBAll").click(toggleAllNodes);
        $("#wmech_nCBMiddle").click(toggleMiddleNodes);
        $("#wmech_nCBEnds").click(toggleEndsNodes);
    }

    function toggleNoNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", false, colorize);
        $("#wmech_nCBNone").css('background-color', '#26bae8');
        $("#wmech_nCBAll").css('background-color', '#ddd');
        $("#wmech_nCBMiddle").css('background-color', '#ddd');
        $("#wmech_nCBEnds").css('background-color', '#ddd');
    }

    function toggleAllNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", true, colorize);
        $("#wmech_nCBNone").css('background-color', '#ddd');
        $("#wmech_nCBAll").css('background-color', '#26bae8');
        $("#wmech_nCBMiddle").css('background-color', '#ddd');
        $("#wmech_nCBEnds").css('background-color', '#ddd');
    }

    function toggleMiddleNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", true, colorize);
        panelToggleNodes(".fromNodeClosed:first", false, colorize);
        panelToggleNodes(".fromNodeClosed:last", false, colorize);
        $("#wmech_nCBNone").css('background-color', '#ddd');
        $("#wmech_nCBAll").css('background-color', '#ddd');
        $("#wmech_nCBMiddle").css('background-color', '#26bae8');
        $("#wmech_nCBEnds").css('background-color', '#ddd');
    }

    function toggleEndsNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", false, colorize);
        panelToggleNodes(".fromNodeClosed:first", true, colorize);
        panelToggleNodes(".fromNodeClosed:last", true, colorize);
        $("#wmech_nCBNone").css('background-color', '#ddd');
        $("#wmech_nCBAll").css('background-color', '#ddd');
        $("#wmech_nCBMiddle").css('background-color', '#ddd');
        $("#wmech_nCBEnds").css('background-color', '#26bae8');
    }

    function panelToggleNodes(selector, setting, colorize = false) {
        $(selector).each(function() {
            this.checked = setting;
            $(this).change();
            if (colorize) {
                setTimeout(function() {
                    colorizeRow(this);
                }, 20);
            }
        });
    }

    function colorizeRow(elem) {
        var root = elem; //.shadow-root;
        $(root).find(".wz-slider").css("background-color", "rgb(63, 188, 113)");
        $(elem).parent().parent().css("background-color", "rgba(63, 188, 113, 0.4)");
        $(elem).one("click", function() {
            uncolorizeRow(elem);
        });
    }

    function uncolorizeRow(button) {
        var root = button; // .shadow-root;
        $(root).find(".wz-slider").css("background-color", "");
        $(button).parent().parent().css("background-color", "rgb(242, 244, 247);");
    }

    function addMTERadios() {
        if (radio != "no"){
         $("#closure_eventId").parent().css("height", 0).css("overflow", "hidden");
        $("#closure_eventId").removeAttr("required");
        $(".label-with-tooltip").after("<div id='wmech_mteradiosdiv'><form id='wmech_mteradiosform' name='wmech_mte'></form></div>");
        var to = $("#closure_eventId").children().length - 1;
        for (var i = 0; i < to; i++) {
            var labelText = $("#closure_eventId wz-option:nth-child(" + (i + 1) + ")").text();
            var labelVal = $("#closure_eventId wz-option:nth-child(" + (i + 1) + ")").val();
            $("#wmech_mteradiosform").append('<div><input id="testButton' + i + '" type="radio" name="wmech_mte" data-mte-val="' + labelVal + '"><label for="testButton' + i + '" class="wmech_mtelabel">' + labelText + '</label></div>');
        }
        $('input[type=radio][name="wmech_mte"]').change(function() {
            if (this.id == "testButton0") {
                $("#closure_eventId").removeAttr("value");
            } else {
                $("#closure_eventId").val($(this).data("mte-val")).change();
            }
            $(".wmech_mtelabel").removeClass("wmech_mtelabelselected");
            $("label[for='" + this.id + "']").addClass("wmech_mtelabelselected");
        });
        var firstSelected = $("#closure_eventId").val();
        if (firstSelected == "") {
            $("#closure_eventId").val("").change();
            setTimeout(function() {
                $("#closure_eventId").removeAttr("value");
            }, 100);
            $("input[data-mte-val='']").click();
        } else {
            $("input[data-mte-val='" + firstSelected + "']").click();
        }
        }
        radio = ""
    }

    function checkIfNeedToAddPanelWatcher() {
        setTimeout(function() {
            if ($("#closure_permanent").length == 0) {
                addPanelWatcher();
            } else {
                checkIfNeedToAddPanelWatcher();
            }
        }, 1000);
    }

    function initCSS() {
        log("Initializing CSS.");
        $("<style id='wmechStyle'></style>").appendTo("head");
        $("#wmechStyle").html([
            ".wmechClosureDetailsDiv { background-color: rgba(63, 188, 113, 0.5); margin: 5px; padding: 5px; border-radius: 5px; }",
            ".wmechMainText { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; margin-bottom: 0px !important; line-height: 1; margin-left: 10px; }",
            ".wmechSettingsLabel { white-space: pre-line !important; display: inline-block; font-weight: none !important; padding-left: 5px; }",
            ".wmechSettingsDiv { position: relative; }",
            ".wmech_input { border-radius: 5px; border: 1px solid lightgray; margin: 1px; height: 25px !important;  }",
            ".wmech_input.wmech_inputregex:nth-of-type(2) { width: 40%}",
            ".wmech_input.wmech_inputregex:nth-of-type(3) { width: 20%}",
            ".wmech_input.wmech_inputregex:nth-of-type(4) { width: 30%}",
            ".wmech_inputpreset { width: 100%; text-align: center; }",
            ".wmech_presetcheckbox { margin-right: 10px !important; margin-top: 5px !important; }",
            ".wmech_closurebutton { font-weight: bold; width: 100%; height: 25px; margin: 2px 0; border-radius: 5px; background-color: white; }",
            "#wmechStringAppendSpan { font-weight: bold; }",
            "#wmechStringAppendSpan::before { content: 'String: '; }",
            ".wmech_colorinputlabel { background-color: black; width: 100%; height: 25px; border-radius: 5px; text-align: center; }",
            ".wmechClosureTrackingContainer { margin-bottom: 2px; font-family: font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; border-radius: 5px; background-color: red; clear: both; padding: 5px; } ",
            ".wmechClosureTrackingContainer h2 { line-height: 20px; font-size: 16px; font-weight: bold; padding-bottom: 0px !important; color: white; }",
            ".wmechClosureTrackingContainer ul { list-style-position: inside; background-color: white; padding: 5px; margin-bottom: 0px; border-radius: 5px;} ",
            ".wmechCTCreated { color: purple; } ",
            ".wmechCTUpdated { color: blue; } ",
            ".wmechCTOpened { color: #33b300; } ",
            ".wmechCTButton { margin: 0 2px 2px 0; padding: 2px; font-weight: bold; display: inline-block; background-color: white; border-radius: 2px; font-size: 12px; line-height: 1; } ",
            ".wmechCTPL { color: blue; } ",
            ".wmechCTVisit { color: blue; } ",
            ".wmechCTOpen { background-color: #82b57f; color: white; } ",
            ".wmechCTExtend { background-color: #ffdc00; } ",
            ".wmechCTSubmitPL { background-color: #82b57f; color: white; border-radius: 5px;} ",
            ".wmech_closureButton { text-align: center; font-family: 'Rubik', 'Boing-light', sans-serif; font-weight: 700; border: 1px solid gray; background-color: #ddd; color: black; border-radius: 5px; font-size: 11px; cursor: pointer;} ",
            ".wmech_nodeClosureButton { display: inline-block; width: 23%; margin: 1%;  }",
            ".wmech_dirbutton { width: 100%; margin: 0.3em 0; }",
            ".wmech_buttonNotAllowed { background: lightgray; color: gray; cursor: not-allowed; }",
            ".wmech_colorinput { width: 20%; } ",
            "#wmech_mteradiosdiv { background-color: #f2f4f7; overflow-y: scroll; height: 100px; border: 1px solid gray; border-radius: 5px; padding: 5px; margin-bottom: 2px; } ",
            "input[name='wmech_mte'] { margin-right: 3px; } ",
            ".wmech_lengthExtenderButton { margin: 0 1px; padding: 0 4px; color: black; } ",
            "#wmech_presetchooser { width: 100%; height: 25px; }",
            ".wmech_presetlabel { margin-left: 10px; height: 25px; }",
            ".wmech_presetsetting { margin-right: 10px; float: right; }",
            ".wmech_presetdropdown { height: 25px; } ",
            ".wmech_mtelabel { font-weight: normal; font-size: 14px; }",
            ".wmech_mtelabelselected { font-weight: bold; }",
            ".wmech_seglistchevron { position: absolute; cursor: pointer; font-size: 14px; float: right; width: 100%; text-align: right; margin: 5px 5px 0 0; }",
            ".wmech_presetpermatooltip { margin-left: 10px; }",
            "#wmech_selectAllCheckbox { margin-left: 4px; } ",
            "#wmech_selectAllDiv { margin-bottom: 4px } ",
            "#wmech_selectAllText { font-weight: bold; margin-left: 4px; display: inline }",
            ".wmech_settingsheader { font-weight: bold; margin-bottom: 0 !important; }",
            ".wmech_timezonewarnmessage { text-align: center }",
            ".wmech_timezonewarnmessage span { font-weight: bold; color: black; background-color: red }",
            ".wmech_settingsinput { text-align: center; width: 100%; }",
            ".wmech-tab-pane { width: 100%; display: none; padding: 15px 0; }",
            ".wmech-tab-pane .active { width: 100%; display: block; padding: 15px 0; }",
            ".wmech-alert { background-color: #00FFFF; border-radius: 5px; display: none; }"
        ].join('\n\n'));
    }

    async function clickClosure(elem, dbl = false) {
        if (W.model.actionManager._undoStack.length > 0) {
            return WazeWrap.Alerts.error(GM_info.script.name, "Can't add closure because you have unsaved edits.");
        }
        $("wz-button.add-closure-button").click();
        await new Promise(r => setTimeout(r, 100));
        var ruleIndex = parseInt($(elem).data("preset-val"));
        var nameString = $("#wmech_preset" + (ruleIndex + 1) + "reason").val();
        $("#closure_reason").val(closureName(nameString)).change();
        if ($("#wmech_preset" + (ruleIndex + 1) + "timeString").val().length > 0) {
            var ruleParsed = parseRule($("#wmech_preset" + (ruleIndex + 1) + "timeString").val());
            changeDateField("#closure_endDate", ruleParsed[0]);
//            $("#closure_endDate").val(ruleParsed[0]).change();
            changeTimeField($("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"), ruleParsed[1]);
//            $("#closure_endTime").val(ruleParsed[1]).change();
        }
        var permClosures = $(".wmech_presetcheckbox").eq(ruleIndex).prop("checked");
        if (permClosures) {
            setTimeout(function() {
                $("#closure_permanent").prop("checked", "checked").change();
            }, 50);
        }
        var nodeClosuresOption = $("#wmech_preset" + (ruleIndex + 1) + "nodes").val();
        if (nodeClosuresOption == "Middle") {
            setTimeout(function() {
                toggleMiddleNodes(true);
            }, 50);
        }
        if (nodeClosuresOption == "All") {
            setTimeout(function() {
                toggleAllNodes(true);
            }, 50);
        }
        if (nodeClosuresOption == "Ends") {
            setTimeout(function() {
                toggleEndsNodes(true);
            }, 50);
        }
        if (nodeClosuresOption == "None") {
            setTimeout(function() {
                toggleNoNodes(true);
            }, 50);
        }
        setTimeout(function() {
            $("#closure_reason").css("background-color", "rgba(63, 188, 113, 0.5)");
            $("#closure_endDate").css("background-color", "rgba(63, 188, 113, 0.5)");
            $("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").css("background-color", "rgba(63, 188, 113, 0.5)");
//            $("#closure_endTime").css("background-color", "rgba(63, 188, 113, 0.5)");
            if (permClosures) {
                $(".edit-closure > form > div > #closure_permanent").css("color", "rgba(63, 188, 113, 1)");
            }
        }, 20);
        var direction = $("#wmech_preset" + (ruleIndex + 1) + "direction").val();
        var dirNumber = 3;
        if (direction == "Two Way") {
            dirNumber = 3;
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#26bae8');
        } else if (direction == "A --> B") {
            dirNumber = 1;
            $("#wmech_dBAB").css('background-color', '#26bae8');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#ddd');
        } else if (direction == "B --> A") {
            dirNumber = 2;
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#26bae8');
            $("#wmech_dBTW").css('background-color', '#ddd');
        }
        $("#closure_direction wz-option[value='" + dirNumber + "']").click();

        var mteRegEx = $("#wmech_preset" + (ruleIndex + 1) + "mteString").val();
        if (mteRegEx.length > 0) {
            var mteFuncResult = matchMTE(mteRegEx);
            if (mteFuncResult != false) {
                $("#closure_eventId").val(mteFuncResult.val.toString());
            }
        }else{
            document.querySelector("#closure_eventId > wz-option:nth-child(1)").shadowRoot.querySelector("div").click();
        }
    }

    function matchMTE(match) {
        var mtes = [];
        $("#closure_eventId").children().each(function() {
            var text = $(this).text();
            var val = $(this).val();
            mtes.push({ 'name': text, 'val': val });
        });
        for (var i = 0; i < mtes.length; i++) {
            if (mtes[i].name.toLowerCase() == match.toLowerCase()) {
                console.log(mtes[i]);
                return mtes[i];
            }
        }
        return false;
    }

    function closureName(reason) {
        var finalString = reason;
        var selectedType = getSelectedType(selectedType);
        // Replace with name and type
        finalString = finalString.replace("{{type}}", selectedType);

        // Replace with segs
        var selectedSegs = W.selectionManager.getSegmentSelection().segments;
        var firstSelectedSegName = W.model.streets.getObjectById(selectedSegs[0].attributes.primaryStreetID).attributes.name;
        var lastSelectedSegName = W.model.streets.getObjectById(selectedSegs[selectedSegs.length - 1].attributes.primaryStreetID).attributes.name;
        if (firstSelectedSegName == null) {
            firstSelectedSegName = "";
        }
        if (lastSelectedSegName == null) {
            lastSelectedSegName = "";
        }
        finalString = finalString.replace("{{firstSegName}}", firstSelectedSegName).replace("{{lastSegName}}", lastSelectedSegName);

        // RegEx
        // var replaceRE = new RegExp($("input.wmech_inputregex").val(), $("input.wmech_inputregex").eq(1).val());
        // finalString = finalString.replace(replaceRE, $("input.wmech_inputregex").eq(2).val());

        // Return
        return finalString;
    }

    function getSelectedType(option) {
        var SelObj = W.selectionManager.getSelectedDataModelObjects();
        var rawType = SelObj[0].attributes.roadType;
        var newType;
        switch (rawType) {
            case 8: // Off-road / Not maintained"
                newType = "Road";
                break;
            case 1: // Local Street"
                newType = "Street";
                break;
            case 2: // Primary Street"
                newType = "Primary Street";
                break;
            case 3: // Freeway (Interstate / Other)
                newType = "Freeway";
                break;
            case 6: // Major Highway
                newType = "Highway";
                break;
            case 7: //Minor Highway
                newType = "Highway";
                break;
            case 4: // Ramp
                newType = "Ramp";
                break;
            case 20: // PLR
                newType = "Parking Lot";
                break;
            case 17: // PR
                newType = "Private";
                break;
            case 15: // Ferry
                newType = "Ferry";
                break;
            default: // Other road types
                newType = "Roads";
                break;
        }
               if (SelObj.length > 1) { // If multiple segments selected, check for different road types
                   const multipleTypesSelected = SelObj.some(seg => seg.attributes.roadType !== SelObj[0].attributes.roadType);
                   if (multipleTypesSelected == true) {newType = "Multiple Road Types"};
               }
        return newType;
    }

    function parseRule(rule) {
        //alert(rule);
        var LY = "no";
        var newMon = 0;
        var newDay = 0;
        var d = new Date();
        var yr = d.getFullYear();
        var mon = d.getMonth() + 1;
        var day = d.getDate();
        var hr = d.getHours();
        var min = d.getMinutes();
        if (rule.substring(0, 1) == "U") {
            var count = (rule.match(/,/g) || []).length;
            var timeString = rule.substring(3);
            var ruleHr = parseInt(timeString.substring(0, 2));
            var ruleMin = parseInt(timeString.substring(3, 5));
            // fix for last day of month and adding 1 month with clicksaver to ensure it is actually last day of following month.
        if (yr == "2024" || yr == "2028" || yr == "2032" || yr == "2036" || yr == "2040" || yr == "2044" || yr == "2048") LY = "yes";
            if (mon == "04" || mon == "06" || mon == "07" || mon == "09" || mon == "11" || mon == "12"){
                if (day == "30"){
                    newMon = mon + 1;
                    newDay = 1;
                }else{
                    newMon = mon;
                    newDay = day + 1;
                }
            }else{
                if (mon == "03" || mon == "05" || mon == "08" || mon == "10"){
                    if (day == "31"){
                        newMon = mon + 1;
                        newDay = 1;
                    }else{
                    newMon = mon;
                    newDay = day + 1;
                }
                }else{
                    if (mon == "02" && day > "27" && LY == "no" || mon == "02" && day > "28" && LY == "yes"){
//                        if (LY == "yes" && day > "28"){
                            newMon = mon + 1;
                            newDay = 1;
                        }else{
                            newMon = mon;
                            newDay = day;
                        }
                    }
                }
//       }
            if (count == 0) {
                // (ex. "U: 05:00", "U: 23:15")
                if (ruleHr > hr || (ruleHr == hr && ruleMin > min)) { return [assembleYear([yr, mon, day]), assembleTime([ruleHr, ruleMin])]; }
                if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { return [assembleYear([yr, newMon, newDay]), assembleTime([ruleHr, ruleMin])]; }
            } else if (count == 1) {
                timeString = rule.substring(3, 8);
                var durationString = rule.substring(rule.lastIndexOf(" ") + 1).trim();
                var newDate = new Date();
                if (durationString.match(/^[M|T|W|F|S]/) == null) {
                    var loopLength = durationString.match(/[a-z]/g).length;
                    //alert(newDate);
                    for (var i = 0; i < loopLength; i++) {
                        var nextNum = durationString.match(/[^(y|o|d|h|m)]*/)[0];
                        var nextLetter = durationString.match(/[a-z]/g)[0];
                        switch (nextLetter) {
                            case "y":
                                newDate.setFullYear(newDate.getFullYear() + parseInt(nextNum));
                                break; //newYr += parseInt(nextNum); break;
                            case "o":
                                newDate.setMonth(newDate.getMonth() + parseInt(nextNum));
                                break; //newMon += parseInt(nextNum); break;
                            case "d":
                                newDate.setDate(newDate.getDate() + parseInt(nextNum));
                                break; //newDay += parseInt(nextNum); break;
                            case "h":
                                newDate.setHours(newDate.getHours() + parseInt(nextNum));
                                break; //newHr += parseInt(nextNum); break;
                            case "m":
                                newDate.setMinutes(newDate.getMinutes() + parseInt(nextNum));
                                break; //newMin += parseInt(nextNum); break;
                        }
                        durationString = durationString.replace(durationString.substring(0, (nextNum + nextLetter).length), "");
                    }
                    if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { newDate.setDate(newDate.getDate() + 1); }
                } else {
                    var dayOfWeek = durationString;
                    var dOWNum;
                    var dOWNDate = d;
                    if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { dOWNDate.setDate(dOWNDate.getDate() + 1); }
                    switch (dayOfWeek) {
                        case "Sun":
                            dOWNum = 0;
                            break;
                        case "Mon":
                            dOWNum = 1;
                            break;
                        case "Tue":
                            dOWNum = 2;
                            break;
                        case "Wed":
                            dOWNum = 3;
                            break;
                        case "Thu":
                            dOWNum = 4;
                            break;
                        case "Fri":
                            dOWNum = 5;
                            break;
                        case "Sat":
                            dOWNum = 6;
                            break;
                    }
                    newDate.setDate(dOWNDate.getDate() + (dOWNum + 7 - dOWNDate.getDay()) % 7);
                    //alert(newDate);
                }
                //alert(ruleHr + ruleMin);
                return [assembleYear([newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()]), assembleTime([ruleHr, ruleMin])];
            } else if (count == 2) {
                timeString = rule.substring(3, 8);
                var dayOfWeek = rule.substring(10, 13);
                var durationString = rule.substring(rule.lastIndexOf(" ") + 1).trim();
                var newDate = d;
                var dOWNum;
                //alert(timeString + dayOfWeek + durationString);
                if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { newDate.setDate(newDate.getDate() + 1); }
                switch (dayOfWeek) {
                    case "Sun":
                        dOWNum = 0;
                        break;
                    case "Mon":
                        dOWNum = 1;
                        break;
                    case "Tue":
                        dOWNum = 2;
                        break;
                    case "Wed":
                        dOWNum = 3;
                        break;
                    case "Thu":
                        dOWNum = 4;
                        break;
                    case "Fri":
                        dOWNum = 5;
                        break;
                    case "Sat":
                        dOWNum = 6;
                        break;
                }
                newDate.setDate(newDate.getDate() + (dOWNum + 7 - newDate.getDay()) % 7);
                var loopLength = durationString.match(/[a-z]/g).length;
                //alert(newDate);
                //alert(loopLength);
                for (var i = 0; i < loopLength; i++) {
                    var nextNum = durationString.match(/[^(y|o|d|h|m)]*/)[0];
                    var nextLetter = durationString.match(/[a-z]/g)[0];
                    switch (nextLetter) {
                        case "y":
                            newDate.setFullYear(newDate.getFullYear() + parseInt(nextNum));
                            break; //newYr += parseInt(nextNum); break;
                        case "o":
                            newDate.setMonth(newDate.getMonth() + parseInt(nextNum));
                            break; //newMon += parseInt(nextNum); break;
                        case "d":
                            newDate.setDate(newDate.getDate() + parseInt(nextNum));
                            break; //newDay += parseInt(nextNum); break;
                        case "h":
                            newDate.setHours(newDate.getHours() + parseInt(nextNum));
                            break; //newHr += parseInt(nextNum); break;
                        case "m":
                            newDate.setMinutes(newDate.getMinutes() + parseInt(nextNum));
                            break; //newMin += parseInt(nextNum); break;
                    }
                    durationString = durationString.replace(durationString.substring(0, (nextNum + nextLetter).length), "");
                }
                //alert(newDate);
                return [assembleYear([newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()]), assembleTime([ruleHr, ruleMin])];
            }
        } else if (rule.substring(0, 1) == "D") {
            // Date closure (ex. "D: 2020-03-14 03:14")
            var date = rule.substring(3, 13);
            var time = rule.substring(14, 19);
            return [assembleYear([date.substring(0, 4), date.substring(5, 7), date.substring(8, 10)]), assembleTime([time.substring(0, 2), time.substring(3, 5)])];
        } else {
            var newDate = new Date();
            var loopLength = rule.match(/[a-z]/g).length;
            for (var i = 0; i < loopLength; i++) {
                var nextNum = rule.match(/[^(y|o|d|h|m)]*/)[0];
                var nextLetter = rule.match(/[a-z]/g)[0];
                switch (nextLetter) {
                    case "y":
                        newDate.setFullYear(newDate.getFullYear() + parseInt(nextNum));
                        break; //newYr += parseInt(nextNum); break;
                    case "o":
                        newDate.setMonth(newDate.getMonth() + parseInt(nextNum));
                        break; //newMon += parseInt(nextNum); break;
                    case "d":
                        newDate.setDate(newDate.getDate() + parseInt(nextNum));
                        break; //newDay += parseInt(nextNum); break;
                    case "h":
                        newDate.setHours(newDate.getHours() + parseInt(nextNum));
                        break; //newHr += parseInt(nextNum); break;
                    case "m":
                        newDate.setMinutes(newDate.getMinutes() + parseInt(nextNum));
                        break; //newMin += parseInt(nextNum); break;
                }
                rule = rule.replace(rule.substring(0, (nextNum + nextLetter).length), "");
            }
            return [assembleYear([newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()]), assembleTime([newDate.getHours(), newDate.getMinutes()])];
        }
    }

    function assembleYear(parts) {
        // parts[0] is yr, parts[1] is mon, parts[2] is day
        //        Lang = I18n.currentLocale()
        if (DateFormat == "mmddyyyy"){
            return addZero(parts[1]) + dateSeparator + addZero(parts[2]) + dateSeparator + parts[0];
        }else{
            if (DateFormat == "yyyymmdd"){
                return parts[0] + dateSeparator + addZero(parts[1]) + dateSeparator + addZero(parts[2]);
            }else{
                if (DateFormat == "ddmmyyyy"){
                    return addZero(parts[2]) + dateSeparator + addZero(parts[1]) + dateSeparator + parts[0];
                }}}}

    function assembleTime(parts) {
        // parts[0] is hr, parts[1] is min
        return addZero(parts[0]) + ":" + addZero(parts[1]);
    }

    function getDate(prop) {
        var d = new Date();
        switch (prop) {
            case "yr":
                return d.getFullYear();
            case "mm":
                return addZero(d.getMonth());
            case "dd":
                return addZero(d.getDate());
            case "hr":
                return addZero(d.getHours());
            case "min":
                return addZero(d.getMinutes());
            case "sec":
                return d.getSeconds();
        }
    }

    function addZero(string, length = 2) {
        return ("0" + string).substr(-length);
    }

    function closureDateToString() {
        return getDate("yr") + "/" + getDate("mm") + "/" + getDate("dd") + " " + getDate("hr") + ":" + getDate("min");
    }

    function log(message) {
        console.log("WMECH: " + message);
    }

    function error(message) {
        console.log("WMECH ERROR: " + message);
    }

    bootstrap();
})();