您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Save, and load, restrictions from local storage.
当前为
/* global $ */ // ==UserScript== // @name Restriction Manager // @version 0.4 // @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 }; var clipboardTarget = "*Clipboard*"; // 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>" + 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>'); } var timeRegexp = /(\d\d?):(\d\d?)/ function time2Int(time) { var m = timeRegexp.exec(time); return m == null ? 0 : (m[1] * 60) + m[2]; } function compareTimeFrames(a, b) { if (a == null) { return (b == null ? 0 : -1); } else if (b == null) { return 1; } else { a = a[0]; b = b[0]; var c = time2Int(a.fromTime) - time2Int(b.fromTime); if (c == 0) { c = time2Int(a.toTime) - time2Int(b.toTime); } return c; } } function compareRestrictions(a, b) { var c = compareTimeFrames(a.timeFrames, b.timeFrames); if (c == 0) { c = a.defaultType.localeCompare(b); } return c; } 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 input = tgt.parents('div.rtmgr').find("input"); var name = 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('subActions') && 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); if (clipboardTarget == name) { input.val(restrictions).select(); document.execCommand('copy'); input.val(clipboardTarget).blur(); } else { 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 input = tgt.parents('div.rtmgr').find("input"); var name = input.val().trim(); if (name != "") { var restrictions; if (name.startsWith('[') & name.endsWith(']')) { restrictions = name; } else { restrictions = localStorage.getItem(lsPrefix + name); } restrictions = JSON.parse(restrictions).sort(compareRestrictions); 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, plus, driveProfileItem, subscription; 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]; plus = lastFaPlus(restrictionsModal); plus.click(); driveProfileItem = plus.parents("div.drive-profile-item"); $("div.btn-group.open a.do-init-vehicle-type", driveProfileItem).click(); $("div.vehicle-type span.restriction-chip-content", driveProfileItem).click(); $('a.do-set-vehicle-type[data-value="' + vehicleType + '"]', driveProfileItem).click(); } if (driveProfile.numPassengers > 0) { plus = lastFaPlus(restrictionsModal); plus.click(); driveProfileItem = plus.parents("div.drive-profile-item"); $("div.btn-group.open a.do-init-num-passengers", driveProfileItem).click(); if (driveProfile.numPassengers > 2) { $("a.do-set-num-passengers[data-value='" + driveProfile.numPassengers + "']").click(); } } for(var k = 0; k < driveProfile.subscriptions.length; k++) { subscription = driveProfile.subscriptions[k]; plus = lastFaPlus(restrictionsModal); plus.click(); driveProfileItem = plus.parents("div.drive-profile-item"); $("div.btn-group.open a.do-init-subscription", driveProfileItem).click(); $("div.subscription span.restriction-chip-content", driveProfileItem).click(); $('a.do-set-subscription[data-value="' + subscription + '"]', driveProfileItem).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); })();