Greasy Fork is available in English.

WME Find Deleted Objects

Attempts to show the history of a venue that has been deleted in an area.

Από την 18/01/2019. Δείτε την τελευταία έκδοση.

// ==UserScript==
// @name         WME Find Deleted Objects
// @namespace    https://greasyfork.org/users/32336-joyriding
// @version      2019.01.18.02
// @description  Attempts to show the history of a venue that has been deleted in an area.
// @author       Joyriding
// @include      https://beta.waze.com/*
// @include      https://www.waze.com/forum/*
// @include      https://www.waze.com/editor*
// @include      https://www.waze.com/*/editor*
// @exclude      https://www.waze.com/user/editor*
// @icon         data:image/png;base64,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
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @require      https://greasyfork.org/scripts/38421-wme-utils-navigationpoint/code/WME%20Utils%20-%20NavigationPoint.js
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// @license      GPLv3
// @grant        none
// ==/UserScript==

/* global W */
/* global OL */
/* global I18n */
/* global $ */
/* global WazeWrap */
/* global NavigationPoint */
/* global require */
/* global uuidGenerator */
/* global Backbone */

(function() {
    'use strict';

    var settings = {};
    var _wmeFdoFeatureLayer;
    var _wmeFdoMarkerLayer;

    var requestStatus = [];
    var seenVenueIds = [];
    var subcategoryToParentCategories = [];

    var rateLimitLastCall = Date.now();
    var rateLimitLastModified = Date.now();
    var rateLimitBaseDelay = 50;
    var rateLimitAddedDelay = 0;
    var rateLimitDelayStepUp = 25;
    var rateLimitDelayStepDown = 5;

    var flags = {
        venue: { }
    };

    var externalProviderDetails = { };

    function bootstrap(tries) {
        tries = tries || 1;

        if (W && W.map &&
            W.model && W.loginManager.user &&
            $ ) {
            init();
        } else if (tries < 1000)
            setTimeout(function () {bootstrap(tries++);}, 200);
    }

    function init()
    {
        _wmeFdoFeatureLayer = new OL.Layer.Vector("_wmeFdoFeatureLayer",{uniqueName: "__wmeFdoFeatureLayer"});
        W.map.addLayer(_wmeFdoFeatureLayer);
        _wmeFdoFeatureLayer.setVisibility(true);

        _wmeFdoMarkerLayer = new OL.Layer.Markers("_wmeFdoMarkerLayer",{uniqueName: "__wmeFdoMarkerLayer"});
        W.map.addLayer(_wmeFdoMarkerLayer);
        _wmeFdoMarkerLayer.setVisibility(true);

        var curr_ver = GM_info.script.version;

        let styleElements = getWmeStyles();

        var $style = $("<style>");
        $style.html([
            '<style>',
            '#wmeFdoStatusDate { margin-top:10px;margin-bottom:10px; }',
            '.spinner-disable { opacity: 0; }',
            '.wmeFdoFormInputLabel { display:inline-block;margin-right:10px;}',
            '.wmeFdoFormInput { display:inline-block; width:150px;}',
            '.wmeFdoButtonArea {margin-top:10px;margin-bottom:10px; }',
            '.wmeFdoGoogleBtn { margin-top:10px;margin-bottom:10px; }',
            '.wmeFdoRecreate a { color: #b5b5b5; cursor: pointer; text-decoration:none;}',
            '.wmeFdoRecreate a:hover { text-decoration:underline; }',
            '.wmeFdoResultDetail { position: relative; }',
            '.wmeFdoDetailControls {position: absolute;right: 17px;top: 10px;}',
            '.wmeFdoFlag a { text-decoration: none; }',
            '.wmeFdoFlag { cursor:pointer; opacity:0.2; margin-right:5px;}',
            '.wmeFdoFlag:hover { color:red; opacity:.75}',
            '.wmeFdoFlag.flagged { color: red; opacity:1}',
            '.historySummaryItem { margin-left:10px; color:#a7a7a7; }',
            '.historySummaryItem a, .historySummaryItem a:visited { color:#a7a7a7; }',
            '.wmeFdo-place-delete { filter: saturate(17) hue-rotate(65deg); }',
            '.wmeFdo-place-delete:hover { filter: brightness(110%) saturate(17) hue-rotate(65deg) !important; }',
            '.wmeFdo-place-delete:after {',
            'border-top: 3px solid #ffffff;',
            'width: 69%;',
            'height: 49%;',
            'position: absolute;',
            'bottom: 6px;',
            'left: 8px;',
            'content: "";',
            'transform: rotate(-45deg);',
            '}',
            '#wmeFdoSubTabs, .wmeFdoFlag { display: none!important; }',
            '.wmeFdoResultTypeVenue { opacity:0.5; margin-left:-3px;margin-right:5px;position:relative;top:3px;' + styleElements.resultTypeVenueStyle.css + '}',
            '.wmeFdoResultTypeCamera { opacity:0.5; margin-left:-3px;margin-right:5px;position:relative;top:3px;' + styleElements.resultTypeCameraStyle.css + '}',
            '.wmeFdoResultTypeArea { opacity:0.5; margin-left:-3px;margin-right:5px;position:relative;top:10px;' + styleElements.resultTypeAreaStyle.css + '}',
            '</style>'
        ].join(' '));


        var $section = $('<div id="wmeFdoPanel">');
        $section.html(
            '<div id="wmeFdoHead">',

            '</div>'
        );

        var $navTabs = $('<ul id="wmeFdoSubTabs" class="nav nav-tabs"><li class="active"><a data-toggle="tab" href="#wmeFdoTabFind">Find</a></li>' +
                         '<li><a data-toggle="tab" href="#wmeFdoTabFlagged" id="wmeFdoTabFlagEvent">Flagged</a></li>' +
                         '<li><a data-toggle="tab" href="#wmeFdoTabSettings"><span class="fa fa-gear"></span></a></li></ul>');
        var $tabContent = $('<div class="tab-content" style="padding:5px;">');
        var $fdoTabFind = $('<div id="wmeFdoTabFind" class="tab-pane active">');
        $fdoTabFind.append([
            '<h4 style="margin-top:0px;">Find Deleted Objects</h4>',
            '<h6 style="margin-top:0px;">' + curr_ver + '</h6>'
        ].join(' '));
        createSettingsCheckbox($fdoTabFind, 'wmeFdoNearbyEditors','Nearby Editors');
        $fdoTabFind.append([
            '<div class="controls-container" style="padding-top: 2px;"><div class="wmeFdoFormInputLabel">Editor Name</div><input type="text" id="wmeFdoEditorName" class="form-control wmeFdoFormInput"><label for="wmeFdoEditorName"></label></div><br>',
        ].join(' '));
        createSettingsCheckbox($fdoTabFind, 'wmeFdoIncludeSelf','Include Deletes By This Editor');
        createSettingsCheckbox($fdoTabFind, 'wmeFdoLimitToScreen','Limit to Screen');
        $fdoTabFind.append([
            '<div class="controls-container" style="padding-top: 2px;"><div class="wmeFdoFormInputLabel">Find Prior To</div><input type="text" id="wmeFdoFindDate" class="form-control wmeFdoFormInput"><label for="wmeFdoFindDate"></label></div><br>',
        ].join(' '));


        var $fdoTabFlagged = $('<div id="wmeFdoTabFlagged" class="tab-pane">');
        $fdoTabFlagged.html('<h4>Flagged Objects<h4>');
        let $fdoTabFlaggedResults = $('<div id="wmeFdoFlaggedResults>');
        $fdoTabFlagged.append($fdoTabFlaggedResults);

        var $fdoTabSettings = $('<div id="wmeFdoTabSettings" class="tab-pane">');
        $fdoTabSettings.append([
            '<h4>Settings</h4>',
            '<div id="wmeFdoSettings">',
            '<div class="controls-container" style="padding-top: 2px;">Rate Limit Delay: <input type="text" id="wmeFdoRateLimitDelay" ><label for="wmeFdoRateLimitDelay"></label></div>',
            '</div>'
        ].join(' '));

        $('#wmeFdoTabSettings').append('div').append('Reset Settings');

        $tabContent.append($fdoTabFind,$fdoTabFlagged,$fdoTabSettings);
        $section.append($navTabs, $tabContent);
        $section.append($style);





        new WazeWrap.Interface.Tab('FiDO', $section.html(), initializeSettings);
        prepareTab();

        WazeWrap.Interface.AddLayerCheckbox("display", "Deleted Objects", settings.Enabled, onLayerCheckboxChanged);
        onLayerCheckboxChanged(settings.Enabled);

        let buttons = document.createElement('div');
        buttons.className = 'wmeFdoButtonArea';
        $('#wmeFdoTabFind').append(buttons);

        var findButton = document.createElement('button');
        findButton.id = 'wmeFdoFindButton';
        findButton.textContent = 'Start Scan';
        findButton.className = 'btn btn-success center-block';
        buttons.append(findButton);

        var cancelButton = document.createElement('button');
        cancelButton.id = 'wmeFdoCancelButton';
        cancelButton.textContent = 'Cancel Scan';
        cancelButton.className = 'btn btn-warning center-block hidden';
        buttons.append(cancelButton);

        findButton.addEventListener('click', function() {
            findButton.className = 'btn btn-success center-block hidden';
            cancelButton.className = 'btn btn-danger center-block ';
            _wmeFdoFeatureLayer.removeAllFeatures();
            _wmeFdoMarkerLayer.clearMarkers();
            seenVenueIds = [];
            requestStatus = {};
            resetRateLimit();
            document.getElementById('wmeFdoTransactionList').innerHTML = '';
            let tab = document.getElementById('wmeFdoTab');
            tab.innerHTML = 'FiDO <span class="spinner-enable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            $('#wmeFdoTabFind').data("status","running");

            settings.Enabled = true;
            onLayerCheckboxChanged(settings.Enabled); // TODO: Fix this so the Layer menu checkbox gets updated
            findObjects();
        });

        cancelButton.addEventListener('click', function() {
            cancelButton.textContent = 'Cancelling...';
            cancelButton.className = 'btn btn-warning center-block disabled';
            $('#wmeFdoTabFind').data("status","cancelled");
            let tab = document.getElementById('wmeFdoTab');
            tab.innerHTML = 'FiDO <span class="spinner-disable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            cancelScan(0);
        });

        $("#wmeFdoEditorName").keyup(function(event) {
            if (event.keyCode === 13) {
                $("#wmeFdoFindButton").click();
            }
        });

        $("#wmeFdoFindDate").keyup(function(event) {
            if (event.keyCode === 13) {
                $("#wmeFdoFindButton").click();
            }
        });

        $('#wmeFdoNearbyEditors').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            if(this.checked)
            {
                $("#wmeFdoEditorName").prop('disabled', true);
                $("#wmeFdoIncludeSelf").prop('disabled', true);
                $("#wmeFdoLimitToScreen").prop('disabled', true);

            }
            else
            {
                $("#wmeFdoEditorName").prop('disabled', false);
                $("#wmeFdoIncludeSelf").prop('disabled', false);
                $("#wmeFdoLimitToScreen").prop('disabled', false);
            }
        });

        let flaggedTab = document.getElementById('wmeFdoTabFlagEvent');
        flaggedTab.addEventListener('click', function() {
            let flagList = '';
            for (var id in flags.venue) {
                flagList = flagList + id + '<br>';
                console.log(id);
            }
            let results = document.getElementById('wmeFdoTabFlagged');
            results.append(flagList);
        });

        var $usersDiv = $( '<div id="fdoUsersPanel"></div>');

        var dateSpan = document.createElement('div');
        dateSpan.id = 'wmeFdoStatusDate';
        $('#wmeFdoTabFind').append(dateSpan);

        let inputDate = document.getElementById('wmeFdoFindDate');
        inputDate.value = formatCurrentDate();

        $('#wmeFdoTabFind').append(buildResultsPanel());
        console.log('FiDO: done');
    }



    function initializeSettings()
    {
        loadSettings();
        setChecked('wmeFdoIncludeSelf', settings.IncludeSelf);
        setChecked('wmeFdoLimitToScreen', settings.LimitToScreen);
        setChecked('wmeFdoNearbyEditors', settings.NearbyEditors);


        //   if(settings.Enabled)
        //      W.selectionManager.events.register("selectionchanged", null, drawSelection);

        $('.wmeFdoSettingsCheckbox').change(function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = this.checked;
            saveSettings();
            if(this.checked)
            {
                //   W.selectionManager.events.register("selectionchanged", null, drawSelection);
                //   processVenues(W.model.venues.getObjectArray());
            }
            else
            {
                //  W.selectionManager.events.unregister("selectionchanged", null, drawSelection);
                //   wmeFdoSelectedLayer.removeAllFeatures();
                // processVenues(W.model.venues.getObjectArray());
            }
        });


        if(settings.NearbyEditors)
        {
            $("#wmeFdoEditorName").prop('disabled', true);
            $("#wmeFdoIncludeSelf").prop('disabled', true);
            $("#wmeFdoLimitToScreen").prop('disabled', true);

        }
        else
        {
            $("#wmeFdoEditorName").prop('disabled', false);
            $("#wmeFdoIncludeSelf").prop('disabled', false);
            $("#wmeFdoLimitToScreen").prop('disabled', false);
        }

        $('.wmeFdoSettingsText').on('input propertychange paste', function() {
            var settingName = $(this)[0].id.substr(6);
            settings[settingName] = parseInt($(this).val());
            saveSettings();

            //W.selectionManager.events.unregister("selectionchanged", null, drawSelection);
            //processVenues(W.model.venues.getObjectArray());
        });


    }

    function getWmeStyles() {
        // Get the sprite icons from the native WME CSS so that we can use our own document structure

        let styleElements = {
            resultTypeVenueStyle: { cssParts: {}, css: "" },
            resultTypeCameraStyle: { cssParts: {}, css: "" },
            resultTypeAreaStyle: { cssParts: {}, css: "" }
        };

        let $tempDiv = null;
        let tempQuerySelector = null;
        let tempComputedStyle = null;
        let tempCurrentStyle = null;

        //.form-search .search-result-region .search-result .icon {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        $tempDiv = $('<div class="form-search">').append($('<div class="search-result-region">').append($('<div class="search-result">').append($('<div class="icon">'))));
        $('body').append($tempDiv);

        tempQuerySelector = document.querySelector('.form-search .search-result-region .search-result .icon');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector);
        tempCurrentStyle = styleElements.resultTypeVenueStyle.cssParts;

        tempCurrentStyle.backgroundImage = tempComputedStyle.getPropertyValue('background-image');
        tempCurrentStyle.backgroundPosition = tempComputedStyle.getPropertyValue('background-position');
        tempCurrentStyle.backgroundSize = tempComputedStyle.getPropertyValue('background-size');
        tempCurrentStyle.width = tempComputedStyle.getPropertyValue('width');
        tempCurrentStyle.height = tempComputedStyle.getPropertyValue('height');
        styleElements.resultTypeVenueStyle.css = `background-image:${tempCurrentStyle.backgroundImage};background-size:${tempCurrentStyle.backgroundSize};background-position:${tempCurrentStyle.backgroundPosition};width:${tempCurrentStyle.width};height:${tempCurrentStyle.height};`;
        $tempDiv.remove();

        //#edit-buttons .camera .item-icon::after {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        tempQuerySelector = document.querySelector('#edit-buttons .camera .item-icon');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector,'::after');
        tempCurrentStyle = styleElements.resultTypeCameraStyle.cssParts;

        tempCurrentStyle.backgroundImage = tempComputedStyle.getPropertyValue('background-image');
        tempCurrentStyle.backgroundPosition = tempComputedStyle.getPropertyValue('background-position');
        tempCurrentStyle.backgroundSize = tempComputedStyle.getPropertyValue('background-size');
        tempCurrentStyle.width = tempComputedStyle.getPropertyValue('width');
        tempCurrentStyle.height = tempComputedStyle.getPropertyValue('height');
        styleElements.resultTypeCameraStyle.css = `background-image:${tempCurrentStyle.backgroundImage};background-size:${tempCurrentStyle.backgroundSize};background-position:${tempCurrentStyle.backgroundPosition};width:${tempCurrentStyle.width};height:${tempCurrentStyle.height};`;
        //$tempDiv.remove();

        //#edit-buttons .toolbar-group .toolbar-group-item .drawing-controls .drawing-control.polygon:after {
        //background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        tempQuerySelector = document.querySelector('#edit-buttons .toolbar-group .toolbar-group-item .drawing-controls .drawing-control.polygon');
        tempComputedStyle = window.getComputedStyle(tempQuerySelector,'::after');
        tempCurrentStyle = styleElements.resultTypeAreaStyle.cssParts;

        tempCurrentStyle.backgroundImage = tempComputedStyle.getPropertyValue('background-image');
        tempCurrentStyle.backgroundPosition = tempComputedStyle.getPropertyValue('background-position');
        tempCurrentStyle.backgroundSize = tempComputedStyle.getPropertyValue('background-size');
        tempCurrentStyle.width = tempComputedStyle.getPropertyValue('width');
        tempCurrentStyle.height = tempComputedStyle.getPropertyValue('height');
        styleElements.resultTypeAreaStyle.css = `background-image:${tempCurrentStyle.backgroundImage};background-size:${tempCurrentStyle.backgroundSize};background-position:${tempCurrentStyle.backgroundPosition};width:${tempCurrentStyle.width};height:${tempCurrentStyle.height};`;

        return styleElements;
    }



    function buildResultsPanel() {
        return $('<div id="fdoResultsPanel">')
            .append($('<div class="elementHistoryContainer">')
                    .append($('<div class="historyContent">')
                            .append($('<div class="transactions">')
                                    .append($('<ul id="wmeFdoTransactionList">'))
                                   )
                           )
                   );
    }

    function onLayerCheckboxChanged(checked) {
        settings.Enabled = checked;
        if (settings.Enabled) {
            _wmeFdoFeatureLayer.setVisibility(true);
            _wmeFdoMarkerLayer.setVisibility(true);
        } else {
            _wmeFdoFeatureLayer.setVisibility(false);
            _wmeFdoMarkerLayer.setVisibility(false);
        }
        saveSettings();
    }

    function cancelScan(tries) {
        //TODO: Move rest of cancel code into here


        tries = tries || 1;
        //console.log('incomplete: ' + incompleteRequestCount());
        if (incompleteRequestCount() > 0 && tries < 10) {
            console.log('Cancel requested, waiting for ' + incompleteRequestCount() + ' requests to complete');
            setTimeout(function () {cancelScan(tries++);}, 500);
        } else {
            // TODO: Change button to Start Scan
            let findButton = document.getElementById('wmeFdoFindButton');
            let cancelButton = document.getElementById('wmeFdoCancelButton');
            findButton.className = 'btn btn-success center-block';
            cancelButton.className = 'btn btn-warning center-block hidden';
            cancelButton.textContent = 'Cancel Scan';
            $('#wmeFdoStatusDate')[0].innerText = 'Scan Cancelled';
            console.log('Cancelled');
        }

    }

    function incompleteRequestCount() {
        let incompleteCount = 0;
        let failedCount = 0;
        Object.keys(requestStatus).forEach(function (url) {
            if (requestStatus[url].status != 'successful') {
                if (Date.now() - requestStatus[url].lastAttempt < 10000) {
                    incompleteCount++;
                } else {
                    failedCount++;
                }
            }
        });
        return incompleteCount;
    }

    function defaultRequestStatus() {
        return {
            status: 'initial',
            attempts: 0,
            lastAttempt: null
        };
    }

    function requestParams(url) {
        return {
            url: url,
            iteration: 0,
            nextTransactionDate: null,
            delay: calculateDelay(url)
        }
    }

    function calculateDelay(url) {
        let hash = Math.abs(hashCode(url)).toString();
        let delay = parseInt(hash.substring(hash.length - 2));
        if (delay == 0) {
            delay = 30;
        }
        return delay;
    }

    function hashCode(s) {
        let h;
        for(let i = 0; i < s.length; i++) {
            h = Math.imul(31, h) + s.charCodeAt(i) | 0;
        }
        return h;
    }

    async function beginFind(lookFor, transactionLookup) {
        // var findCenterPoint = new OL.Geometry.Point(lookFor.locationCenter.coordinates[0], lookFor.locationCenter.coordinates[1]).transform(W.map.displayProjection, W.map.projection);

        if ( $('#wmeFdoTabFind').data("status") == "running"
            && transactionLookup.iteration < transactionLookup.maxIterations
           )
        {
            transactionLookup.iteration++;

            let requestParamsTransactions = requestParams(getApiUrlTransactions(transactionLookup.userId, transactionLookup.nextTransactionDate));
            requestStatus[requestParamsTransactions.url] = defaultRequestStatus();
            var data = await getApiRequest(requestParamsTransactions);
            while (typeof(data.error) != 'undefined' && data.error && requestParamsTransactions.attempts < 100)
            {
                data = await getApiRequest(requestParamsTransactions);
            }
            if (data.error) {
                let delay = 100 * requestParamsTransactions.delay;
                console.log(`Last chance attempt, delay: ${delay}`);
                await wait(delay);
                data = await getApiRequest(requestParamsTransactions);
            }
            if (data.error) {
                console.log('Request failed.');
            }

            transactionLookup.nextTransactionDate = data.nextTransactionDate
            let nextDate = 'Complete';
            if (data.nextTransactionDate != null)
            {
                nextDate = uuidToDate(data.nextTransactionDate);
                //$('#wmeFdoStatusDate')[0].innerText = 'Scanning thru ' + nextDate + '...';
                $('#wmeFdoStatusDate')[0].innerText = 'Scanning...';
            }


            var transactionCount = 0;
            data.transactions.forEach(function(transaction) {
                transaction.operations.forEach(async function(operation) {
                    if (operation.objectType == lookFor.objectType || operation.objectType == 'Camera')
                    {
                        transactionCount++;
                        var operationCenterPoint = new OL.Geometry.Point(operation.objectCentroid.coordinates[0], operation.objectCentroid.coordinates[1]).transform(W.map.displayProjection, W.map.projection);
                        //var distance = operationCenterPoint.distanceTo(findCenterPoint);
                        //if (distance < 1000)
                        if (!lookFor.limitToScreen || lookFor.searchArea.containsPoint(operationCenterPoint))
                        {
                            if (operation.objectType == 'Camera') {
                                console.log('camera: ' + operation.objectID);
                            } else {

                                getDeletedItemsVenue(operation, transactionLookup, lookFor, operationCenterPoint);
                            }
                        }
                    }
                });
            });

            if (transactionLookup.nextTransactionDate != null) {
                beginFind(lookFor, transactionLookup);
            } else {
                endScan("End of Editor History", false);
            }

        } else {
            if (transactionLookup.iteration >= transactionLookup.maxIterations)
            {
                endScan("Transaction Limit Exceeded", false);
            }
        }

    }

    async function getDeletedItemsVenue(operation, transactionLookup, lookFor, operationCenterPoint) {
        //console.log('element history for ' + data.nextTransactionDate);
        let requestParamsElementHistory = requestParams(getApiUrlElementHistory('venue', operation.objectID, null));
        requestStatus[requestParamsElementHistory.url] = defaultRequestStatus();
        var venueHistory = await getApiRequest(requestParamsElementHistory);
        while (typeof(venueHistory.error) != 'undefined' && venueHistory.error && requestParamsElementHistory.attempts < 100)
        {
            venueHistory = await getApiRequest(requestParamsElementHistory);
        }

        venueHistory.transactions.objects.forEach(function(venueTransaction) {
            venueTransaction.objects.forEach(function(venueAction) {
                if (venueAction.actionType == 'DELETE') {
                    //             console.log(venueAction);
                    if (venueAction.objectType == 'venue' && venueAction.oldValue != null)
                    {
                        let selfDelete = false;
                        if (transactionLookup.userId == venueTransaction.userID) {
                            selfDelete = true;
                        }

                        if (lookFor.includeSelf || !selfDelete) {
                            // Skip if searching for editor who deleted it
                            if (!seenVenueIds.includes(venueAction.objectID))
                            {
                                seenVenueIds.push(venueAction.objectID);
                                // console.log('Deleted Venue Name: ' + venueAction.oldValue.name);
                                // console.log('Deleted Venue ID: ' + venueAction.objectID);
                                // console.log('User ID: ' + venueTransaction.userID);
                                // console.log(venueHistory);
                                var record = document.createElement('div');
                                record.id=venueTransaction.transactionID;
                                let username = null;
                                venueHistory.users.objects.forEach( function(user) {
                                    if (user.id == venueTransaction.userID) {
                                        let userRank = user.rank + 1;
                                        username = user.userName + '(' + userRank + ')';
                                    }
                                });


                                record.innerText = username + ": " + venueAction.oldValue.name + " - " + venueAction.objectID;
                                // $('#wmeFdoTransactionList').append(record);

                                var details = document.createElement('li');
                                details.id = 'entry-' + venueAction.objectID;
                                details.className = 'element-history-item tx-has-content tx-has-related closed';
                                //details.innerText= username + ": " + venueAction.oldValue.name + " - " + venueAction.objectID;

                                var txHeader = document.createElement('div');
                                txHeader.className = 'tx-header';
                                let detailsDisplayedOnce = false;
                                txHeader.onclick = function () {

                                    if (!detailsDisplayedOnce && details.classList.contains('closed')) {
                                        detailsDisplayedOnce = true;
                                        displayExternalProviders(venueAction.objectID, venueItem.externalProviderIDs);
                                    }
                                    toggleDetailsDropDown(details.id, operationCenterPoint);
                                };

                                var txTypeDiv = document.createElement('div');
                                txTypeDiv.className = 'flex-noshrink';
                                txHeader.append(txTypeDiv);

                                var txTypeIcon = document.createElement('div');
                                txTypeIcon.className = 'flex-noshrink wmeFdoResultTypeVenue';
                                txTypeDiv.append(txTypeIcon);

                                var txSummary = document.createElement('div');
                                txSummary.className = 'tx-summary';
                                txHeader.append(txSummary);

                                var txCollapse = document.createElement('div');
                                txCollapse.className = 'flex-noshrink tx-toggle-closed';
                                txHeader.append(txCollapse);

                                var txAuthorDate = document.createElement('div');
                                txAuthorDate.className = 'tx-author-date';



                                var txPreview = document.createElement('div');
                                txPreview.className = 'tx-preview';


                                var txName = document.createElement('h4');
                                //txName.className = 'type';
                                txAuthorDate.append(txName);

                                txSummary.append(txAuthorDate);
                                txSummary.append(txPreview);


                                var txContent = document.createElement('div');
                                txContent.className = 'tx-content wmeFdoResultDetail';

                                var txContentClass = document.createElement('div');
                                txContentClass.className = 'main-object-region tx-changes';
                                txContent.append(txContentClass);

                                var txContentInfo = document.createElement('div');
                                txContentInfo.className = 'related-objects-region tx-changes';

                                var txContentData = document.createElement('div');

                                var controls = document.createElement('div');
                                controls.className = 'wmeFdoDetailControls';
                                txContent.append(controls);


                                txContentInfo.append(txContentData);
                                txContent.append(txContentInfo);

                                details.append(txHeader);
                                details.append(txContent);

                                var venueItem = venueAction.oldValue;

                                if (typeof(venueItem.geometry) == 'undefined') {
                                    // History does not contain any details for this place, other than it was deleted.
                                    // TODO: create empty record to at least show that there was something here at one point.
                                    console.log(`No history found for venue id ${venueAction.objectID}`);
                                } else {
                                    if (venueItem.geometry.type == 'Polygon') {
                                        var txTypeArea = document.createElement('div');
                                        txTypeArea.className = 'flex-noshrink wmeFdoResultTypeArea';
                                        txTypeDiv.append(txTypeArea);
                                    }


                                    let streetName = formatFullStreetName(venueHistory, venueItem.streetID);
                                    let address = streetName;
                                    if (typeof(venueItem.houseNumber) != 'undefined') {
                                        address = venueItem.houseNumber + ' ' + address;
                                    }

                                    var venueName = venueItem.name;
                                    if (typeof venueName == 'undefined' || venueName == " " || venueName == "")
                                    {
                                        if (typeof(venueItem.categories) != 'undefined' && venueItem.categories[0] == 'RESIDENCE_HOME') {
                                            let resiStreet = formatStreetName(venueHistory, venueItem.streetID);
                                            if (typeof(venueItem.houseNumber) != 'undefined') {
                                                resiStreet = venueItem.houseNumber + ' ' + resiStreet;
                                            }
                                            venueName = resiStreet;
                                        } else if (typeof(venueItem.categories) != 'undefined') {
                                            venueName = 'unnamed ' + I18n.t("venues.categories." + venueItem.categories[0]);
                                        } else {
                                            venueName = 'unnamed';
                                            //console.log('No category: ' + venueAction.objectID);
                                        }
                                    }

                                    txName.innerText = venueName;

                                    txPreview.innerHTML = streetName + '<br>Deleted by ' + username + " " + timeConverter(venueTransaction.date);

                                    var info = document.createElement('div');
                                    info.id = 'details-' + venueAction.objectID;

                                    let flagControl = document.createElement('div');
                                    flagControl.className = 'focus-buttons';
                                    let flag = document.createElement('a');
                                    flag.className = 'wmeFdoFlag';
                                    if (flags['venue'][venueAction.objectID]) { flag.className = 'wmeFdoFlag flagged'; }
                                    flag.innerHTML = '<span class="fa fa-flag"></span>';
                                    flag.onclick = function () {
                                        toggleFlag(flag, 'venue', venueAction.objectID);
                                    };
                                    flagControl.append(flag);
                                    controls.append(flagControl);

                                    var focusButtons = document.createElement('div');
                                    focusButtons.className = 'focus-buttons';

                                    var focus = document.createElement('a');
                                    focus.className = 'focus';
                                    focus.onclick = function () {
                                        var lonLat = new OL.LonLat(operationCenterPoint.x, operationCenterPoint.y);
                                        W.map.moveTo(lonLat, 6);
                                        highlightPin(pin);
                                    };
                                    focusButtons.append(focus);
                                    controls.append(focusButtons);



                                    let lockRank = venueItem.lockRank +1;
                                    txContentData.append(info);
                                    let searchText = venueItem.name + " " + streetName;

                                    var searchButton = document.createElement('button');
                                    searchButton.textContent = 'Google';
                                    searchButton.className = 'wmeFdoGoogleBtn btn btn-default center-block';
                                    searchButton.addEventListener('click', function() {
                                        window.open('https://www.google.com/search?q=' + encodeURIComponent(searchText),'_blank');
                                    });

                                    let recreateDiv = document.createElement('div');
                                    recreateDiv.className = 'wmeFdoRecreate';

                                    let recreateButton = document.createElement('a');
                                    recreateButton.textContent = 'Recreate Place';
                                    recreateButton.className = 'focus';
                                    recreateButton.addEventListener('click', function() {
                                        recreatePlace(venueItem, venueHistory, operationCenterPoint);
                                    });
                                    recreateDiv.append(recreateButton);

                                    txContentData.append(searchButton);
                                    txContentData.append(recreateDiv);

                                    let panelHTML = '<ul>';
                                    //panelHTML += '<ul>';

                                    let venueIdReference = 'details-venue-id-' + venueAction.objectID;
                                    panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Venue ID</div>';
                                    panelHTML += '<div class="ca-description ca-preview" id="' + venueIdReference + '">';
                                    panelHTML += '</div></li>';

                                    if (address != null && address != "" && address != " ") {
                                        panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Address</div>';
                                        panelHTML += '<div class="ca-description ca-preview">';
                                        panelHTML += address;
                                        panelHTML += '</div></li>';
                                    }

                                    panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Categories</div>';
                                    panelHTML += '<div class="ca-description ca-preview">';
                                    panelHTML += formatCategories(venueItem.categories);
                                    panelHTML += '</div></li>';

                                    if (typeof(venueItem.description) != 'undefined') {
                                        panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Description</div>';
                                        panelHTML += '<div class="ca-description ca-preview">';
                                        panelHTML += venueItem.description;
                                        panelHTML += '</div></li>';
                                    }

                                    if (typeof(venueItem.url) != 'undefined') {
                                        panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Website</div>';
                                        panelHTML += '<div class="ca-description ca-preview">';
                                        panelHTML += venueItem.url;
                                        panelHTML += '</div></li>';
                                    }

                                    if (typeof(venueItem.phone) != 'undefined') {
                                        panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Phone</div>';
                                        panelHTML += '<div class="ca-description ca-preview">';
                                        panelHTML += venueItem.phone;
                                        panelHTML += '</div></li>';
                                    }

                                    if (typeof(venueItem.lockRank) != 'undefined') {
                                        panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">Lock Rank</div>';
                                        panelHTML += '<div class="ca-description ca-preview">';
                                        panelHTML += lockRank;
                                        panelHTML += '</div></li>';
                                    }

                                    if (typeof(venueItem.externalProviderIDs) != 'undefined') {
                                        panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">External Provider IDs</div>';
                                        panelHTML += '<div class="ca-description ca-preview">';
                                        panelHTML += '<div id="wmeFdoExternalProviders-' + venueAction.objectID + '"></div>';
                                        panelHTML += '</div></li>';
                                    }

                                    panelHTML+= '<li class="tx-changed-attribute"><div class="ca-name">History</div>';
                                    panelHTML += '<div class="ca-description ca-preview">';
                                    panelHTML += formatVenueHistory(venueHistory);
                                    panelHTML += '</div></li>';


                                    panelHTML += '</ul>';
                                    info.innerHTML = panelHTML;



                                    //info.innerHTML += 'Changed from';
                                    //info.innerHTML += '<span class="ca-value ca-value-old">';
                                    //info.innerHTML += 'Walmart';
                                    //info.innerHTML += '</span>';
                                    //info.innerHTML += '<span>';
                                    //info.innerHTML += 'to';
                                    //info.innerHTML += '</span>';
                                    //info.innerHTML += '<span class="ca-value ca-value-new">';
                                    //info.innerHTML += 'Walmart Supercenter';
                                    //info.innerHTML += '</span>';

                                    $('#wmeFdoTransactionList').append(details);

                                    let venueIdDiv = document.getElementById(venueIdReference);

                                    let venueIdDisplay = document.createElement('span');
                                    if (W.loginManager.user.userName == "Joyriding") {
                                        venueIdDisplay.innerHTML = `<a href="https://${window.location.host}${W.Config.api_base}/ElementHistory?objectType=venue&objectID=${venueAction.objectID}" target="_blank">${venueAction.objectID}</a> `;
                                    } else {
                                        venueIdDisplay.innerHTML = venueAction.objectID;
                                    }
                                    venueIdDiv.append(venueIdDisplay);

                                    let pin = displayPin(operation, venueAction.oldValue);
                                    let areaPreview = null;

                                    details.onmouseenter = function () {
                                        highlightPin(pin);
                                        areaPreview = showDeletedArea(operation, venueAction.oldValue);
                                    };
                                    details.onmouseleave = function () {
                                        returnPin(pin);
                                        //removeDeletedArea();
                                        _wmeFdoFeatureLayer.removeFeatures([areaPreview]);
                                    };
                                }

                            }
                        }

                    }
                }
            });

        });
    }

    function toggleFlag (control, objectType, objectID) {
        if (flags[objectType][objectID]) {
            delete flags[objectType][objectID];
            control.className = 'wmeFdoFlag';
        } else {
            flags[objectType][objectID] = true;
            control.className = 'wmeFdoFlag flagged';
        }

    }

    function displayDetails() {

    }

    async function recreatePlace(origVenue, venueHistory, operationCenterPoint) {
        var lonLat = new OL.LonLat(operationCenterPoint.x, operationCenterPoint.y);
        if(!W.map.getExtent().containsLonLat(operationCenterPoint.toLonLat())) {
            // Move to pin so that the City can be populated correctly
            W.map.moveTo(lonLat, 6);
            await wait(1000); //TODO: Check to see that cities have loaded before continuing rather than guessing a time delay
        }

        let wazeActionUpdateFeatureGeometry = require("Waze/Action/UpdateFeatureGeometry");
        let wazefeatureVectorLandmark = require("Waze/Feature/Vector/Landmark");
        let wazeActionAddLandmark = require("Waze/Action/AddLandmark");
        //console.log(origVenue);

        let landmark = new wazefeatureVectorLandmark();
        if (origVenue.geometry.type == 'Polygon') {
            let points = [];
            origVenue.geometry.coordinates[0].forEach( function(coord) {
                let point = new OL.Geometry.Point(coord[0],coord[1]);
                point.transform(W.map.displayProjection,W.map.projection);
                points.push(point);
            });
            points.push(points[0]);
            let linearRing = new OL.Geometry.LinearRing(points);
            let polygon = new OL.Geometry.Polygon(linearRing);
            landmark.geometry = polygon;
        } else {
            let point = new OL.Geometry.Point(origVenue.geometry.coordinates[0],origVenue.geometry.coordinates[1]);
            point.transform(W.map.displayProjection,W.map.projection);
            landmark.geometry = point;
        }

        if (typeof(origVenue.name) != 'undefined') { landmark.attributes.name = origVenue.name; }
        if (typeof(origVenue.aliases) != 'undefined') { landmark.attributes.aliases = origVenue.aliases; }
        if (typeof(origVenue.categories) != 'undefined') { landmark.attributes.categories = origVenue.categories; }
        if (typeof(origVenue.description) != 'undefined') { landmark.attributes.description = origVenue.description; }
        if (typeof(origVenue.lockRank) != 'undefined') { landmark.attributes.lockRank = origVenue.lockRank; }
        if (typeof(origVenue.openingHours) != 'undefined') { landmark.attributes.openingHours = origVenue.openingHours; }
        if (typeof(origVenue.phone) != 'undefined') { landmark.attributes.phone = origVenue.phone; }
        if (typeof(origVenue.rank) != 'undefined') { landmark.attributes.rank = origVenue.rank; }
        if (typeof(origVenue.residential) != 'undefined') { landmark.attributes.residential = origVenue.residential; }
        if (typeof(origVenue.services) != 'undefined') { landmark.attributes.services = origVenue.services; }
        if (typeof(origVenue.url) != 'undefined') { landmark.attributes.url = origVenue.url; }

        if (origVenue.categories.indexOf('GAS_STATION') > -1 || origVenue.categories.indexOf('PARKING_LOT') > -1) {
            if (typeof(origVenue.brand) != 'undefined') { landmark.attributes.brand = origVenue.brand; }
        }

        if (origVenue.categories.indexOf('PARKING_LOT') > -1)
        {
            if (typeof(origVenue.categoryAttributes) != 'undefined') { landmark.attributes.categoryAttributes = origVenue.categoryAttributes; }
        }

        if (typeof(origVenue.entryExitPoints) != 'undefined') { //TODO: and if # entry points > 0
            // Only use first point for now until multiple are supported.
            //origVenue.entryExitPoints.forEach(function(origEntryExitPoint) // Uncomment once supported
            {
                let origEntryExitPoint = origVenue.entryExitPoints[0]; // Remove once supported
                let point = new OL.Geometry.Point(origEntryExitPoint.point.coordinates[0],origEntryExitPoint.point.coordinates[1]);
                point.transform(W.map.displayProjection, W.map.projection);
                let entryExitPoint = new NavigationPoint(point);
                // Uncomment below once supported
                //entryExitPoint._name = origEntryExitPoint.name;
                //entryExitPoint._entry = origEntryExitPoint.entry;
                //entryExitPoint._exit = origEntryExitPoint.exit;
                //entryExitPoint._isPrimary = origEntryExitPoint.primary;
                landmark.attributes.entryExitPoints.push(entryExitPoint);
            }
            //);
        }



        //debugger;
        W.model.actionManager.add(new wazeActionAddLandmark(landmark));

        if (typeof(origVenue.streetID) != 'undefined') {

            //TODO: Make this a separate function and use for all address building, cache after built by streetID
            var addressParts = {
                streetName: "",
                emptyStreet: false,
                cityName: "",
                emptyCity: false,
                streetID: origVenue.streetID,
                stateID: null,
                countryID: null,
                addressFormShown: false,
                editable: true,
                fullAddress: "",
                ttsLocales: [W.Config.tts.default_locale],
                altStreets: new Backbone.Collection,
                newAltStreets: new Backbone.Collection
            };

            venueHistory.streets.objects.forEach(function(street) {
                if (street.id == origVenue.streetID) {
                    if (street.name == "")
                        addressParts.emptyStreet = true;
                    addressParts.streetName = street.name;

                    venueHistory.cities.objects.forEach(function(city) {
                        if (street.cityID == city.id) {
                            if (city.name == "")
                                addressParts.emptyCity = true;
                            addressParts.cityName = city.name;
                            addressParts.stateID = city.stateID;
                            venueHistory.states.objects.forEach(function(state) {
                                if (city.stateID == state.id) {
                                    addressParts.countryID = state.countryID;

                                }
                            });
                        }
                    });
                }
            });

            let wazeActionUpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress');
            W.model.actionManager.add(new wazeActionUpdateFeatureAddress(landmark, addressParts,{streetIDField: 'streetID'}));
        }

        if (typeof(origVenue.houseNumber) != 'undefined') {
            let wazeActionUpdateObject = require('Waze/Action/UpdateObject');
            W.model.actionManager.add(new wazeActionUpdateObject(landmark,{houseNumber: origVenue.houseNumber}));
        }

        //console.log(landmark);

        let foundFeature = null;
        W.selectionManager._layers.forEach(function(layer) {
            if (layer.featureType == 'venue') {
                layer.features.forEach(function(feature) {
                    if (landmark.geometry.id == feature.geometry.id)
                    {
                        foundFeature = feature;
                    }
                });
            }
        });
        if (foundFeature != null) {
            W.selectionManager.selectFeature(foundFeature);
            W.selectionManager._triggerSelectionChanged()
        }



        var epm = Backbone.Model.extend({
            defaults: {
                uuid: null,
                name: null,
                url: null,
                location: null
            },
            initialize: function() {
                if (null === this.get("uuid")) return this.set({
                    uuid: this.id
                })
            },
            getDetails: function(e) {
                this.set({uuid: e});
                return this.set({
                    name: externalProviderDetails[e].name,
                    location: externalProviderDetails[e].geometry.location,
                    url: externalProviderDetails[e].url
                });

            },
            _getDetailsFromUuid: function(e) {
                return this.set({
                    name: this.get("uuid"),
                    location: "",
                    url: ""
                })
            },
            toJSON: function() {
                return this.get("uuid")
            }
        });




        if (typeof(origVenue.externalProviderIDs) != 'undefined') {
            let ids = [];
            origVenue.externalProviderIDs.forEach(function(externalProviderID) {
                let newID = new epm();
                newID.getDetails(externalProviderID);
                newID.collection = new Backbone.Collection();
                // newID.collection.model.push(newID);
                ids.push(newID);

                // - Find an existing venue with an external provider.
                // - If found:
                //   -- Copy that object to new venue
                //   -- Update object with data from deleted venue

                //debugger;
                // landmark.attributes.externalProviderIDs.push(newID);

            }); //10521
            let wazeActionUpdateObject = require('Waze/Action/UpdateObject');
            //debugger;
            if (window.location.host.includes('beta')) { //TODO: Make work in Prod, or wait for Beta to be promoted
                W.model.actionManager.add(new wazeActionUpdateObject(landmark,{externalProviderIDs: ids}));
            }
        }
        //console.log(landmark);
    }

    function displayExternalProviders(venueID, externalProviderIDs) {
        if (typeof(externalProviderIDs) != 'undefined') {
            externalProviderIDs.forEach(function(externalProviderID) {
                if (typeof(externalProviderDetails[externalProviderID]) == 'undefined') {
                    let sessionid = '';
                    if (typeof(uuidGenerator) != 'undefined') {
                        sessionid = uuidGenerator.v1();
                    }
                    let url = `https://${window.location.host}/${W.Config.places_api.url.details}?placeid=${externalProviderID}&key=${W.apiKeys.googleMapsApiKey}&sessiontoken=${sessionid}`;
                    $.getJSON(url,function(data) {
                        //TODO: check for invalid result
                        let closed = data.result.permanently_closed;
                        let extProv = document.getElementById('wmeFdoExternalProviders-' + venueID);
                        let div = document.createElement('div');
                        div.innerHTML = '<a href="' + data.result.url + '" target="_blank">' + externalProviderID + '</a>';
                        if (closed) {
                            div.innerHTML = div.innerHTML + ' <span style="color:red;">closed</span>';
                        }
                        extProv.append(div);
                        externalProviderDetails[externalProviderID] = data.result;

                        // console.log(data);
                    });
                } else {
                    let data = {};
                    data.result = externalProviderDetails[externalProviderID];
                    let closed = data.result.permanently_closed;
                    let extProv = document.getElementById('wmeFdoExternalProviders-' + venueID);
                    let div = document.createElement('div');
                    div.innerHTML = '<a href="' + data.result.url + '" target="_blank">' + externalProviderID + '</a>';
                    if (closed) {
                        div.innerHTML = div.innerHTML + ' <span style="color:red;">closed</span>';
                    }
                    extProv.append(div);
                }
            });
        }
    }

    function toggleDetailsDropDown(elementId, operationCenterPoint) {
        let details = document.getElementById(elementId);
        let openDropDown = false;
        if (details.classList.contains('closed')) {
            openDropDown = true;
        }
        closeAllDetailsDropDown();
        if (openDropDown) {
            details.className = 'element-history-item tx-has-content';
            settings.Enabled = true;
            onLayerCheckboxChanged(settings.Enabled); // TODO: Fix this so the Layer menu checkbox gets updated
            if(!W.map.getExtent().containsLonLat(operationCenterPoint.toLonLat())) {
                W.map.moveTo(operationCenterPoint.toLonLat(), 6);
            }
        }
    }

    function openDetailsDropDown(elementId, operationCenterPoint) {
        let details = document.getElementById(elementId);
        details.className = 'element-history-item tx-has-content';
        //    var lonLat = new OL.LonLat(operationCenterPoint.x, operationCenterPoint.y);
        //W.map.moveTo(operationCenterPoint.toLonLat(), 6);
    }

    function closeDetailsDropDown(elementId) {
        let details = document.getElementById(elementId);
        details.className = 'element-history-item tx-has-content tx-has-related closed';
    }

    function closeAllDetailsDropDown() {
        let ul = document.getElementById('wmeFdoTransactionList');
        let items = ul.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let elementId = items[i].id;
            if (elementId.startsWith('entry-')) {
                closeDetailsDropDown(elementId);
            }
        }
    }

    function displayPin(transactionOperation, deletedVenue) {
        let deletedPlacePt=new OL.Geometry.Point(transactionOperation.objectCentroid.coordinates[0], transactionOperation.objectCentroid.coordinates[1]);
        deletedPlacePt.transform(W.map.displayProjection, W.map.projection);
        let point = new OL.Geometry.Point(deletedPlacePt.x, deletedPlacePt.y);
        let style = {strokeColor: 'black',
                     strokeWidth: '5',
                     strokeDashstyle: 'solid',
                     pointRadius: '5',
                     fillOpacity: '1'};
        let feature = new OL.Feature.Vector(point, {id:transactionOperation.objectID}, style);

        var di = require("Waze/DivIcon");
        var iconA = new di("place-update add_venue low map-marker wmeFdo-place-delete");
        var lonlatA = new OL.LonLat(deletedPlacePt.toLonLat().lon, deletedPlacePt.toLonLat().lat);
        // let markerLayer = W.map.getLayerByUniqueName('__wmeFdoMarkerLayer');
        let marker = new OL.Marker(lonlatA, iconA);
        marker.id = 1;
        marker.events.register('click', marker, function(evt) {
            W.selectionManager.unselectAll();
            selectTab();
            let elementId = 'entry-' + transactionOperation.objectID;
            closeAllDetailsDropDown();
            openDetailsDropDown(elementId, deletedPlacePt);
            document.getElementById(elementId).scrollIntoView();
        });

        _wmeFdoFeatureLayer.addFeatures([feature]);
        _wmeFdoMarkerLayer.addMarker(marker);
        return feature;
    }

    function showDeletedArea(transactionOperation, deletedVenue) {

        let polygon = null;
        if (deletedVenue.geometry.type == 'Polygon') {
            let points = [];
            deletedVenue.geometry.coordinates[0].forEach( function(coord) {
                let point = new OL.Geometry.Point(coord[0],coord[1]);
                point.transform(W.map.displayProjection,W.map.projection);
                points.push(point);
            });
            points.push(points[0]);
            let linearRing = new OL.Geometry.LinearRing(points);
            polygon = new OL.Geometry.Polygon(linearRing);
        }
        let style = {strokeColor: 'orange',
                     strokeWidth: '4',
                     strokeDashstyle: 'solid',
                     fillOpacity: '0.2'};
        let feature = new OL.Feature.Vector(polygon, {id:transactionOperation.objectID}, style);
        _wmeFdoFeatureLayer.addFeatures([feature]);
        return feature;
    }

    function getParentCategory(category) {
        // Build subcategory > parent category mapping once
        if (subcategoryToParentCategories.length == 0) {
            let index = 0;
            Object.keys(W.Config.venues.subcategories).forEach(function(parent) {
                let parentName = Object.getOwnPropertyNames(W.Config.venues.subcategories)[index];
                subcategoryToParentCategories[parentName] = parentName;
                W.Config.venues.subcategories[parentName].forEach(function(subcategory) {
                    subcategoryToParentCategories[subcategory] = parentName;
                });
                index++;
            });
        }

        return subcategoryToParentCategories[category];
    }

    function getCategoryIconClass(category) {
        let parentCategory = getParentCategory(category);

        // TODO: Lowercase and replace '_' with '-' instead?
        switch(parentCategory) {
            case 'TRANSPORTATION':
                return 'transportation';
                break;
            case 'PROFESSIONAL_AND_PUBLIC':
                return 'professional-and-public';
                break;
            case 'SHOPPING_AND_SERVICES':
                return 'shopping-and-services';
                break;
            case 'FOOD_AND_DRINK':
                return 'food-and-drink';
                break;
            case 'CULTURE_AND_ENTERTAINEMENT':
                return 'culture-and-entertainement';
                break;
            case 'LODGING':
                return 'lodging';
                break;
            case 'OUTDOORS':
                return 'outdoors';
                break;
            case 'NATURAL_FEATURES':
                return 'natural-features';
                break;
            case 'PARKING_LOT':
                return 'parking-lot';
            default:
                return 'OTHER';
        }

        // WME CSS Example:
        //     #edit-buttons .transportation .item-icon::after {
        //     background-image: url(//editor-assets.waze.com/production/img/toolbare2f6b31….png);
        //         background-position: -13px -70px;
        //        width: 12px;
        //         height: 12px;
        //        }
    }

    function formatStreetName(venueHistory, streetId) {
        // TODO: replace with address cache lookup/build function
        let streetName = null;
        venueHistory.streets.objects.forEach(function(street) {
            if (street.id == streetId) {
                streetName = street.name;
            }
        });

        if (streetName == null) {
            streetName = "";
        }
        return streetName;
    }

    function formatFullStreetName(venueHistory, streetId) {
        // TODO: replace with address cache lookup/build function
        let streetName = null;
        venueHistory.streets.objects.forEach(function(street) {
            if (street.id == streetId) {
                streetName = street.name;
                venueHistory.cities.objects.forEach(function(city) {
                    if (street.cityID == city.id) {
                        if (streetName != '') {
                            streetName = streetName + ", ";
                        }
                        streetName = streetName + city.name;
                        venueHistory.states.objects.forEach(function(state) {
                            if (city.stateID == state.id) {
                                if (city.name != '') {
                                    streetName = streetName + ", ";
                                }
                                streetName = streetName + state.name;
                            }
                        });
                    }
                });
            }
        });

        if (streetName == null) {
            streetName = "";
        }
        return streetName;
    }

    function formatCategories(categories) {
        let output = ''
        categories.forEach(function(category) {
            output = output + ", " + I18n.t("venues.categories." + category);
        });
        output = output.substring(2);

        return output;
    }

    function formatExternalProviders(externalProviderIDs) {
        let output = "<ul>";
        if (typeof externalProviderIDs != 'undefined') {
            externalProviderIDs.forEach(function(externalProviderID) {
                output = output + "<li>" + externalProviderID + "</ul>";
            });
        }
        output = output + "</ul>";
        return output;
    }

    function formatVenueHistory(venueHistory) {
        let output = "<ul>";
        venueHistory.transactions.objects.forEach(function(venueTransaction) {
            let username = null;
            venueHistory.users.objects.forEach( function(user) {
                if (user.id == venueTransaction.userID) {
                    let userRank = user.rank + 1;
                    let name = user.userName + '(' + userRank + ')';
                    username = `<a target="_blank" href="https://${window.location.host}/user/editor/${user.userName}">${name}</a>`;
                }
            });
            let detailCount = 0;
            let requestType = venueTransaction.actionType;
            if (venueTransaction.objects[0].objectType == 'venueUpdateRequest') {
                requestType = requestType + " (PUR)";
            }
            output = output + "<li><div>" + timeConverter(venueTransaction.date) + " " + requestType + " " + username + "</div>";
            output = output + '<ul class="historySummaryItem">';
            if (typeof(venueTransaction.objects[0].newValue) != 'undefined' && typeof(venueTransaction.objects[0].newValue.name) != 'undefined')
            {
                detailCount++;
                output = output + '<li>Name: ' + venueTransaction.objects[0].newValue.name + '</li>';
                if (venueTransaction.objects[0].actionType == 'UPDATE') {
                    output = output + '<li>Was: ' + venueTransaction.objects[0].oldValue.name + '</li>';
                }

            }
            if (typeof(venueTransaction.objects[0].oldValue) != 'undefined' && venueTransaction.objects[0].oldValue.type == 'IMAGE') {
                output = output + `<li><a target="_blank" href="https://venue-image.waze.com/${venueTransaction.objects[0].oldValue.id}">Rejected Photo</a></li>`;
            }
            output = output + '</ul>';
            if (detailCount > 0) {
                output = output + '</li style="margin-bottom:4px">';
            } else {
                output = output + "</li>";
            }
            // console.log(venueTransaction);
        });
        output = output + "</ul>";
        return output;
    }

    function endScan(reasonText, setEndedFlag) {
        if (setEndedFlag) {
            var findButton = document.getElementById('wmeFdoFindButton');
            findButton.className = 'btn btn-success center-block';

            var cancelButton = document.getElementById('wmeFdoCancelButton');
            cancelButton.className = 'btn btn-warning center-block hidden';

            $('#wmeFdoTabFind').data("status","ended");

            let tab = document.getElementById('wmeFdoTab');
            tab.innerHTML = 'FiDO <span class="spinner-disable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
        }
        $('#wmeFdoStatusDate')[0].innerText = reasonText;
    }

    async function findObjects() {

        //var centerCoords = W.map.getCenter().clone().transform(W.map.projection, W.map.displayProjection);
        if (settings.NearbyEditors) {

            let editorListScreen = getNearbyEditors();

            let featuresList = await getScreenFeatures();
            // console.log(featuresList);
            // debugger;
            //console.log(featuresList[0].venues);

            let editorListFeatures = getNearbyEditorsFromFeatures(featuresList);

            let editorListTemp = [];
            editorListScreen.forEach(function(userID) {
                editorListTemp[userID] = true;
            });
            editorListFeatures.forEach(function(userID) {
                editorListTemp[userID] = true;
            });

            let editorList = [];
            Object.keys(editorListTemp).forEach(function(userID) {
                editorList.push(userID);
            });


            let searchAreaGeometry = getSearchArea();

            editorList.forEach(function (userID) {

                //console.log('userID: ' + userID);



                var lookFor = {
                    objectType:'Venue',
                    actionType:'DELETE',
                    searchArea: searchAreaGeometry,
                    limitToScreen: true,
                    includeSelf: true

                }

                Date.prototype.addDays = function(days) {
                    var date = new Date(this.valueOf());
                    date.setDate(date.getDate() + days);
                    return date;
                }

                let startDate = null;
                let startDateInput = document.getElementById('wmeFdoFindDate');
                if (Date.parse(startDateInput.value) != null) {
                    let dateInput = new Date(startDateInput.value).addDays(1);
                    startDate = dateToUuid(dateInput);
                }

                var transactionLookup = {
                    username:null,
                    userId:userID,
                    nextTransactionDate:startDate,
                    iteration:0,
                    maxIterations:100000
                };

                beginFind(lookFor, transactionLookup);
            });
        } else {


            var searchAreaGeometry = getSearchArea();

            var lookFor = {
                objectType:'Venue',
                actionType:'DELETE',
                searchArea: searchAreaGeometry,
                limitToScreen: settings.LimitToScreen,
                includeSelf: settings.IncludeSelf

            }

            Date.prototype.addDays = function(days) {
                var date = new Date(this.valueOf());
                date.setDate(date.getDate() + days);
                return date;
            }

            let startDate = null;
            let startDateInput = document.getElementById('wmeFdoFindDate');
            if (Date.parse(startDateInput.value) != null) {
                let dateInput = new Date(startDateInput.value).addDays(1);
                startDate = dateToUuid(dateInput);
            }

            var transactionLookup = {
                username:$('#wmeFdoEditorName')[0].value,
                userId:null,
                nextTransactionDate:startDate,
                iteration:0,
                maxIterations:100000
            };

            if (transactionLookup.username == null || transactionLookup.username == "") {
                endScan('Please enter an editor name!', true);
            } else {
                let requestParamsUserProfile = requestParams(getApiUrlUserProfile(transactionLookup.username));
                requestStatus[requestParamsUserProfile.url] = defaultRequestStatus();
                var userProfile = await getApiRequest(requestParamsUserProfile);
                if (typeof(userProfile.userID) == 'undefined') {
                    endScan('Editor name not found!', true);
                } else {
                    transactionLookup.userId = userProfile.userID;
                    beginFind(lookFor, transactionLookup);
                }
            }
        }



    }

    function getSearchArea() {
        var theVenue = null;
        var count = 0;
        for (var v in W.model.venues.objects) {
            if (W.model.venues.objects.hasOwnProperty(v) === false) {
                continue;
            }
            var venue = W.model.venues.objects[v];
            if (venue.isPoint() === true) {
                continue;
            }
            if ($.isNumeric(venue.attributes.id) && parseInt(venue.attributes.id) <= 0) {
                theVenue = venue;
                count++;
            }
        }

        if (count === 0) {
            console.log("using screen");
            return W.map.getExtent().toGeometry();
        }
        if (theVenue.geometry.components.length !== 1) {
            alert("Can't parse the geometry");
            return;
        } else {
            console.log("using unsaved place area");
            return theVenue.geometry.clone();

        }
    }

    function getNearbyEditors() {

        $('#wmeFdoStatusDate')[0].innerText = 'Finding Nearby Editors...';

        let earliestDate = parseInt(new Date('2016-12-31').getTime());
        let editorList = [];
        let editorListReturn = [];

        Object.keys(W.model.venues.objects).forEach(function (venueId) {
            let venue = W.model.venues.objects[venueId].attributes;
            if (typeof(venue.createdOn) != 'undefined' && venue.createdOn > earliestDate) {
                editorList[venue.createdBy] = venue.createdOn;
            }
            if (typeof(venue.updatedOn) != 'undefined' && venue.updatedOn > earliestDate) {
                editorList[venue.updatedBy] = venue.updatedBy;
            }
            //console.log(W.model.venues.objects[venueId].attributes.createdBy)
        });

        Object.keys(W.model.segments.objects).forEach(function (segmentId) {
            let segment = W.model.segments.objects[segmentId].attributes;
            if (typeof(segment.createdOn) != 'undefined' && segment.createdOn > earliestDate) {
                editorList[segment.createdBy] = segment.createdOn;
            }
            if (typeof(segment.updatedOn) != 'undefined' && segment.updatedOn > earliestDate) {
                editorList[segment.updatedBy] = segment.updatedBy;
            }
            //console.log(W.model.venues.objects[venueId].attributes.createdBy)
        });

        Object.keys(editorList).forEach(function (editorId) {
            if (editorId != 'undefined') {
                editorListReturn.push(editorId);
            }
        });

        return editorListReturn;

    }

    function getNearbyEditorsFromFeatures(featuresList) {

        $('#wmeFdoStatusDate')[0].innerText = 'Finding Nearby Editors...';

        let earliestDate = parseInt(new Date('2016-12-31').getTime());
        let editorList = [];
        let editorListReturn = [];

        featuresList.forEach(function (features) {

            if (typeof(features.venues) != 'undefined') {
                features.venues.objects.forEach(function (venue) {
                    if (typeof(venue.createdOn) != 'undefined' && venue.createdOn > earliestDate) {
                        editorList[venue.createdBy] = venue.createdOn;
                    }
                    if (typeof(venue.updatedOn) != 'undefined' && venue.updatedOn > earliestDate) {
                        editorList[venue.updatedBy] = venue.updatedBy;
                    }
                });
            }

            if (typeof(features.segments) != 'undefined') {
                features.segments.objects.forEach(function (segment) {
                    if (typeof(segment.createdOn) != 'undefined' && segment.createdOn > earliestDate) {
                        editorList[segment.createdBy] = segment.createdOn;
                    }
                    if (typeof(segment.updatedOn) != 'undefined' && segment.updatedOn > earliestDate) {
                        editorList[segment.updatedBy] = segment.updatedBy;
                    }
                });
            }


        });

        Object.keys(editorList).forEach(function (editorId) {
            if (editorId != 'undefined') {
                editorListReturn.push(editorId);
            }
        });

        return editorListReturn;

    }

    async function getScreenFeatures() {

        return new Promise( async resolve => {
            let urlList = [];
            let featuresList = [];
            let maxDegrees = 0.125;
            let minDegrees = 0.0625;
            let extent = W.map.getExtent().transform(W.map.projection,W.map.displayProjection);
            //NW: LT - left top
            //NE: RT - right top
            //SW: LB - left bottom
            //SE: RB - right bottom

            let geoLT = new OL.Geometry.Point(extent.left,extent.top);
            let geoRB = new OL.Geometry.Point(extent.right,extent.bottom);
            let geoLTSplit = new OL.Geometry.Point(geoLT.x,geoLT.y);
            let geoRBSplit = new OL.Geometry.Point(geoRB.x,geoRB.y);
            let geoTempY = new OL.Geometry.Point(geoLT.x,geoLT.y);

            let xSplit = geoLT.x;
            let ySplit = geoLT.y;

            //console.log(extent);
            //console.log(geoLT);
            //console.log(geoRB);

            while (ySplit >= geoRB.y) {
                geoTempY.y = geoTempY.y - maxDegrees;

                ySplit = geoTempY.y;
                let degreesFromGeoRBY = geoRB.y - ySplit;
                //debugger;
                if (ySplit > geoRB.y)
                    geoRBSplit.y = ySplit
                else if (degreesFromGeoRBY <= minDegrees)
                    geoRBSplit.y = ySplit - (minDegrees - degreesFromGeoRBY);
                else
                    geoRBSplit.y = geoRB.y;

                while (xSplit <= geoRB.x) {
                    geoTempY.x = geoTempY.x + maxDegrees;

                    xSplit = geoTempY.x;
                    let degreesFromGeoRBX = geoRB.x - xSplit;
                    if (xSplit > geoRB.x)
                        geoRBSplit.x = xSplit
                    else if (degreesFromGeoRBX <= minDegrees)
                        geoRBSplit.x = xSplit - (minDegrees - degreesFromGeoRBX);
                    else
                        geoRBSplit.x = geoRB.x;

                    //  let bboxGeoLT = geoLTSplit.clone();
                    //  let bboxGeoEB = geoEBSplit.clone();
                    let bbox = `${geoLTSplit.x},${geoLTSplit.y},${geoRBSplit.x},${geoRBSplit.y}`;
                    let url = getApiUrlFeatures(bbox);
                    urlList.push(url);
                    //console.log(url);
                    let params = requestParams(url);
                    requestStatus[url] = defaultRequestStatus();
                    let features = null;

                    features = await getApiRequest(params);
                    featuresList.push(features);


                    geoLTSplit.x = xSplit;
                }

                geoLTSplit.y = ySplit;
                geoLTSplit.x = geoLT.x;
                xSplit = geoLT.x;
                geoTempY.x = xSplit;
            }

            resolve(featuresList);
        });
    }


    async function getApiRequest(requestParams) {


        let currentTime = Date.now();
        //console.log(requestParams.delay);

        requestStatus[requestParams.url].lastAttempt = currentTime;
        requestStatus[requestParams.url].status = 'waiting';
        // delay semi-randomized by using the last two digits of the hashcode of the url
        let delayTimeMs = requestParams.delay + requestParams.iteration;
        let rateLimitAttempt = 0;
        while (currentTime - rateLimitLastCall < getCurrentRateLimit() && rateLimitAttempt < 200) {
            // Attempt to rate limit requests by holding current request until configured amount of time has elapsed since last request.
            rateLimitAttempt++;
            let difference = currentTime - rateLimitLastCall;

            if (delayTimeMs > 400) {
                console.log("delay > 400 " + requestParams);
            }

            await wait(delayTimeMs);

            currentTime = Date.now();

        }
        //if (rateLimitAttempt == 100) {
        //    console.log("max attempts hit");
        //} else if (rateLimitAttempt == 0) {
        //    let diff = currentTime - rateLimitLastCall;
        //    console.log("did not rate limit (" + diff + ") " + requestParams.url);
        //}

        rateLimitLastCall = Date.now();
        requestStatus[requestParams.url].attempts++;
        requestStatus[requestParams.url].lastAttempt = Date.now();
        requestStatus[requestParams.url].status = 'requested';
        return new Promise( resolve => {
            $.ajax({
                type: 'GET',
                url: requestParams.url,
                success: function(data) {
                    adjustRateLimitDown();
                    requestStatus[requestParams.url].status = 'successful';
                    resolve(data);
                },
                statusCode: {
                    429: function() {

                        adjustRateLimitUp();
                        requestStatus[requestParams.url].status = 'error: rate limit';
                        console.log('Rate Limited: attempt ' + requestParams.attempts + " new delay: " + rateLimitAddedDelay);
                        resolve({"error": true,reason:"rate limit"});
                    },
                    500: function() {
                        resolve({"error":true,reason:"error"});
                    }
                }
            });

        });
    }

    function resetRateLimit() {
        rateLimitAddedDelay = 0;
        console.log(`Rate Limit reset: ${getCurrentRateLimit()}ms`);
    }

    function getCurrentRateLimit() {
        return rateLimitBaseDelay + rateLimitAddedDelay;
    }

    function adjustRateLimitDown() {
        let currentTime = Date.now();
        if (rateLimitAddedDelay > 0 && currentTime - rateLimitLastModified < 5000) {
            rateLimitAddedDelay -= rateLimitDelayStepDown;
            rateLimitLastModified = currentTime;
            console.log(`New Rate Limit (lowered): ${getCurrentRateLimit()}ms`);
        }
    }

    function adjustRateLimitUp() {
        let currentTime = Date.now();
        rateLimitAddedDelay += rateLimitDelayStepUp;
        rateLimitLastModified = currentTime;
        console.log(`New Rate Limit (raised): ${getCurrentRateLimit()}ms`);
    }

    function getApiUrlUserProfile(username) {
        return `https://${window.location.host}${W.Config.api_base}/UserProfile/Profile?username=${username}`;
    }

    function getApiUrlTransactions(userId, nextTransactionDate) {
        let url = `https://${window.location.host}${W.Config.api_base}/UserProfile/Transactions?userID=${userId}`;
        if (nextTransactionDate != null) {
            url = url + '&till=' + nextTransactionDate;
        }
        return url;
    }

    function getApiUrlElementHistory(objectType, objectId, nextTransactionDate) {
        let url = `https://${window.location.host}${W.Config.api_base}/ElementHistory?objectType=${objectType}&objectID=${objectId}`;
        if (nextTransactionDate != null) {
            url = url + '&till=' + nextTransactionDate;
        }
        return url;
    }

    function getApiUrlFeatures(bbox) {
        //let bbox = '-77.636232%2C43.152936%2C-77.6232%2C43.160287';
        let url = `https://${window.location.host}${W.Config.api_base}/Features?language=en-US&bbox=${bbox}&roadTypes=2%2C3%2C4%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20%2C21%2C22&venueLevel=4&venueFilter=3%2C3%2C3`;

        return url;
    }

    async function highlightPin(pin) {
        if (pin.onScreen()) {
            let layer = W.map.getLayerByUniqueName('__wmeFdoFeatureLayer');

            let enableAnimation = false;
            if (enableAnimation) {
                pin.data.mouseout = false;
                pin.style.strokeColor = 'red';
                for (var i=100;i>=1;i--) {
                    await wait(10);
                    pin.style.strokeWidth = i;
                    layer.redraw();
                    if (pin.data.mouseout == true) {
                        i = 1;
                        returnPin(pin);
                    }
                }
                pin.style.strokeWidth = 4;
                pin.style.pointRadius = 5;
                pin.style.strokeColor = 'orange';
                layer.redraw();
                if (pin.data.mouseout == true) {
                    returnPin(pin);
                }
            } else {
                pin.style.strokeWidth = 4;
                pin.style.pointRadius = 5;
                pin.style.strokeColor = 'orange';
                layer.redraw();
            }
        }
    }
    function returnPin(pin) {

        let style = {strokeColor: 'black',
                     strokeWidth: '4',
                     strokeDashstyle: 'solid',
                     pointRadius: '4',
                     fillOpacity: '1'};

        if (pin.onScreen()) {
            let layer = W.map.getLayerByUniqueName('__wmeFdoFeatureLayer');
            pin.data.mouseout = true;
            //pin.style.strokeColor = 'black';
            //pin.style.strokeWidth = 5;
            pin.style = style;
            layer.redraw();
        }
    }

    function createSettingsCheckbox($div, settingID, textDescription) {
        let $checkbox = $('<input>', {type:'checkbox', id:settingID, class:'wmeFdoSettingsCheckbox'});
        $div.append(
            $('<div>', {class:'controls-container'}).css({paddingTop:'2px'}).append(
                $checkbox,
                $('<label>', {for:settingID}).text(textDescription).css({whiteSpace:'pre-line'})
            )
        );
        return $checkbox;
    }

    function setChecked(checkboxId, checked) {
        $('#' + checkboxId).prop('checked', checked);
    }

    function saveSettings() {
        if (localStorage) {
            var localsettings = {
                Enabled: settings.Enabled,
                IncludeSelf: settings.IncludeSelf,
                LimitToScreen: settings.LimitToScreen,
                NearbyEditors: settings.NearbyEditors
            };

            localStorage.setItem("wmeFdo_Settings", JSON.stringify(localsettings));
        }
    }

    function loadSettings() {
        var loadedSettings = $.parseJSON(localStorage.getItem("wmeFdo_Settings"));
        var defaultSettings = {
            Enabled: true,
            IncludeSelf: true,
            LimitToScreen: true,
            NearbyEditors: true
        };
        settings = loadedSettings ? loadedSettings : defaultSettings;
        for (var prop in defaultSettings) {
            if (!settings.hasOwnProperty(prop))
                settings[prop] = defaultSettings[prop];
        }

    }

    function prepareTab() {
        let tabs = document.getElementById('user-tabs');
        let items = tabs.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let a = items[i].getElementsByTagName('a')[0]
            if (a.href.includes('sidepanel-fido')) {
                a.id = "wmeFdoTab";
                a.innerHTML = 'FiDO <span class="spinner-disable"><i class="icon-spinner icon-spin fa fa-spinner fa-spin"></i></span>';
            }
        }
    }

    function selectTab() {
        let tabs = document.getElementById('user-tabs');
        let items = tabs.getElementsByTagName('li');
        for (var i = 0; i < items.length; ++i) {
            let a = items[i].getElementsByTagName('a')[0]
            if (a.href.includes('sidepanel-fido')) {
                items[i].getElementsByTagName('a')[0].click();
            }
        }
    }

    function dateToUuid(inputDate) {
        let timestamp = parseInt(new Date(inputDate).getTime());
        // uuid = "a02102cd-19df-11e9-bd87-1201fde9baf6"
        // timestamp = 1547678386643;
        let a = timestamp * 10000;
        let b = a + 122192928000000000;
        let c = b.toString(16);
        //if (c.length % 2) {
        //   c = '0' + c;
        //}
        let d = c.substring(7) + '-' + c.substring(3,7) + '-1' + c.substring(0,3) + '-bd87-1201fde9baf6';

        return d;
    }

    function get_time_int(uuid_str) {
        var uuid_arr = uuid_str.split( '-' ),
            time_str = [
                uuid_arr[ 2 ].substring( 1 ),
                uuid_arr[ 1 ],
                uuid_arr[ 0 ]
            ].join( '' );
        return parseInt( time_str, 16 );
    };

    function uuidToDate(uuid_str) {
        var int_time = get_time_int( uuid_str ) - 122192928000000000,
            int_millisec = Math.floor( int_time / 10000 );
        //return new Date( int_millisec );
        return timeConverter(int_millisec);
    };

    function formatCurrentDate() {
        var today = new Date();
        var dd = today.getDate();
        var mm = today.getMonth() + 1; //January is 0!
        var yyyy = today.getFullYear();

        if (dd < 10) {
            dd = '0' + dd;
        }

        if (mm < 10) {
            mm = '0' + mm;
        }

        return yyyy + '-' + mm + '-' + dd;
    }

    function timeConverter(UNIX_timestamp){
        var a = new Date(UNIX_timestamp);
        var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        var year = a.getFullYear();
        var month = months[a.getMonth()];
        var date = a.getDate();
        if (date.toString().length == 1) {
            date = "0" + date;
        }
        var hour = a.getHours();
        if (hour.toString().length == 1) {
            hour = "0" + hour;
        }
        var min = a.getMinutes();
        if (min.toString().length == 1) {
            min = "0" + min;
        }
        var sec = a.getSeconds();
        if (sec.toString().length == 1) {
            sec = "0" + sec;
        }
        //Dec 08, 2018
        var time = month + ' ' + date + ', ' + year + ' ' + hour + ':' + min;
        return time;
    }

    async function wait(ms) {
        return new Promise(resolve => {
            setTimeout(resolve, ms);
        });
    }


    bootstrap();
})();