Restriction Manager

Save, and load, restrictions from local storage.

目前为 2018-06-29 提交的版本。查看 最新版本

/* global $ */

// ==UserScript==
// @name        Restriction Manager
// @version     0.2
// @description Save, and load, restrictions from local storage.
// @namespace   mailto:[email protected]
// @include     https://www.waze.com/editor*
// @include     https://www.waze.com/*/editor*
// @include     https://beta.waze.com/*
// @exclude     https://www.waze.com/user/*
// @exclude     https://www.waze.com/*/user/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAjCAIAAABzZz93AAAABnRSTlMA/wD/AP83WBt9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABV0lEQVRIiWP8//8/A70ACxl6vm6JgjC4fZaRpJGRVJ/BbYID4q1kotAmkgBpllHoAhIso9BbJFiG3yYi3UFyarx39rRewx0IO6nBZ5IxL/F6ifIZisOffoAzrzzFrub/v7/kW0YqYGRiJtMy4tMFQZUELKM8BZJgGSZQkhbAI4vfcfgsI+gtHWksSRGPLpokEJItoyS2cOnFbhl10wUByygHWJ2LxTIC3pLiMyPXBeiWkRKAIupS+KQxjaJtakSzjwmPHA7AqwNlCKiSaDdKG4RGiRDeSEH4jEY2IQMmkm16fjOvdDmP73Ie391TnxOlA244yQnk3ulH825AmG/K1z0jSS8TA4kBeOfxG5IsgACIFST7TEVWhAzLIID0+sxULkkDwhTpDMKbqzEA4////+mQDhkYGLh9ljExkN4/IM8mhoGpPGnqObjh6F0m6sYfmh9I7p9RAgAFyXyizju5WQAAAABJRU5ErkJggg==
// @resource    jqUI_CSS  https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css
// @grant       none
// @copyright   2018, kjg53
// @license     GNU GPL v3
// @author      kjg53
// ==/UserScript==

(function() {
    var initialized = false;
    var lsPrefix = "rtmgr:";

    // Map the css class used to identify the three restriction blocks to the direction constants used in the data.
    var classToDirection={"forward-restrictions-summary": "FWD",
                          "reverse-restrictions-summary": "REV",
                          "bidi-restrictions-summary": "BOTH"};

    // Convert the segment's default type to the driving modality that implied the type.  Creating a Toll Free restriction
    // implies that the segment is otherwise (i.e. defaults) to tolled which is what is then stored in the model.
    // Finding the tolled default thereby implies that the current restriction is specifying a toll free rule.
    var defaultType2drivingModality = {"TOLL": "DRIVING_TOLL_FREE",
                           "FREE":"DRIVING_BLOCKED",
                           "BLOCKED":"DRIVING_ALLOWED"};

    // Map the single bit constants used in the weekdays property to the integer numbers encoded in each week days HTML display.
    var weekdayBit2Idx = {
        1:1,
        2:2,
        4:3,
        8:4,
        16:5,
        32:6,
        64:0
    };

    // Get a sorted list of saved restrictions found in local storage.
    function allSavedRestrictions() {
        var all = [];
        for(var i = 0; i < localStorage.length; i++) {
            var key = localStorage.key(i);

            if (key.indexOf(lsPrefix) == 0) {
                key = key.substring(lsPrefix.length);

                all.push(key);
            }
        }

        all.sort();

        return all;
    }

    // Convert list of saved restrictions into a string of HTML option elements.
    function allSavedRestrictionAsOptions() {
        var all = allSavedRestrictions();
        return all.length == 0 ? "" : "<option>&nbsp/<option><option>" + all.join("</option><option>") + "</option>";
    }

    // Update all restriction selectors to display the saved restrictions returned by allSavedRestrictions
    function updateSavedRestrictionSelectors(root) {
        $("div.rtmgr div.name select", root).html(allSavedRestrictionAsOptions()).each(resizeDivName);
    }

    // The content of the div.name element are positioned relative to its location.  As a result, the
    // div normally collapses to a point in the screen layout.  This function expands the div to enclose
    // its contents such that other elements are laid out around them.
    function resizeDivName(idx, child) {
        var div = $(child).parents("div.name").first();
        var height = 0;
        var width = 0;

        div.children().each(function(idx, child) {
            child = $(child);
            height = Math.max(height, child.height());
            width = Math.max(width, child.width());
        });

        div.width(width).height(height);
    }


    // Identify the direction of the restrictions associated with the specified button.
    function direction(btn) {
        var classes = btn.parents("div.restriction-summary-group").attr('class').split(' ');

        while(classes.length) {
            var cls = classes.pop();
            var dir = classToDirection[cls];

            if (dir) {
                return dir;
            }
        }
    }


    function setValue(selector, model, value) {
        if (value != null) {
            var sel = $(selector, model);
            var oldValue = sel.val();
            if (oldValue != value) {
                sel.val(value);
                sel.change();
            }
        }
    }

    function setCheck(selector, model, value) {
        if (value != null) {
            value = !!value;
            var sel = $(selector, model);
            var oldValue = sel.prop('checked');
            if (oldValue != value) {
                sel.prop('checked', value);
                sel.change();
            }
        }
    }

    function setSelector(name, model, value) {
        setValue('select[name="' + name + '"]', model, value);
    }

    function lastFaPlus(modal) {
        return $("i.fa-plus", modal).last();
    }

    function clearMessages() {
        $("div.restriction-validatation-region div.rtmgr").remove();
    }
    function addMessage(text) {
        var rvr = $("div.restriction-validation-region");
        var rvrul = $("ul", rvr);
        if (rvrul.length == 0) {
            rvr.append('<div><div class="restriction-validation-title">The Restrictions Manager encountered the following issue.</div><div class="collection-region"><ul></ul></div></div>');
            rvrul = $("ul", rvr);
        }
        rvrul.append('<li class="restriction-validation-error">' + text + '</li>');
    }

    function initializeRestrictionManager() {
        if (initialized) {
            return;
        }

        var observerTarget = document.getElementById("dialog-region");

        if (!observerTarget) {
            window.console.log("Restriction Manager: waiting for WME...");
            setTimeout(initializeRestrictionManager, 1015);
        }

        // Inject my stylesheet into the head
        var sheet = $('head').append('<style type="text/css"/>').children('style').last();
        sheet.append('div.rtmgr-column {display: flex; flex-direction: column}');
        sheet.append('div.rtmgr-row {display: flex; flex-direction: row; justify-content: space-around}');
        sheet.append('div.rtmgr btn {margin-top: 5px}');
        sheet.append('div.rtmgr div.name input {width: 250px; position: absolute; left: 0px; top: 0px; z-index: 1}');
        sheet.append('div.rtmgr div.name select {width: 275px; position: absolute; left: 0px; top: 0px}');
        sheet.append('div.rtmgr div.name {width: 275px; position: relative; left: 0px; top: 0px}');

        // create an observer instance
        var observer = new MutationObserver(function(mutations) {
            var si = W.selectionManager.getSelectedFeatures();

            mutations.forEach(function(mutation) {
                if("childList" == mutation.type && mutation.addedNodes.length) {
                    var restrictionsModal = $("div.modal-dialog.restrictions-modal", observerTarget);

                    if (restrictionsModal) {
                        var modalTitle = $(restrictionsModal).find("h3.modal-title").first();
                        var title = modalTitle.text().replace(/[\x00-\x1F\x7F-\x9F]/g, "");

                        if ("Time based restrictions" == title) {
                            if (modalTitle.data('rtmgr') === undefined) {
                                // Flag this modal as having already augmented
                                modalTitle.data('rtmgr', true);

                                // Add the UI elements to the modal
                                $("div.restriction-summary-group div.restriction-summary-title", restrictionsModal)
                                    .append (
                                        ""
                                            + "<div class='rtmgr rtmgr-column'>"
                                            +   "<div class='name'>"
                                            +     "<input type='text'/>"
                                            +     "<select/>"
                                            +   "</div>"
                                            +   "<div class='rtmgr-row'>"
                                            +     "<button class='btn save'>Save</button>"
                                            +     "<button class='btn apply'>Apply</button>"
                                            +     "<button class='btn delete'>Delete</button>"
                                            +   "</div>"
                                            + "</div>");

                                // Initialize the saved restriction selectors
                                updateSavedRestrictionSelectors(restrictionsModal);

                                // When a selection is made copy it to the overlapping input element to make it visible.
                                $("div.rtmgr select").change(function(evt) {
                                    var tgt = evt.target;
                                    var txt = $(tgt).parent().children("input");
                                    var text = tgt.options[tgt.selectedIndex].text;
                                    txt.val(text);
                                });

                                // Delete action
                                $("div.rtmgr button.delete", restrictionsModal).click(function(evt) {
                                    var tgt = $(evt.target);
                                    var inp = tgt.parents('div.rtmgr').find("input");
                                    var name = inp.val();
                                    if (name != "") {
                                        localStorage.removeItem(lsPrefix + name);
                                        updateSavedRestrictionSelectors(restrictionsModal);
                                        inp.val("");
                                    }
                                });

                                // Save action (only one segment currently selected)
                                if (si.length == 1) {
                                    $("div.rtmgr button.save", restrictionsModal).click(function(evt) {
                                        var tgt = $(evt.target);
                                        var name = tgt.parents('div.rtmgr').find("input").val();
                                        if (name != "") {
                                            var dir = direction(tgt);
                                            var attrs = si[0].model.getAttributes();
                                            var src = attrs.restrictions;

                                            // Checking for pending updates to the selected segment's restrictions.  If found, save a copy of them.
                                            // This is a convenience feature that enables an editor to Apply a restriction change to a segment and then store it for re-use without first having to save it on the original segment.
                                            for(var i = W.model.actionManager.actions.length; i-- > 0;) {
                                                var action = W.model.actionManager.actions[i];
                                                if (action.model.hasOwnProperty('segments') && action.subActions[0].attributes.id == si[0].model.attributes.id && action.subActions[0].newAttributes.hasOwnProperty('restrictions')) {
                                                    src = action.subActions[0].newAttributes.restrictions;
                                                    break;
                                                }
                                            }

                                            var restrictions = [];
                                            for (i = 0;  i< src.length; i++) {
                                                var restriction = src[i];
                                                if (restriction._direction == dir) {
                                                    restrictions.push(restriction);
                                                }
                                            }

                                            restrictions = JSON.stringify(restrictions);

                                            localStorage.setItem(lsPrefix + name, restrictions);
                                            updateSavedRestrictionSelectors(restrictionsModal);
                                        }
                                    });
                                } else {
$("div.rtmgr button.save", restrictionsModal).click(function(evt) {
clearMessages();
addMessage("Save is only enabled when displaying the restrictions for a SINGLE segment");
});
                                }

                                // Apply saved restrictions to the current segment
                                $("div.rtmgr button.apply", restrictionsModal).click(function(evt) {
                                    var tgt = $(evt.target);
                                    var name = tgt.parents('div.rtmgr').find("input").val();
                                    if (name != "") {
                                        var restrictions = localStorage.getItem(lsPrefix + name);
                                        restrictions = JSON.parse(restrictions);

                                        var rsg = $(evt.target).parents("div.restriction-summary-group").first();
                                        var classes = rsg.attr('class').split(' ');
                                        classes.splice(classes.indexOf('restriction-summary-group'), 1);

                                        // Delete all current restrictions associated with the action's direction
                                        while (true) {
                                            var doDelete = "." + classes[0] + " .restriction-editing-actions i.do-delete";
                                            var deleteRestrictions = $(doDelete, restrictionsModal);

                                            if (deleteRestrictions.length == 0) {
                                                break;
                                            }

                                            deleteRestrictions.eq(0).click();
                                        }

                                        // Create new restrictions
                                        while (restrictions.length) {
                                            var restriction = restrictions.shift();

                                            $("." + classes[0] + " button.do-create", restrictionsModal).click();

                                            setSelector('disposition', restrictionsModal, restriction.disposition);
                                            setSelector('laneType', restrictionsModal, restriction.laneType);
                                            setValue('textarea[name="description"]', restrictionsModal, restriction.description);

                                            if (restriction.timeFrames != null && restriction.timeFrames.length != 0) {
                                                var weekdays = restriction.timeFrames[0].weekdays;

                                                var bit = 1;
                                                for(var idx = 0; idx < 7; idx++) {
                                                    var set = weekdays & bit;
                                                    set = (set != 0);
                                                    setCheck('input#day-ordinal-' + weekdayBit2Idx[bit] + '-checkbox', restrictionsModal, set);
                                                    bit <<= 1;
                                                }

                                                if (restriction.timeFrames[0].fromTime && restriction.timeFrames[0].toTime) {
                                                    setCheck("input#is-all-day-checkbox", restrictionsModal, false);
                                                    setValue("input.timepicker-from-time", restrictionsModal, restriction.timeFrames[0].fromTime);
                                                    setValue("input.timepicker-to-time", restrictionsModal, restriction.timeFrames[0].toTime);
                                                }

                                                if (restriction.timeFrames[0].startDate && restriction.timeFrames[0].endDate) {
                                                    setCheck("input#is-during-dates-on-radio", restrictionsModal, true);

                                                    // Ref: http://www.daterangepicker.com/
                                                    var drp = $('input.btn.datepicker', restrictionsModal).data('daterangepicker');

                                                    var re = /(\d{4})-(\d{2})-(\d{2})/;
                                                    var match = re.exec(restriction.timeFrames[0].startDate);
                                                    var startDate = match[2] + "/" + match[3] + "/" + match[1];

                                                    match = re.exec(restriction.timeFrames[0].endDate);
                                                    var endDate = match[2] + "/" + match[3] + "/" + match[1];

                                                    // WME's callback is fired by drp.hide().
                                                    drp.show();
                                                    drp.setStartDate(startDate);
                                                    drp.setEndDate(endDate);
                                                    drp.hide();
                                                }
                                            }

                                            var drivingModality;
                                            // if ALL vehicles are blocked then the default type is simply BLOCKED and the modality is blocked.
                                            if ("BLOCKED" == restriction.defaultType && !restriction.driveProfiles.hasOwnProperty("FREE") && !restriction.driveProfiles.hasOwnProperty("BLOCKED")) {
                                                drivingModality = "DRIVING_BLOCKED";
                                            } else {
                                                drivingModality = defaultType2drivingModality[restriction.defaultType];
                                            }

                                            setValue("select.do-change-driving-modality", restrictionsModal, drivingModality);

                                            var driveProfiles, driveProfile, i, j, vehicleType;
                                            if (restriction.driveProfiles.hasOwnProperty("FREE")) {
                                                driveProfiles = restriction.driveProfiles.FREE;
                                                for(i = 0; i < driveProfiles.length; i++) {
                                                    driveProfile = driveProfiles[i];

                                                    $("div.add-drive-profile-item.do-add-item", restrictionsModal).click();

                                                    for(j = 0; j < driveProfile.vehicleTypes.length; j++) {
                                                        vehicleType = driveProfile.vehicleTypes[j];

                                                        var plus = lastFaPlus(restrictionsModal);
                                                        plus.click();
                                                        var driveProfileItem = plus.parents("div.drive-profile-item");
                                                        $("div.btn-group.open a.do-init-vehicle-type", dpi).click();

                                                        $("div.vehicle-type span.restriction-chip-content", driveProfileItem).click();

                                                        $('a.do-set-vehicle-type[data-value="' + vehicleType + '"]', driveProfileItem).click();
                                                    }

                                                    if (driveProfile.numPassengers > 0) {
                                                        var plus2 = lastFaPlus(restrictionsModal);
                                                        plus2.click();
                                                        var dpi = plus2.parents("div.drive-profile-item");
                                                        $("div.btn-group.open a.do-init-num-passengers", dpi).click();
                                                        if (driveProfile.numPassengers > 2) {
                                                            $("a.do-set-num-passengers[data-value='" + driveProfile.numPassengers + "']").click();
                                                        }
                                                    }

                                                    for(var k = 0; k < driveProfile.subscriptions.length; k++) {
                                                        var subscription = driveProfile.subscriptions[k];

                                                        var plus3 = lastFaPlus(restrictionsModal);
                                                        plus3.click();
                                                        var driveProfileItem2 = plus3.parents("div.drive-profile-item");
                                                        $("div.btn-group.open a.do-init-subscription", driveProfileItem2).click();


                                                        $("div.subscription span.restriction-chip-content", driveProfileItem2).click();

                                                        $('a.do-set-subscription[data-value="' + subscription + '"]', driveProfileItem2).click();
                                                    }
                                                }
                                            } else if (restriction.driveProfiles.hasOwnProperty("BLOCKED")) {
                                                driveProfiles = restriction.driveProfiles.BLOCKED;

                                                for(i = 0; i < driveProfiles.length; i++) {
                                                    driveProfile = driveProfiles[i];

                                                    if (driveProfile.vehicleTypes.length > 0) {
                                                        setCheck('input#all-vehicles-off-radio', restrictionsModal, true);

                                                        for(j = 0; j < driveProfile.vehicleTypes.length; j++) {
                                                            vehicleType = driveProfile.vehicleTypes[j];

                                                            setCheck('input#vehicle-type-' + vehicleType + '-checkbox', restrictionsModal, true);
                                                        }
                                                    }
                                                }
                                            }

                                            $("div.modal-footer button.do-create", restrictionsModal).click();
                                        }
                                    }
                                });
                            }
                        }
                    }
                }
            });
        });

        // configuration of the observer:
        var config = { attributes: false, childList: true, characterData: false, subtree: true };

        // pass in the target node, as well as the observer options
        observer.observe(observerTarget, config);

        initialized = true;
    }

     setTimeout(initializeRestrictionManager, 1000);
 })();

QingJ © 2025

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