Restriction Manager

Save, and load, restrictions from local storage.

目前为 2019-06-10 提交的版本,查看 最新版本

  1. /* global $ */
  2.  
  3. // ==UserScript==
  4. // @name Restriction Manager
  5. // @version 1.2
  6. // @description Save, and load, restrictions from local storage.
  7. // @namespace mailto:waze.kjg53@gmail.com
  8. // @include https://www.waze.com/editor
  9. // @include https://www.waze.com/editor*
  10. // @include https://www.waze.com/*/editor*
  11. // @include https://beta.waze.com/*
  12. // @exclude https://www.waze.com/user/*
  13. // @exclude https://www.waze.com/*/user/*
  14. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAjCAIAAABzZz93AAAABnRSTlMA/wD/AP83WBt9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABV0lEQVRIiWP8//8/A70ACxl6vm6JgjC4fZaRpJGRVJ/BbYID4q1kotAmkgBpllHoAhIso9BbJFiG3yYi3UFyarx39rRewx0IO6nBZ5IxL/F6ifIZisOffoAzrzzFrub/v7/kW0YqYGRiJtMy4tMFQZUELKM8BZJgGSZQkhbAI4vfcfgsI+gtHWksSRGPLpokEJItoyS2cOnFbhl10wUByygHWJ2LxTIC3pLiMyPXBeiWkRKAIupS+KQxjaJtakSzjwmPHA7AqwNlCKiSaDdKG4RGiRDeSEH4jEY2IQMmkm16fjOvdDmP73Ie391TnxOlA244yQnk3ulH825AmG/K1z0jSS8TA4kBeOfxG5IsgACIFST7TEVWhAzLIID0+sxULkkDwhTpDMKbqzEA4////+mQDhkYGLh9ljExkN4/IM8mhoGpPGnqObjh6F0m6sYfmh9I7p9RAgAFyXyizju5WQAAAABJRU5ErkJggg==
  15. // @resource jqUI_CSS https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css
  16. // @grant none
  17. // @copyright 2018, kjg53
  18. // @author kjg53
  19. // @license MIT
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. var initialized = false;
  24. var lsPrefix = "rtmgr:";
  25.  
  26. // Map the css class used to identify the three restriction blocks to the direction constants used in the data.
  27. var classToDirection={"forward-restrictions-summary": "FWD",
  28. "reverse-restrictions-summary": "REV",
  29. "bidi-restrictions-summary": "BOTH"};
  30.  
  31. // Convert the segment's default type to the driving modality that implied the type. Creating a Toll Free restriction
  32. // implies that the segment is otherwise (i.e. defaults) to tolled which is what is then stored in the model.
  33. // Finding the tolled default thereby implies that the current restriction is specifying a toll free rule.
  34. var defaultType2drivingModality = {"TOLL": "DRIVING_TOLL_FREE",
  35. "FREE":"DRIVING_BLOCKED",
  36. "BLOCKED":"DRIVING_ALLOWED"};
  37.  
  38. // Map the single bit constants used in the weekdays property to the integer numbers encoded in each week days HTML display.
  39. var weekdayBit2Idx = {
  40. 1:1,
  41. 2:2,
  42. 4:3,
  43. 8:4,
  44. 16:5,
  45. 32:6,
  46. 64:0
  47. };
  48.  
  49. var clipboardTarget = "*Clipboard*";
  50.  
  51. // Get a sorted list of saved restrictions found in local storage.
  52. function allSavedRestrictions() {
  53. var all = [];
  54. for(var i = 0; i < localStorage.length; i++) {
  55. var key = localStorage.key(i);
  56.  
  57. if (key.indexOf(lsPrefix) == 0) {
  58. key = key.substring(lsPrefix.length);
  59.  
  60. all.push(key);
  61. }
  62. }
  63. all.sort();
  64.  
  65. return all;
  66. }
  67.  
  68. function extractRestrictions() {
  69. var extracted = {};
  70. for(var i = 0; i < localStorage.length; i++) {
  71. var key = localStorage.key(i);
  72.  
  73. if (key.indexOf(lsPrefix) == 0) {
  74. var name = key.substring(lsPrefix.length);
  75.  
  76. extracted[name] = localStorage.getItem(key);
  77. }
  78. }
  79.  
  80. return extracted;
  81. }
  82.  
  83. // Convert list of saved restrictions into a string of HTML option elements.
  84. function allSavedRestrictionAsOptions() {
  85. var all = allSavedRestrictions();
  86. return all.length == 0 ? "" : "<option selected></option><option>" + all.join("</option><option>") + "</option>";
  87. }
  88.  
  89. // Update all restriction selectors to display the saved restrictions returned by allSavedRestrictions
  90. function updateSavedRestrictionSelectors(root) {
  91. $("div.rtmgr div.name select", root).html(allSavedRestrictionAsOptions()).each(resizeDivName);
  92. }
  93.  
  94. // The content of the div.name element are positioned relative to its location. As a result, the
  95. // div normally collapses to a point in the screen layout. This function expands the div to enclose
  96. // its contents such that other elements are laid out around them.
  97. function resizeDivName(idx, child) {
  98. var div = $(child).parents("div.name").first();
  99. var height = 0;
  100. var width = 0;
  101.  
  102. div.children().each(function(idx, child) {
  103. child = $(child);
  104. height = Math.max(height, child.height());
  105. width = Math.max(width, child.width());
  106. });
  107.  
  108. div.width(width).height(height);
  109. }
  110.  
  111.  
  112. // Identify the direction of the restrictions associated with the specified button.
  113. function direction(btn) {
  114. var classes = btn.parents("div.restriction-summary-group").attr('class').split(' ');
  115.  
  116. while(classes.length) {
  117. var cls = classes.pop();
  118. var dir = classToDirection[cls];
  119.  
  120. if (dir) {
  121. return dir;
  122. }
  123. }
  124. }
  125.  
  126.  
  127. function setValue(selector, model, value) {
  128. if (value != null) {
  129. var sel = $(selector, model);
  130. var oldValue = sel.val();
  131. if (oldValue != value) {
  132. sel.val(value);
  133. sel.change();
  134. }
  135. }
  136. }
  137.  
  138. function setCheck(selector, model, value) {
  139. if (value != null) {
  140. value = !!value;
  141. var sel = $(selector, model);
  142. var oldValue = sel.prop('checked');
  143. if (oldValue != value) {
  144. sel.prop('checked', value);
  145. sel.change();
  146. }
  147. }
  148. }
  149.  
  150. function setSelector(name, model, value) {
  151. setValue('select[name="' + name + '"]', model, value);
  152. }
  153.  
  154. function lastFaPlus(modal) {
  155. return $("i.fa-plus", modal).last();
  156. }
  157.  
  158. function clearMessages() {
  159. $("div.modal-header-messages div.rtmgr").remove();
  160. }
  161. function addMessage(text, icon, color) {
  162. var rvr = $("div.modal-header-messages");
  163. if (icon) {
  164. icon = '<i class="fa fa-' + icon + '"/> ';
  165. } else {
  166. icon = "";
  167. }
  168. if (color) {
  169. color = ' style="color: ' + color + '"';
  170. } else {
  171. color = "";
  172. }
  173. rvr.append('<div class="modal-header-message"' + color + '>' + icon + text + '</div>');
  174. }
  175.  
  176. var timeRegexp = /(\d\d?):(\d\d?)/
  177.  
  178. function time2Int(time, ifNull) {
  179. var m = timeRegexp.exec(time);
  180. return m == null ? -1 : (m[1] * 60) + m[2];
  181. }
  182.  
  183. function compareTimeFrames(a, b) {
  184. if (a == null) {
  185. return (b == null ? 0 : -1);
  186. } else if (b == null) {
  187. return 1;
  188. } else {
  189. a = a[0];
  190. b = b[0];
  191.  
  192. var c = time2Int(a.fromTime, -1) - time2Int(b.fromTime, -1);
  193. if (c == 0) {
  194. c = time2Int(a.toTime, 1440) - time2Int(b.toTime, 1440);
  195. }
  196.  
  197. return c;
  198. }
  199. }
  200.  
  201. function compareRestrictions(a, b) {
  202. var c = compareTimeFrames(a.timeFrames, b.timeFrames);
  203. if (c == 0) {
  204. c = a.defaultType.localeCompare(b.defaultType);
  205. }
  206. return c;
  207. }
  208.  
  209. function handleImportDragOver(evt) {
  210. evt.stopPropagation();
  211. evt.preventDefault();
  212. evt.dataTransfer.dropEffect = "copy";
  213. }
  214.  
  215. function handleImportFile(file) {
  216. var reader = new FileReader();
  217.  
  218. reader.onload = function(e) {
  219. var restrictions = JSON.parse(e.target.result);
  220.  
  221. for(var restriction in restrictions) {
  222. localStorage.setItem(lsPrefix + restriction, restrictions[restriction]);
  223. }
  224. };
  225.  
  226. reader.readAsText(file);
  227. }
  228. function handleImportDrop(evt) {
  229. evt.stopPropagation();
  230. evt.preventDefault();
  231.  
  232. var i, f, item, seen = {};
  233. for(i = 0; item = evt.dataTransfer.items[i]; i++) {
  234. if (item.kind === 'file') {
  235. f = item.getAsFile();
  236. handleImportFile(f);
  237. seen[f.name] = true;
  238. }
  239. }
  240. for(i = 0; f = evt.dataTransfer.files[i]; i++) {
  241. if (seen[f.name] !== true) {
  242. handleImportFile(f);
  243. }
  244. }
  245. }
  246.  
  247. function initializeRestrictionManager() {
  248. if (initialized) {
  249. return;
  250. }
  251.  
  252. var observerTarget = document.getElementById("dialog-region");
  253.  
  254. if (!observerTarget) {
  255. window.console.log("Restriction Manager: waiting for WME...");
  256. setTimeout(initializeRestrictionManager, 1015);
  257. }
  258.  
  259. // Inject my stylesheet into the head
  260. var sheet = $('head').append('<style type="text/css"/>').children('style').last();
  261. sheet.append('div.rtmgr-column {display: flex; flex-direction: column}');
  262. sheet.append('div.rtmgr-row {display: flex; flex-direction: row; justify-content: space-around}');
  263. sheet.append('div.rtmgr button.btn {margin-top: 5px; border-radius: 40%}');
  264. sheet.append('div.rtmgr div.name input {width: 250px; position: absolute; left: 0px; top: 0px; z-index: 1}');
  265. sheet.append('div.rtmgr div.name select {width: 275px; position: absolute; left: 0px; top: 0px}');
  266. sheet.append('div.rtmgr div.name {width: 275px; position: relative; left: 0px; top: 0px}');
  267. sheet.append('h3.modal-title img.icon {float:right;height:20px;width:20px}');
  268. sheet.append('dialog.rtmgr span.cmd {text-decoration: underline}');
  269. sheet.append('dialog.rtmgr .import, dialog.rtmgr .export {width:30px; height: 30px}');
  270.  
  271. // create an observer instance
  272. var observer = new MutationObserver(function(mutations) {
  273. var si = W.selectionManager.getSelectedFeatures();
  274.  
  275. mutations.forEach(function(mutation) {
  276. if("childList" == mutation.type && mutation.addedNodes.length) {
  277. var restrictionsModal = $("div.modal-dialog.restrictions-modal", observerTarget);
  278.  
  279. if (restrictionsModal) {
  280. var modalTitle = $(restrictionsModal).find("h3.modal-title").first();
  281. var title = modalTitle.text();
  282.  
  283. if (I18n.translations[I18n.locale].restrictions.modal_headers.restriction_summary == title) {
  284. if (modalTitle.data('rtmgr') === undefined) {
  285. // Flag this modal as having already augmented
  286. modalTitle.data('rtmgr', true);
  287. modalTitle.append("<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAjCAIAAABzZz93AAAABnRSTlMA/wD/AP83WBt9AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABV0lEQVRIiWP8//8/A70ACxl6vm6JgjC4fZaRpJGRVJ/BbYID4q1kotAmkgBpllHoAhIso9BbJFiG3yYi3UFyarx39rRewx0IO6nBZ5IxL/F6ifIZisOffoAzrzzFrub/v7/kW0YqYGRiJtMy4tMFQZUELKM8BZJgGSZQkhbAI4vfcfgsI+gtHWksSRGPLpokEJItoyS2cOnFbhl10wUByygHWJ2LxTIC3pLiMyPXBeiWkRKAIupS+KQxjaJtakSzjwmPHA7AqwNlCKiSaDdKG4RGiRDeSEH4jEY2IQMmkm16fjOvdDmP73Ie391TnxOlA244yQnk3ulH825AmG/K1z0jSS8TA4kBeOfxG5IsgACIFST7TEVWhAzLIID0+sxULkkDwhTpDMKbqzEA4////+mQDhkYGLh9ljExkN4/IM8mhoGpPGnqObjh6F0m6sYfmh9I7p9RAgAFyXyizju5WQAAAABJRU5ErkJggg==' class='icon'>"
  288. + "<dialog class='rtmgr'>"
  289. + "<p style='text-align: center; font-weight: bold; font-size: 1.3em'>Restriction Manager</p>"
  290. + "<p style='font-style: italic; font-size: .7em'>Stores restrictions in your browser's local storage so that they may be easily applied to other segments.</p>"
  291. + "<p style='padding-left: 3em; text-indent: -3em;'>"
  292. + "<span class='cmd'>Save</span>: Saves these restrictions to the selected key.<br>"
  293. + "<span style='font-style: italic; font-size: .7em'>Note: Newly edited restrictions must be applied to the segment before the manager can save them.</span>"
  294. + "</p>"
  295. + "<p>"
  296. + "<span class='cmd'>Apply</span>: Replaces the current restrictions with the restrictions associated with the selected key.</p>"
  297. + "<p>"
  298. + "<span class='cmd'>Delete</span>: Delete the selected key from your browser's local storage.</p>"
  299. + "</dialog>");
  300. if (window.File && window.FileReader && window.FileList && window.Blob) {
  301. $('dialog.rtmgr', modalTitle)
  302. .append("<p>Offline Storage: "
  303. + "<a download='restrictions.txt'><img title='Click here to export all saved restrictions' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAABBVJREFUeJzt3btqVFEUh/FPDXiLJIhooYENKUQrqxQitrYiGgufQfENBBG08AnEWkGwsUojFhbeQFBUsBNBRBGJsZBg4lgMBzRkkrPO7NvM+v/gdJl9lrM+MqOFB/zZB9wA3gO/gSXgITBfcijJYw74AvQGXPeB7cWmk6QOAt8YvPzmul1qQEnrFpsvv7mOFZpREtkKLNI+gOtlxsxva+kBMjkATBl+/kiqQWrjJQDrF7sdSaaokJcAZAAF4JwCcE4BOKcAnFMAzikA5xSAcwrAOQXgnAJwTgE4pwCcUwDOKQDnFIBzCsA5BeCcAnBOATinAJxTAM4pAOcUgHMKwDkF4JwCcE4BOKcAnFMAzikA5xSAcwrAOQXgnAJwTgE4pwCcUwDOKQDnFIBzCsA5BeCcAnBOATg3kfj8WWAG+Am8AZYT328c7ALOAseBSeAj8AB4WnIoqzPAO/7/P/iXgJvA7gLzBNo/K6AHLBSYEeAU8HnATA+AvYXmMrnKxm/ua2B/5pnCJjPVEMA5+g+x2miul8DOArO1dpp2b/Bb8kYQWs5VKoA2y2+ua5lnM3lF+zc5ZwTBMFfuACzL79F/8FWVX94PYXuTc0YQjHPlCsC6/OY6HGuAmCXNdHjNUeAR+b8T1OAccIdufxObjDVEzAB+dHydxwiGWT7Ah3ijxLMN+Ir911mOj4NgnCXlR0DXX/vN9TjhbEO7Qvc/WMoIgnGOVAHMM9zyV4GTiWaLYjvwhPoiCMYZUgQw7PJ7wMUEc0U3DbygrgiC8f6xA4ix/MuRZ0qqtgiC8d4xA3C3/EZNEQTjfWMF4Hb5jVoiCMZ7xgjA/fIbNUQQjPcbNgAtf43SEQTjvYYJQMsfoGQEwXifrgHMAyvGe7lYfqNUBMF4jy4BaPktlYggGM+3BqDlG00Dz8kXQTCebQlAy+8oZwTBeG7bALT8IeWKIBjPbBOAlh9JjgiC8bzNAtDyI0sdQTCetVEA59Hyk0gZQTCeMygALT+xVBEE4xnrBaDlZ5IigmB8/doAtPzMYkcQjK/9NwAtv5CYEQTj65oAtPzCpogTwZzxNQto+dWIEcEn489/QcuvSowIcl5afgKjEoGWn1DtEWj5GdQagZafUW0RaPkF1BKBll9Q6Qi0/AqUikDLr8gU8Awt37VcEWj5FUsdgZY/AlJFoOWPkNgRaPkjKFYEWv4IGzYCLX8MdI1Ayx8j1gi0/DE0Rf+hCxst/g9wqdSAkt4e4B7rL38RuFButDK2lB6gkDn6TzWZBX7R/3i4C3wvOZSIiIiIiIiIiIhIGjH/KfgElT/RakwsAztiHVblI0glHwXgnAJwTgE4pwCcUwDOKQDnFIBzCsA5BeCcAnBOATinAJybiHhWD1iNeJ6sbyXmYX8BhFf7gV3QQHIAAAAASUVORK5CYII=' class='export' download='restrictions.txt'></a>"
  304. + "<img title='Drag file here to import restrictions' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAA61JREFUeJzt3U+LjWEcxvHvDEJKhIVslNlYSZGsiKJmUPZ2lpaaF8DGglfA5uQNKOIVyChRLCkLf2qyUEaTGSkWx6TJzJnzPM9937/7nOv61NmdGb9xfZmaOmcgrSngtx9ZH1eGXmMIkyk/mY0eByDOAYhzAOIcgDgHIM4BiHMA4hyAOAcgzgGIcwDiHIC4zYk/3yLwOPHntNU+RR9gZmZmZmZmZmYjaFP0AYEmgIPAfvo/wfwVeo0Vsx24Cczz78UWP4EHwOHAu6yAvcAr1n/VzSIwHXadZbUPeMPGL71aAmaCbrRMhh3fEYyhpuM7gjHSdnxHMAa6ju8IRliq8R3BCEo9viMYIbnGdwQjIPf4jqBipcZfeSzjCKpRenxHUJGo8R1BBaLHdwSBahnfEQSobXxHUFCt4zuCAmof3xFklGL8hczPdwSZpBj/NtBr+DEn6R7BhfR/HVpSjQ/NAwBHECrl+NAuAHAEIVKPD+0DAEdQVI7xoVsA4AiKyDU+dA8AHEFWOceHNAGAI8gi9/iQLgBwBEmVGB/SBgCOIIlS40P6AMARdFJyfMgTAKSJ4GKDP28slB4f8gUAjqCRiPEhbwDgCIYSNT7kDwAcwUCR40OZAMARrCl6fCgXADiCVWoYH8oGAI4AqGd8KB8AiEdQ0/gQEwCIRlDb+BAXAIhFsBN4TV3jQ2wA0D2CJeBYhruSu0t940N8ANA9grlMdyWzh/67btY2PtQRAHSPYCrjbZ3NUOf4UE8A0C2CyykPSf17A3e3/Lg7wGzKQyo3B5wHvrf42NxxdnKa+v7lr+g1vKuENv8THCp0Wytbga/UNz7UGQA0i+Bpwbtam6W+8aHeAGC4CH4ARwvf1cokcJ/BX8ytgLt6G9wUGQDAceDDOrd8Ac4G3NTJVeAtq7+Ql8CloHt61B0AwA7gGvAQeAE8Aa4Du4LuSeIAcIT+j4cj9ag/gOJS//bwtXz++7AKpf45gI0YByDOAYhzAOIcgDgHIM4BiHMA4hyAOAcgzgGIcwDiHIA4ByDOAYhzAOIcgDgHIM4BiHMA4hyAOAcgzgGIcwDiHIA4ByDOAYhzAOIcgDgHIM4BiHMA4hyAOAcgzgGIcwDiHIA4ByDOAYhzAOIcgDgHIE4pAJn3/21CKYCFBs/9lu2KyigF8LzBc59lu8LCbAM+MtxbxU8H3WiZnaH/a1gHjX8v7Dor4hTwnv+HXwZuoPVtkYnoA4JsAc4BJ+h/a3gHPALmI48yMyvrD75JO5BlUd2tAAAAAElFTkSuQmCC' class='import'>"
  305. + "</p>");
  306. }
  307. $('dialog.rtmgr', modalTitle).append("<p style='text-align: right; font-style: italic; font-size: .5em'>Click anywhere to close.</p>");
  308. $("img.icon", modalTitle).click(function(evt) {
  309. $("dialog.rtmgr", modalTitle)[0].showModal();
  310. });
  311. $("dialog.rtmgr", modalTitle).click(function(evt) {
  312. evt.currentTarget.close();
  313. });
  314. if (window.File && window.FileReader && window.FileList && window.Blob) {
  315. $("img.export", modalTitle).click(function(evt) {
  316. var data = JSON.stringify(extractRestrictions());
  317. evt.target.parentNode.href = "data:text/plain;base64," + btoa(data);
  318. });
  319.  
  320. var imgImport = $("img.import", modalTitle)[0];
  321. imgImport.addEventListener('dragover', handleImportDragOver, false);
  322. imgImport.addEventListener('drop', handleImportDrop, false);
  323. }
  324.  
  325. // Add the UI elements to the modal
  326. $("div.restriction-summary-group div.restriction-summary-title", restrictionsModal)
  327. .append (
  328. ""
  329. + "<div class='rtmgr rtmgr-column'>"
  330. + "<div class='name'>"
  331. + "<input type='text'/>"
  332. + "<select/>"
  333. + "</div>"
  334. + "<div class='rtmgr-row'>"
  335. + "<button class='btn save'>Save</button>"
  336. + "<button class='btn apply'>Apply</button>"
  337. + "<button class='btn delete'>Delete</button>"
  338. + "</div>"
  339. + "</div>");
  340.  
  341. // Initialize the saved restriction selectors
  342. updateSavedRestrictionSelectors(restrictionsModal);
  343.  
  344. // When a selection is made copy it to the overlapping input element to make it visible.
  345. $("div.rtmgr select").change(function(evt) {
  346. var tgt = evt.target;
  347. var txt = $(tgt).parent().children("input");
  348. var opt = tgt.options[tgt.selectedIndex];
  349. txt.val(opt.text);
  350. $(opt).prop('selected', false);
  351. $(opt).parent().children("option:first").prop("selected", "selected");
  352. });
  353.  
  354. // Delete action
  355. $("div.rtmgr button.delete", restrictionsModal).click(function(evt) {
  356. var tgt = $(evt.target);
  357. var inp = tgt.parents('div.rtmgr').find("input");
  358. var name = inp.val();
  359. if (name == "") {
  360. addMessage("Specify the name of the restrictions being deleted.", 'ban', 'red');
  361. } else {
  362. localStorage.removeItem(lsPrefix + name);
  363. updateSavedRestrictionSelectors(restrictionsModal);
  364. inp.val("");
  365. }
  366. });
  367.  
  368. // Save action (only one segment currently selected)
  369. if (si.length == 1) {
  370. $("div.rtmgr button.save", restrictionsModal).click(function(evt) {
  371. var tgt = $(evt.target);
  372. var input = tgt.parents('div.rtmgr').find("input");
  373. var name = input.val();
  374. if (name == "") {
  375. addMessage("The restrictions require a name before they can be saved.", 'ban', 'red');
  376. } else {
  377. var dir = direction(tgt);
  378. var attrs = si[0].model.getAttributes();
  379. var src = attrs.restrictions;
  380.  
  381. // Checking for pending updates to the selected segment's restrictions. If found, save a copy of them.
  382. // 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.
  383. var actions = W.model.actionManager.getActions();
  384. for(var i = actions.length; i-- > 0;) {
  385. var action = actions[i];
  386. if (action.model.hasOwnProperty('subActions') && action.subActions[0].attributes.id == si[0].model.attributes.id && action.subActions[0].newAttributes.hasOwnProperty('restrictions')) {
  387. src = action.subActions[0].newAttributes.restrictions;
  388. break;
  389. }
  390. }
  391.  
  392. var restrictions = [];
  393. for (i = 0; i< src.length; i++) {
  394. var restriction = src[i];
  395. if (restriction._direction == dir) {
  396. restrictions.push(restriction);
  397. }
  398. }
  399.  
  400. restrictions = JSON.stringify(restrictions);
  401. clearMessages();
  402. if (clipboardTarget == name) {
  403. input.val(restrictions).select();
  404. document.execCommand('copy');
  405. input.val(clipboardTarget).blur();
  406. addMessage("Restrictions copied to clipboard");
  407. } else {
  408. localStorage.setItem(lsPrefix + name, restrictions);
  409. addMessage("Restrictions saved to " + name);
  410. }
  411. input.val("");
  412.  
  413. updateSavedRestrictionSelectors(restrictionsModal);
  414. }
  415. });
  416. } else {
  417. $("div.rtmgr button.save", restrictionsModal).click(function(evt) {
  418. clearMessages();
  419. addMessage("Save is only enabled when displaying the restrictions for a SINGLE segment", 'ban', 'red');
  420. });
  421. }
  422.  
  423. // Apply saved restrictions to the current segment
  424. $("div.rtmgr button.apply", restrictionsModal).click(function(evt) {
  425. var tgt = $(evt.target);
  426. var input = tgt.parents('div.rtmgr').find("input");
  427. var name = input.val().trim();
  428. if (name == "") {
  429. addMessage("Specify the name of the restrictions being applied.", 'ban', 'red');
  430. } else {
  431. var restrictions;
  432.  
  433. input.val("");
  434.  
  435. if (name.startsWith('[') & name.endsWith(']')) {
  436. restrictions = name;
  437. } else {
  438. restrictions = localStorage.getItem(lsPrefix + name);
  439. }
  440. restrictions = JSON.parse(restrictions).sort(compareRestrictions);
  441.  
  442. var rsg = $(evt.target).parents("div.restriction-summary-group").first();
  443. var classes = rsg.attr('class').split(' ');
  444. classes.splice(classes.indexOf('restriction-summary-group'), 1);
  445.  
  446. // Delete all current restrictions associated with the action's direction
  447. while (true) {
  448. var doDelete = "." + classes[0] + " .restriction-editing-actions i.do-delete";
  449. var deleteRestrictions = $(doDelete, restrictionsModal);
  450.  
  451. if (deleteRestrictions.length == 0) {
  452. break;
  453. }
  454.  
  455. deleteRestrictions.eq(0).click();
  456. }
  457.  
  458. // Create new restrictions
  459. while (restrictions.length) {
  460. var restriction = restrictions.shift();
  461.  
  462. $("." + classes[0] + " button.do-create", restrictionsModal).click();
  463.  
  464. setSelector('disposition', restrictionsModal, restriction.disposition);
  465. setSelector('laneType', restrictionsModal, restriction.laneType);
  466. setValue('textarea[name="description"]', restrictionsModal, restriction.description);
  467.  
  468. if (restriction.timeFrames != null && restriction.timeFrames.length != 0) {
  469. var weekdays = restriction.timeFrames[0].weekdays;
  470.  
  471. var bit = 1;
  472. for(var idx = 0; idx < 7; idx++) {
  473. var set = weekdays & bit;
  474. set = (set != 0);
  475. setCheck('input#day-ordinal-' + weekdayBit2Idx[bit] + '-checkbox', restrictionsModal, set);
  476. bit <<= 1;
  477. }
  478.  
  479. if (restriction.timeFrames[0].fromTime && restriction.timeFrames[0].toTime) {
  480. setCheck("input#is-all-day-checkbox", restrictionsModal, false);
  481. setValue("input.timepicker-from-time", restrictionsModal, restriction.timeFrames[0].fromTime);
  482. setValue("input.timepicker-to-time", restrictionsModal, restriction.timeFrames[0].toTime);
  483. }
  484.  
  485. if (restriction.timeFrames[0].startDate && restriction.timeFrames[0].endDate) {
  486. setCheck("input#is-during-dates-on-radio", restrictionsModal, true);
  487.  
  488. // Ref: http://www.daterangepicker.com/
  489. var drp = $('input.btn.datepicker', restrictionsModal).data('daterangepicker');
  490.  
  491. var re = /(\d{4})-(\d{2})-(\d{2})/;
  492. var match = re.exec(restriction.timeFrames[0].startDate);
  493. var startDate = match[2] + "/" + match[3] + "/" + match[1];
  494.  
  495. match = re.exec(restriction.timeFrames[0].endDate);
  496. var endDate = match[2] + "/" + match[3] + "/" + match[1];
  497.  
  498. // WME's callback is fired by drp.hide().
  499. drp.show();
  500. drp.setStartDate(startDate);
  501. drp.setEndDate(endDate);
  502. drp.hide();
  503. }
  504. }
  505.  
  506. var drivingModality;
  507. // if ALL vehicles are blocked then the default type is simply BLOCKED and the modality is blocked.
  508. if ("BLOCKED" == restriction.defaultType && !restriction.driveProfiles.hasOwnProperty("FREE") && !restriction.driveProfiles.hasOwnProperty("BLOCKED")) {
  509. drivingModality = "DRIVING_BLOCKED";
  510. } else {
  511. drivingModality = defaultType2drivingModality[restriction.defaultType];
  512. }
  513.  
  514. setValue("select.do-change-driving-modality", restrictionsModal, drivingModality);
  515.  
  516. var driveProfiles, driveProfile, i, j, vehicleType, plus, driveProfileItem, subscription;
  517. if (restriction.driveProfiles.hasOwnProperty("FREE")) {
  518. driveProfiles = restriction.driveProfiles.FREE;
  519. for(i = 0; i < driveProfiles.length; i++) {
  520. driveProfile = driveProfiles[i];
  521.  
  522. $("div.add-drive-profile-item.do-add-item", restrictionsModal).click();
  523.  
  524. for(j = 0; j < driveProfile.vehicleTypes.length; j++) {
  525. vehicleType = driveProfile.vehicleTypes[j];
  526.  
  527. plus = lastFaPlus(restrictionsModal);
  528. plus.click();
  529. driveProfileItem = plus.parents("div.drive-profile-item");
  530. $("div.btn-group.open a.do-init-vehicle-type", driveProfileItem).click();
  531.  
  532. $("div.vehicle-type span.restriction-chip-content", driveProfileItem).click();
  533.  
  534. $('a.do-set-vehicle-type[data-value="' + vehicleType + '"]', driveProfileItem).click();
  535. }
  536.  
  537. if (driveProfile.numPassengers > 0) {
  538. plus = lastFaPlus(restrictionsModal);
  539. plus.click();
  540. driveProfileItem = plus.parents("div.drive-profile-item");
  541. $("div.btn-group.open a.do-init-num-passengers", driveProfileItem).click();
  542.  
  543. if (driveProfile.numPassengers > 2) {
  544. $("a.do-set-num-passengers[data-value='" + driveProfile.numPassengers + "']").click();
  545. }
  546. }
  547.  
  548. for(var k = 0; k < driveProfile.subscriptions.length; k++) {
  549. subscription = driveProfile.subscriptions[k];
  550.  
  551. plus = lastFaPlus(restrictionsModal);
  552. plus.click();
  553. driveProfileItem = plus.parents("div.drive-profile-item");
  554. $("div.btn-group.open a.do-init-subscription", driveProfileItem).click();
  555.  
  556.  
  557. $("div.subscription span.restriction-chip-content", driveProfileItem).click();
  558.  
  559. $('a.do-set-subscription[data-value="' + subscription + '"]', driveProfileItem).click();
  560. }
  561. }
  562. } else if (restriction.driveProfiles.hasOwnProperty("BLOCKED")) {
  563. driveProfiles = restriction.driveProfiles.BLOCKED;
  564.  
  565. for(i = 0; i < driveProfiles.length; i++) {
  566. driveProfile = driveProfiles[i];
  567.  
  568. if (driveProfile.vehicleTypes.length > 0) {
  569. setCheck('input#all-vehicles-off-radio', restrictionsModal, true);
  570.  
  571. for(j = 0; j < driveProfile.vehicleTypes.length; j++) {
  572. vehicleType = driveProfile.vehicleTypes[j];
  573.  
  574. setCheck('input#vehicle-type-' + vehicleType + '-checkbox', restrictionsModal, true);
  575. }
  576. }
  577. }
  578. }
  579.  
  580. $("div.modal-footer button.do-create", restrictionsModal).click();
  581. }
  582. clearMessages();
  583. addMessage("Restrictions from " + name + " applied to current selection.");
  584. }
  585. });
  586. }
  587. }
  588. }
  589. }
  590. });
  591. });
  592.  
  593. // configuration of the observer:
  594. var config = { attributes: false, childList: true, characterData: false, subtree: true };
  595.  
  596. // pass in the target node, as well as the observer options
  597. observer.observe(observerTarget, config);
  598.  
  599. initialized = true;
  600. }
  601.  
  602. setTimeout(initializeRestrictionManager, 1000);
  603. })();