WME PL Jump

Opens a PL in an existing WME window/tab.

  1. // ==UserScript==
  2. // @name WME PL Jump
  3. // @description Opens a PL in an existing WME window/tab.
  4. // @version 2020.11.23.01
  5. // @author The_Cre8r and SAR85
  6. // @copyright The_Cre8r and SAR85
  7. // @license CC BY-NC-ND
  8. // @grant none
  9. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  10. // @namespace https://github.com/TheCre8r/WME-PL-Jump-Release/
  11. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  12. // ==/UserScript==
  13.  
  14. /* global OpenLayers */
  15. /* global W */
  16. /* global $ */
  17. /* global WazeWrap */
  18.  
  19.  
  20. (function () {
  21. 'use strict';
  22. var app,
  23. AppView,
  24. jumpInput,
  25. JumpView,
  26. LinkView,
  27. Link,
  28. links,
  29. LinkCollection,
  30. tab,
  31. c1,
  32. c2,
  33. c3;
  34. function log(txt) {
  35. if ('object' === typeof txt) {
  36. //text = JSON.stringify(text);
  37. }
  38. console.log('PLJ: ' + txt);
  39. }
  40.  
  41. /**
  42. * Checks for necessary page elements or objects before initializing
  43. * script.
  44. * @param tries {Number} The number of tries bootstrapping has been
  45. * attempted.
  46. */
  47. function bootstrap(tries) {
  48. tries = tries || 0;
  49. if (WazeWrap.Ready && $ &&
  50. window.Backbone && $('#edit-buttons').length) {
  51. init();
  52. } else if (tries < 20) {
  53. window.setTimeout(function () {
  54. bootstrap(tries + 1);
  55. }, 1000);
  56. }
  57. }
  58.  
  59. /**
  60. * Initializes global variables and sets up HTML and event listeners.
  61. */
  62. function init() {
  63. var tabContent = '<div id="pljumptabcontent"></div>';
  64.  
  65. if (!$('#pljumpinput').length) {
  66. initializeLink();
  67. initializeCollection();
  68. links = new LinkCollection;
  69. tab = new WazeWrap.Interface.Tab('PLJump', tabContent, init2);
  70. updateAlert();
  71. app = new AppView({ collection: links });
  72. }
  73.  
  74. }
  75. function init2() {
  76. if (!$('#pljumpinput').length) {
  77. initializeViews();
  78. jumpInput = new JumpView({ collection: links });
  79. W.editingMediator.on('change:editingHouseNumbers', function(){
  80. if(!W.editingMediator.attributes.editingHouseNumbers)
  81. init2();
  82. })
  83. }
  84. FUck();
  85. }
  86.  
  87. function FUck() {
  88. log("Checking for WMEFU");
  89. if ($('#_cbShrinkTopBars').length) {
  90. initFU();
  91. }
  92. else {
  93. /**
  94. * MutationObserver for WMEFU
  95. */
  96. let targetNode = document.getElementById('user-tabs'); // Select the node that will be observed for mutations
  97. let config = { attributes:true, childList: true, subtree: true }; // Options for the observer (which mutations to observe)
  98. var callback = (function(mutations) {
  99. mutations.forEach(function(mutation) {
  100. for (let i = 0; i < mutation.addedNodes.length; i++) {
  101. let addedNode = mutation.addedNodes[i];
  102. if (addedNode.nodeType === Node.ELEMENT_NODE) {
  103. if (addedNode.innerText.includes("FU"))
  104. {
  105. initFU();
  106. observer.disconnect();
  107. }
  108. }
  109. }
  110. });
  111. });
  112. var observer = new MutationObserver(callback); // Create an observer instance linked to the callback function
  113. observer.observe(targetNode, config); // Start observing the target node for configured mutations
  114. }
  115. }
  116.  
  117. function initFU() {
  118. log('WMEFU - Loaded');
  119. //Change height based on WMEFU Settings
  120. if (JSON.parse(localStorage.WMEFUSettings).shrinkTopBars && $('#_inpUICompression').find(":selected").text() == "Low")
  121. {
  122. log('WMEFU - Initial Load for Low Compression');
  123. setTimeout(function () {
  124. $('#pljumpinput').css("height","26px");
  125. $('.pljump').css("margin","1px 10px 0px 10px");
  126. return;
  127. }, 950);
  128. }
  129. else if (JSON.parse(localStorage.WMEFUSettings).shrinkTopBars && $('#_inpUICompression').find(":selected").text() == "High")
  130. {
  131. log('WMEFU - Initial Load for High Compression');
  132. setTimeout(function () {
  133. $('#pljumpinput').css("height","19px");
  134. $('.pljump').css("margin","-6px 10px 0px 10px");
  135. return;
  136. }, 950);
  137. }
  138. $('#_cbShrinkTopBars').click(function(){
  139. if ($('#_cbShrinkTopBars').prop('checked'))
  140. {
  141. log('WMEFU - Shrinking');
  142. $('#pljumpinput').css("height","26px");
  143. $('.pljump').css("margin","1px 10px 0px 10px");
  144. return;
  145. }
  146. else
  147. {
  148. log('WMEFU - Restoring');
  149. $('#pljumpinput').css("height","40px");
  150. $('.pljump').css("margin","8px 10px 0px 10px");
  151. return;
  152. }
  153. });
  154. $( "#_inpUICompression" ).change(function() {
  155. if ($('#_inpUICompression').val() == "1")
  156. {
  157. log('WMEFU - Shrinking to Low Compression');
  158. $('#pljumpinput').css("height","26px");
  159. $('.pljump').css("margin","1px 10px 0px 10px");
  160. return;
  161. }
  162. else if ($('#_inpUICompression').val() == "0")
  163. {
  164. log('WMEFU - Restoring to No Compression');
  165. $('#pljumpinput').css("height","40px");
  166. $('.pljump').css("margin","8px 10px 0px 10px");
  167. return;
  168. }
  169. else if ($('#_inpUICompression').val() == "2")
  170. {
  171. log('WMEFU - Shrinking to High Compression');
  172. $('#pljumpinput').css("height","19px");
  173. $('.pljump').css("margin","-6px 10px 0px 10px");
  174. return;
  175. }
  176. });
  177. }
  178.  
  179. function updateAlert() {
  180. var version = GM_info.script.version.toString();
  181. var versionChanges = '';
  182. var previousVersion;
  183.  
  184. versionChanges += 'WME PL Jump v' + version + ' changes:\n';
  185. versionChanges += '- Style Changes\n- Compatibility Updates\n';
  186.  
  187. if (localStorage === void 0) {
  188. return;
  189. }
  190. localStorage.removeItem("pljumplocation");
  191. localStorage.removeItem("WMEPLJLocation");
  192. localStorage.removeItem("pljumpLocation");
  193.  
  194.  
  195. previousVersion = localStorage.pljumpVersion;
  196.  
  197. if (version !== previousVersion) {
  198. alert(versionChanges);
  199. localStorage.pljumpVersion = version;
  200. }
  201. }
  202.  
  203. function initializeLink() {
  204. /**
  205. * Class representing PLs.
  206. */
  207. Link = Backbone.Model.extend({
  208. defaults: {
  209. link: '',
  210. lonLat: null,
  211. segments: null,
  212. mapProblem: null,
  213. nodes: null,
  214. venues: null,
  215. selectionInBounds: false,
  216. selectionOnScreen: false,
  217. updateRequest: null,
  218. zoom: null
  219. },
  220. itemsToSelect: [],
  221. modelObject: null,
  222. /**
  223. * Checks whether the PLs items to select are in the data bounds
  224. * and in view.
  225. */
  226. isSelectionOnScreen: function () {
  227. return this.attributes.selectionInBounds &&
  228. this.attributes.selectionOnScreen;
  229. },
  230. /**
  231. * Checks whether the PL has items to select or not.
  232. */
  233. hasItems: function () {
  234. return this.attributes.segments || this.attributes.nodes ||
  235. this.attributes.venues || this.attributes.updateRequest ||
  236. this.attributes.mapProblem;
  237. },
  238. /**
  239. * Parses PL to extract the location and selected item info.
  240. */
  241. initialize: function () {
  242. var extractedData = {},
  243. link = this.get('link'),
  244. lat,
  245. lon,
  246. lonLat,
  247. mapProblem,
  248. nodes,
  249. segments,
  250. updateRequest,
  251. venues,
  252. zoom;
  253. var linktemp = link;
  254. if (linktemp.includes("google")){
  255. let google = link.split('@').pop().split(',');
  256. lon = google[1];
  257. lat = google[0];
  258. zoom = parseInt(google[2]);
  259. link = 'https://www.waze.com/en-US/editor/?lon=' + lon + '&lat=' + lat + '&zoom=' + $(Math.max(0,Math.min(10,(zoom - 12))))[0];
  260. }
  261. else if (linktemp.includes("mandrillapp")){
  262. let mandrillapp = link.split('_');
  263. mandrillapp = window.atob(mandrillapp[1]);
  264. mandrillapp = mandrillapp.split(/\\/)[0];
  265. link = 'https://www.waze.com/en-US/editor/?' + mandrillapp;
  266. }
  267. else if (linktemp.includes("livemap")){
  268. let livemap = link.replace("lng", "lon");
  269. link = livemap;
  270. }
  271.  
  272. link = decodeURIComponent(link);
  273. lat = link.match(/lat=(-?\d+\.\d+)/);
  274. lon = link.match(/lon=(-?\d+\.\d+)/);
  275. nodes = link.match(/nodes=((\d+,?)+)/);
  276. segments = link.match(/segments=((\d+,?)+)/);
  277. updateRequest = link.match(/mapUpdateRequest=(\d+)/);
  278. mapProblem = link.match(/mapProblem=(\d%2F\d+)/);
  279. venues = link.match(/venues=((\d+\.?)+)/);
  280. zoom = link.match(/zoom=(\d+)/);
  281.  
  282. extractedData.lat = lat && lat.length === 2 ?
  283. parseFloat(lat[1]) : null;
  284. extractedData.lon = lon && lon.length === 2 ?
  285. parseFloat(lon[1]) : null;
  286. extractedData.segments = segments && segments.length > 1 ?
  287. segments[1].split(',') : null;
  288. extractedData.venues = venues ? venues[1].split(',') : null;
  289. extractedData.nodes = nodes && nodes.length > 1 ?
  290. nodes[1].split(',') : null;
  291. extractedData.updateRequest = updateRequest &&
  292. updateRequest.length === 2 ? updateRequest[1] : null;
  293. extractedData.mapProblem = mapProblem &&
  294. mapProblem.length === 2 ?
  295. mapProblem[1].replace('%2F', '/') : null;
  296. extractedData.zoom = zoom && zoom.length === 2 ?
  297. parseInt(zoom[1]) : null;
  298.  
  299. this.set({
  300. 'segments': extractedData.segments,
  301. 'mapProblem': extractedData.mapProblem,
  302. 'nodes': extractedData.nodes,
  303. 'venues': extractedData.venues,
  304. 'updateRequest': extractedData.updateRequest,
  305. 'zoom': extractedData.zoom
  306. });
  307.  
  308. if (extractedData.lon && extractedData.lat) {
  309. lonLat = new OpenLayers.LonLat(extractedData.lon,
  310. extractedData.lat);
  311. lonLat.transform(W.map.displayProjection,
  312. W.map.getProjectionObject());
  313. if (W.map.isValidLonLat(lonLat)) {
  314. this.set('lonLat', lonLat);
  315. }
  316. } else {
  317. this.set('lonLat', { lon: 'None', lat: 'None' });
  318. }
  319.  
  320. this.on('change:link', this.onLinkChanged);
  321. },
  322. /**
  323. * Private method to compile WME objects for selection.
  324. * @private
  325. */
  326. createSelection: function () {
  327. var i,
  328. itemsToSelect = [],
  329. itemType,
  330. itemTypes = ['segments', 'nodes', 'venues'],
  331. mapBounds = W.map.getExtent(),
  332. modelObject,
  333. n,
  334. selectionInBounds = true,
  335. selectionOnScreen = true;
  336.  
  337. var getObject = function (element) {
  338. var object = W.model[itemType].getObjectById(element);
  339. if (object) {
  340. itemsToSelect.push(object);
  341. if (!mapBounds.intersectsBounds(
  342. object.geometry.getBounds())) {
  343. selectionOnScreen = false;
  344. }
  345. } else {
  346. selectionInBounds = false;
  347. }
  348. };
  349.  
  350. for (i = 0, n = itemTypes.length; i < n; i++) {
  351. itemType = itemTypes[i];
  352. _.each(this.attributes[itemType], getObject, this);
  353. }
  354.  
  355. if (this.attributes.updateRequest ||
  356. this.attributes.mapProblem) {
  357. modelObject = W.model.mapUpdateRequests.getObjectById(
  358. this.attributes.updateRequest) || W.model.problems.getObjectById(
  359. this.attributes.mapProblem) || null;
  360. if (!modelObject) {
  361. selectionInBounds = false;
  362. } else if (!mapBounds.intersectsBounds(
  363. modelObject.attributes.geometry.getBounds())) {
  364. selectionOnScreen = false;
  365. }
  366. }
  367.  
  368. this.set({
  369. 'selectionInBounds': selectionInBounds,
  370. 'selectionOnScreen': selectionOnScreen
  371. });
  372. this.modelObject = modelObject;
  373. this.itemsToSelect = itemsToSelect;
  374. return this;
  375. },
  376. /**
  377. * Pans the map to the lat & lon location specified in the PL.
  378. * @private
  379. */
  380. moveTo: function () {
  381. var zoom = this.get('zoom') || W.map.getZoom();
  382. if (this.attributes.lonLat) {
  383. W.map.moveTo(this.attributes.lonLat, zoom);
  384. }
  385. return this;
  386. },
  387. /**
  388. * Public method to go to a Link and select its objects.
  389. */
  390. open: function (forceMove) {
  391. this.createSelection();
  392.  
  393. if (this.hasItems()) {
  394. this.select();
  395. if (forceMove || !this.isSelectionOnScreen()) {
  396. this.moveTo();
  397. }
  398. } else {
  399. this.moveTo();
  400. }
  401.  
  402. return this;
  403. },
  404. /**
  405. * Re-parse the link if it changes.
  406. */
  407. onLinkChanged: function () {
  408. this.initialize();
  409. },
  410. /**
  411. * Selects objects extracted from the PL.
  412. * @private
  413. */
  414. select: function () {
  415. var selectItems = function () {
  416.  
  417. this.createSelection();
  418.  
  419. if (this.modelObject) {
  420. W.commands.execute('problems:show', this.modelObject);
  421. }
  422.  
  423. if (this.itemsToSelect.length > 0) {
  424. W.selectionManager.setSelectedModels(this.itemsToSelect);
  425. }
  426. };
  427.  
  428. WazeWrap.Model.onModelReady(selectItems,
  429. this.isSelectionOnScreen(), this);
  430.  
  431. return this;
  432. }
  433. });
  434. }
  435.  
  436. function initializeCollection() {
  437. /**
  438. * Class representing collection of PLs.
  439. */
  440. LinkCollection = Backbone.Collection.extend({
  441. model: Link,
  442. getLinkID: function () {
  443. var id = 0;
  444. return function () { return id++; };
  445. } ()
  446. });
  447. }
  448. function initializeViews() {
  449. /**
  450. * Class containing the main app interface and logic.
  451. */
  452. AppView = Backbone.View.extend({
  453. collection: null,
  454. /**
  455. * Gets the stored options from localStorage and returns
  456. * them as an object.
  457. */
  458. options: function () {
  459. var defaultOptions = {
  460. 'trackHistory': true,
  461. 'trackSelections': false
  462. };
  463. var options = localStorage && localStorage.pljOptions;
  464.  
  465. options = options && JSON.parse(options) || defaultOptions;
  466. return options;
  467. } (),
  468. clearButtonCss: {
  469. 'margin-bottom': '10px'
  470. },
  471. divCss: {
  472. 'overflow-y': 'scroll',
  473. 'max-height': '400px'
  474. },
  475. tableDivCss: {
  476. 'overflow-y': 'scroll',
  477. 'max-height': '400px',
  478. 'width': '292px'
  479. },
  480. el: $('#pljumptabcontent'),
  481. template: function () {
  482. var $div = $('<div/>'),
  483. $table = $('<table/>').attr('id', 'plj-history-table'),
  484. $clearButton = $('<button/>').attr('id', 'plj-clear-table').
  485. css(this.clearButtonCss).text('Clear all entries'),
  486. $trackHistory = $('<div/>').append($('<input type="checkbox" id="plj-track-history"><label>Track map move history</label>')),
  487. $trackSelections = $('<div/>').append($('<input type="checkbox" id="plj-track-selections"><label>Track selections</label>')),
  488. $infoText = $('<p>Click a table entry below to select it. To force the map to move to the PL location, Ctrl+Click.</p>');
  489.  
  490. $div.append($trackHistory);
  491. $div.append($trackSelections);
  492. $div.append($clearButton.wrap('<div>').parent());
  493. $div.append($infoText.wrap('<div>').parent());
  494. $div.append($table.wrap('<div>').parent().
  495. css(this.tableDivCss));
  496.  
  497. return $div;
  498. },
  499. events: {
  500. 'click #plj-clear-table': 'onClearTableClicked',
  501. 'change #plj-track-history': 'onTrackHistoryChange',
  502. 'change #plj-track-selections': 'onTrackSelectionsChange'
  503. },
  504. initialize: function () {
  505. this.listenTo(this.collection, 'add', this.addLink);
  506. this.render();
  507. this.onTrackHistoryChange();
  508. this.onTrackSelectionsChange();
  509. },
  510. render: function () {
  511. this.$el.append(this.template());
  512.  
  513. this.$trackHistory = this.$el.find('#plj-track-history');
  514. this.$trackHistory.prop('checked',
  515. this.options['trackHistory']);
  516.  
  517. this.$trackSelections = this.$el.find('#plj-track-selections');
  518. this.$trackSelections.prop('checked',
  519. this.options['trackSelections']);
  520.  
  521. this.$table = this.$el.find('#plj-history-table');
  522.  
  523. this.$clearButton = this.$el.find('#plj-clear-table');
  524.  
  525. return this;
  526. },
  527. /**
  528. * Adds a new link to the table.
  529. */
  530. addLink: function (link) {
  531. var view = new LinkView({ model: link });
  532. this.$table.prepend(view.render().$el);
  533. },
  534. /**
  535. * Tracks map moves and determines if the location changed
  536. * after a move. This filters out zoom-only "moves".
  537. */
  538. mapLocationChanged: function () {
  539. var lastLocation,
  540. locationChanged,
  541. newLocation;
  542. var getMapLocation = function () {
  543. var lonLat = W.map.getCenter(),
  544. zoom = W.map.getZoom();
  545. return {
  546. lon: lonLat.lon,
  547. lat: lonLat.lat,
  548. zoom: zoom
  549. };
  550. };
  551. var compareLocations = function () {
  552. newLocation = getMapLocation();
  553. if (newLocation.lon !== lastLocation.lon ||
  554. newLocation.lat !== lastLocation.lat) {
  555. lastLocation = newLocation;
  556. locationChanged = true;
  557. } else {
  558. locationChanged = false;
  559. }
  560. };
  561. lastLocation = getMapLocation();
  562. W.map.events.register('moveend', this, compareLocations);
  563. return function () {
  564. return locationChanged;
  565. };
  566. } (),
  567. /**
  568. * Callback for clearing the link history.
  569. */
  570. onClearTableClicked: function (e) {
  571. this.$table.find('tr').remove();
  572. this.collection.reset();
  573. },
  574. /**
  575. * Callback for map move.
  576. */
  577. onMapMove: function () {
  578. if (this.mapLocationChanged()) {
  579. this.collection.add({
  580. link: $('.WazeControlPermalink a').prop('href')
  581. });
  582. }
  583. },
  584. /**
  585. * Callback for selection change.
  586. */
  587. onSelectionChanged: function () {
  588. if (this.selectionChanged()) {
  589. this.collection.add({
  590. link: $('.WazeControlPermalink a').prop('href')
  591. });
  592. }
  593. },
  594. /**
  595. * Callback for track history checkbox change.
  596. */
  597. onTrackHistoryChange: function (e) {
  598. var track = this.$trackHistory.prop('checked');
  599.  
  600. W.map.events.unregister('moveend', this, this.onMapMove);
  601.  
  602. if (track) {
  603. W.map.events.register('moveend', this, this.onMapMove);
  604. }
  605.  
  606. this.saveOption('trackHistory', track);
  607. },
  608. /**
  609. * Callback for track selections checkbox change.
  610. */
  611. onTrackSelectionsChange: function (e) {
  612. var track = this.$trackSelections.prop('checked');
  613.  
  614. W.selectionManager.events.unregister('selectionchanged', this,
  615. this.onSelectionChanged);
  616.  
  617. if (track) {
  618. W.selectionManager.events.register('selectionchanged', this,
  619. this.onSelectionChanged);
  620. }
  621.  
  622. this.saveOption('trackSelections', track);
  623. },
  624. /**
  625. * Saves app options to localStorage.
  626. */
  627. saveOption: function (key, value) {
  628. if (localStorage === void 0) { return; }
  629. this.options[key] = value;
  630. localStorage.pljOptions = JSON.stringify(this.options);
  631. },
  632. /**
  633. * Tracks selection changes to determine if the same object is
  634. * selected consecutively.
  635. */
  636. selectionChanged: function () {
  637. var lastSelection,
  638. newSelection,
  639. selectionChanged;
  640.  
  641. var getSelectedItem = function () {
  642. return W.selectionManager.hasSelectedFeatures() &&
  643. W.selectionManager.getSelectedFeatures[0];
  644. };
  645.  
  646. var compareSelection = function () {
  647. newSelection = getSelectedItem();
  648. if (W.selectionManager.hasSelectedFeatures() &&
  649. lastSelection !== newSelection) {
  650. lastSelection = newSelection;
  651. selectionChanged = true;
  652. } else {
  653. selectionChanged = false;
  654. }
  655. };
  656.  
  657. lastSelection = getSelectedItem();
  658. W.selectionManager.events.register('selectionchanged', this,
  659. compareSelection);
  660.  
  661. return function () {
  662. return selectionChanged;
  663. };
  664. } ()
  665. });
  666.  
  667. /**
  668. * Class for displaying link data in a table.
  669. */
  670. LinkView = Backbone.View.extend({
  671. tagName: 'tr',
  672. tdCss: {
  673. 'padding': '5px',
  674. 'border': '1px solid gray',
  675. 'border-right': 'none',
  676. 'cursor': 'pointer'
  677. },
  678. linkInfoCss: {
  679. 'margin': 0
  680. },
  681. linkRemoveCss: {
  682. 'padding': '5px 5px 5px 10px',
  683. 'border': '1px solid gray',
  684. 'border-left': 'none',
  685. 'text-align': 'center',
  686. 'font-weight': 'bold'
  687. },
  688. template: function () {
  689. var $nameCell = $('<td/>').
  690. css(this.tdCss).addClass('plj-link'),
  691.  
  692. $deleteCell = $('<td/>').
  693. css(this.linkRemoveCss).
  694. addClass('plj-remove-link').
  695. append($('<a/>').text('X')),
  696.  
  697. attributes = this.model.attributes,
  698. lonLat,
  699. objectsText = [];
  700.  
  701. if (attributes.lonLat) {
  702. lonLat = attributes.lonLat.clone();
  703. lonLat.transform(W.map.getProjectionObject(),
  704. W.map.displayProjection);
  705. }
  706.  
  707. objectsText.push('<b>Lon:</b> ' + (lonLat ?
  708. lonLat.lon.toFixed(3) + '\xB0' : 'None') +
  709. ' <b>Lat:</b> ' + (lonLat ? lonLat.lat.toFixed(3) + '\xB0' : 'None') +
  710. ' <b>Zoom:</b> ' + (attributes.zoom ? attributes.zoom : 'None'));
  711.  
  712. if (this.model.hasItems()) {
  713. if (attributes.updateRequest) {
  714. objectsText.push('<b>Update Request:</b> ' +
  715. attributes.updateRequest);
  716. }
  717. if (attributes.mapProblem) {
  718. objectsText.push('<b>Map Problem:</b> ' +
  719. attributes.mapProblem);
  720. }
  721. if (attributes.segments) {
  722. objectsText.push('<b>Segments:</b> ' +
  723. attributes.segments.join(', '));
  724. }
  725. if (attributes.nodes) {
  726. objectsText.push('<b>Nodes:</b> ' +
  727. attributes.nodes.join(', '));
  728. }
  729. if (attributes.venues) {
  730. objectsText.push('<b>Places:</b> ' +
  731. attributes.venues.join(', '));
  732. }
  733. }
  734.  
  735. _.each(objectsText, function (text) {
  736. $nameCell.append(
  737. //$('<p/>').css(this.linkInfoCss).html(text)
  738. $('<p/>').html(text)
  739. );
  740. }, this);
  741.  
  742. return [$nameCell, $deleteCell];
  743. },
  744. events: {
  745. 'click .plj-link': 'onLinkClicked',
  746. 'click .plj-remove-link': 'onRemoveClicked'
  747. },
  748. initialize: function () {
  749. this.listenTo(this.model, 'change', this.render);
  750. this.listenTo(this.model, 'destroy', this.remove);
  751. },
  752. render: function () {
  753. var cells = this.template();
  754.  
  755. this.$nameCell = cells[0];
  756. this.$deleteCell = cells[1];
  757.  
  758. this.$el.empty();
  759. this.$el.append(this.$nameCell).append(this.$deleteCell);
  760. return this;
  761. },
  762. /**
  763. * Callback for clicking a link.
  764. */
  765. onLinkClicked: function (e) {
  766. var forceMove = e && e.ctrlKey;
  767.  
  768. W.map.events.unregister('moveend', app, app.onMapMove);
  769.  
  770. this.model.open(forceMove);
  771. app.onTrackHistoryChange();
  772. },
  773. /**
  774. * Callback for removing a link.
  775. */
  776. onRemoveClicked: function (e) {
  777. this.model.destroy();
  778. }
  779. });
  780.  
  781. /**
  782. * View for text input box and buttons for manual PL input.
  783. */
  784. JumpView = Backbone.View.extend({
  785. collection: null,
  786. tagName: 'div',
  787. template: function () {
  788. var buttonstyle = '';
  789. var inputstyle = "";
  790. return `<input type="text" id="pljumpinput" style="font-family:'Rubik', 'Boing-light', sans-serif,FontAwesome; display: inline-block; line-height: normal; outline:0px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05); transition: 0.5s all; background-color: #f0f4f6; border-radius: 8px; border: none; padding: 4px 6px 4px 6px; color: #354148; opacity: 1;height:40px;" placeholder="&#xf0c1; WME PL Jump"></input>` +
  791. '<button id="pljumpbutton-move" class="btn btn-primary" style="margin-bottom: 5px;margin-right: 4px;margin-left: 4px;padding-left:5px;padding-right:5px;" title="Select & Jump"><i class="fa fa-hand-pointer-o" aria-hidden="true"></i></button>';
  792. //'<button id="pljumpbutton" class="btn btn-primary" style="margin-bottom: 5px; padding-left:5px; padding-right:5px;" title="Select & Jump"><i class="fa fa-rocket" aria-hidden="true"></i></button>';
  793. },
  794. events: {
  795. 'click #pljumpbutton': 'onJumpClick',
  796. 'click #pljumpbutton-move': 'onJumpMoveClick',
  797. 'keyup #pljumpinput': 'onInputChanged'
  798. },
  799. initialize: function () {
  800. this.render();
  801. this.onInputChanged();
  802. },
  803. render: function () {
  804. this.$el.html(this.template());
  805. this.$el.addClass("pljump");
  806. this.$el.css({
  807. 'float': 'left',
  808. 'width':'213px',
  809. 'margin': '8px 10px 0px 10px'
  810. });
  811.  
  812. this.input = this.$el.find('#pljumpinput');
  813. this.jumpButton = this.$el.find('#pljumpbutton');
  814. this.jumpMoveButton = this.$el.find('#pljumpbutton-move');
  815. // Puts it in the topbar
  816. $('#edit-buttons > div').prepend(this.$el);
  817. },
  818. /**
  819. * Callback for clicking the jump button.
  820. */
  821. onJumpClick: function (e, forceMove) {
  822. var linkText = this.input.val(),
  823. newLink;
  824.  
  825. if (linkText) {
  826. newLink = this.collection.add({ link: linkText });
  827. newLink.open(forceMove);
  828. }
  829. this.input.val('');
  830. this.onInputChanged();
  831. },
  832. /**
  833. * Callback for clicking the jump and move button.
  834. */
  835. onJumpMoveClick: function (e) {
  836. this.onJumpClick(e, true);
  837. },
  838. /**
  839. * Callback for keyup in the input box.
  840. * Disables the buttons if the textbox is empty.
  841. * If the key is 'enter', triggers the jump button click.
  842. */
  843. onInputChanged: function (e) {
  844. /*
  845. this.jumpButton.prop('disabled',
  846. this.input.val() === '' ? true : false);
  847. this.jumpMoveButton.prop('disabled',
  848. this.input.val() === '' ? true : false);
  849. */
  850. if (e && e.keyCode === 13) {
  851. this.onJumpClick();
  852. }
  853. }
  854. });
  855. }
  856. bootstrap();
  857. } ());