Restriction Manager

Save, and load, restrictions from local storage.

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