Greasy Fork is available in English.

wLib

A library for WME script developers.

Този скрипт не може да бъде инсталиран директно. Това е библиотека за други скриптове и може да бъде използвана с мета-директива // @require https://update.greatest.deepsurf.us/scripts/9794/106259/wLib.js

  1. // ==UserScript==
  2. // @name wLib
  3. // @description A library for WME script developers.
  4. // @version 1.0.7
  5. // @author SAR85
  6. // @copyright SAR85
  7. // @license CC BY-NC-ND
  8. // @grant none
  9. // @include https://www.waze.com/editor/*
  10. // @include https://www.waze.com/*/editor/*
  11. // @include https://editor-beta.waze.com/*
  12. // @namespace https://greatest.deepsurf.us/users/9321
  13. // ==/UserScript==
  14.  
  15. /* global W */
  16. /* global OL */
  17. /* global wLib */
  18.  
  19. (function () {
  20. function bootstrap(tries) {
  21. tries = tries || 1;
  22.  
  23. if (window.$ &&
  24. window.Backbone &&
  25. window._ &&
  26. window.OL &&
  27. window.W &&
  28. window.W.map &&
  29. window.W.accelerators &&
  30. window.W.model) {
  31.  
  32. init();
  33.  
  34. } else if (tries < 1000) {
  35.  
  36. setTimeout(function () {
  37. bootstrap(tries++);
  38. }, 200);
  39.  
  40. }
  41. }
  42.  
  43. bootstrap();
  44.  
  45. function init() {
  46. var oldLib = window.wLib;
  47. var root = this;
  48. /**
  49. * The wLib namespace.
  50. * @namespace {Object} wLib
  51. * @global
  52. */
  53. var wLib = {};
  54. /**
  55. * The current wLib version.
  56. * @member {string} wLib.VERSION
  57. */
  58. wLib.VERSION = '1.0.7';
  59. /**
  60. * Whether or not the current WME is the beta version.
  61. * @member {boolean} wLib.isBetaEditor
  62. */
  63. wLib.isBetaEditor = /beta/.test(location.href);
  64. /**
  65. * Sets window.wLib to the last wLib object and returns the current
  66. * version.
  67. * @function wLib.noConflict
  68. */
  69. wLib.noConflict = function () {
  70. root.wLib = oldLib;
  71. return this;
  72. };
  73. /**
  74. * Namespace for functions related to geometry.
  75. * @namespace {Object} wLib.Geometry
  76. */
  77. wLib.Geometry = new Geometry;
  78. /**
  79. * Namespace for functions related to the model.
  80. * @namespace {Object} wLib.Model
  81. */
  82. wLib.Model = new Model;
  83. /**
  84. * Namespace for functions related to the WME interface
  85. * @namespace {Obect} wLib.Interface
  86. */
  87. wLib.Interface = new Interface;
  88. /**
  89. * Namespace for utility functions.
  90. * @namespace {Object} wLib.Util
  91. */
  92. wLib.Util = new Util;
  93. /**
  94. * Namespace for functions related to WME actions.
  95. * @namespace {Object} wLib.api
  96. */
  97. wLib.api = new API;
  98.  
  99. /* Export library to window */
  100. root.wLib = wLib;
  101. }
  102.  
  103. /*** GEOMETRY ***/
  104. function Geometry() {
  105. /**
  106. * Determines if an OpenLayers.Geometry is within the map view.
  107. * @function wLib.Geometry.isGeometryInMapExtent
  108. * @param geometry OpenLayers.Geometry
  109. * @return {Boolean} Whether or not the geometry is in the map extent.
  110. */
  111. this.isGeometryInMapExtent = function (geometry) {
  112. 'use strict';
  113. return geometry && geometry.getBounds &&
  114. W.map.getExtent().intersectsBounds(geometry.getBounds());
  115. };
  116. /**
  117. * Determines if an {OpenLayers.LonLat} is within the map view.
  118. * @function wLib.Geometry.isLonLatInMapExtent
  119. * @param {OpenLayers.LonLat} lonLat The LonLat to check.
  120. * @return {Boolean} Whether or not the LonLat is in the map extent.
  121. */
  122. this.isLonLatInMapExtent = function (lonLat) {
  123. 'use strict';
  124. return lonLat && W.map.getExtent().containsLonLat(lonLat);
  125. };
  126. };
  127.  
  128. /*** MODEL ***/
  129. function Model() {
  130. /**
  131. * Returns whether or not the passed object or object id represents a
  132. * venue.
  133. * @function wLib.Model.isPlace
  134. * @param {(String|Venue)} place A string representing the id of
  135. * the place to test (only works if place is in the data extent) or the
  136. * venue model object itself.
  137. * @return {Boolean} Whether or not the place is a venue.
  138. */
  139. this.isPlace = function (place) {
  140. if (typeof place === 'string') {
  141. place = W.model.venues.get(place);
  142. }
  143. return place && place.CLASS_NAME === 'Waze.Feature.Vector.Landmark';
  144. },
  145. /**
  146. * Returns the geometry object representing the navigation stop point
  147. * of a venue.
  148. * @function wLib.Model.getPlaceNavStopPoint
  149. * @param {(String|Venue)} place A string representing the id of
  150. * the place to test (only works if place is in the data extent) or the
  151. * venue model object itself.
  152. * @return {OL.Geometry.Point} The geometry object representing the
  153. * navigation stop point of a venue.
  154. */
  155. this.getPlaceNavStopPoint = function (place) {
  156. if (typeof place === 'string') {
  157. place = W.model.venues.get(place);
  158. }
  159. return place && place.isPoint() ? place.geometry : place.attributes.entryExitPoints[0].point;
  160. };
  161. /**
  162. * Gets the IDs of any selected segments.
  163. * @function wLib.Model.getSelectedSegmentIDs
  164. * @return {Array} Array containing the IDs of selected segments.
  165. */
  166. this.getSelectedSegmentIDs = function () {
  167. 'use strict';
  168. var i, n, selectedItems, item, segments = [];
  169. if (!W.selectionManager.hasSelectedItems()) {
  170. return false;
  171. } else {
  172. selectedItems = W.selectionManager.selectedItems;
  173. for (i = 0, n = selectedItems.length; i < n; i++) {
  174. item = selectedItems[i].model;
  175. if ('segment' === item.type) {
  176. segments.push(item.attributes.id);
  177. }
  178. }
  179. return segments.length === 0 ? false : segments;
  180. }
  181. };
  182.  
  183. /**
  184. * Defers execution of a callback function until the WME map and data
  185. * model are ready. Call this function before calling a function that
  186. * causes a map and model reload, such as W.map.moveTo(). After the
  187. * move is completed the callback function will be executed.
  188. * @function wLib.Model.onModelReady
  189. * @param {Function} callback The callback function to be executed.
  190. * @param {Boolean} now Whether or not to call the callback now if the
  191. * model is currently ready.
  192. * @param {Object} context The context in which to call the callback.
  193. */
  194. this.onModelReady = function (callback, now, context) {
  195. var deferModelReady = function () {
  196. return $.Deferred(function (dfd) {
  197. var resolve = function () {
  198. dfd.resolve();
  199. W.model.events.unregister('mergeend', null, resolve);
  200. };
  201. W.model.events.register('mergeend', null, resolve);
  202. }).promise();
  203. };
  204. var deferMapReady = function () {
  205. return $.Deferred(function (dfd) {
  206. var resolve = function () {
  207. dfd.resolve();
  208. W.vent.off('operationDone', resolve);
  209. };
  210. W.vent.on('operationDone', resolve);
  211. }).promise();
  212. };
  213.  
  214. if (typeof callback === 'function') {
  215. context = context || callback;
  216. if (now && wLib.Util.mapReady() && wLib.Util.modelReady()) {
  217. callback.call(context);
  218. } else {
  219. $.when(deferMapReady() && deferModelReady()).
  220. then(function () {
  221. callback.call(context);
  222. });
  223. }
  224. }
  225. };
  226. /**
  227. * Retrives a route from the Waze Live Map.
  228. * @class
  229. * @name wLib.Model.RouteSelection
  230. * @param firstSegment The segment to use as the start of the route.
  231. * @param lastSegment The segment to use as the destination for the route.
  232. * @param {Array|Function} callback A function or array of funcitons to be
  233. * executed after the route
  234. * is retrieved. 'This' in the callback functions will refer to the
  235. * RouteSelection object.
  236. * @param {Object} options A hash of options for determining route. Valid
  237. * options are:
  238. * fastest: {Boolean} Whether or not the fastest route should be used.
  239. * Default is false, which selects the shortest route.
  240. * freeways: {Boolean} Whether or not to avoid freeways. Default is false.
  241. * dirt: {Boolean} Whether or not to avoid dirt roads. Default is false.
  242. * longtrails: {Boolean} Whether or not to avoid long dirt roads. Default
  243. * is false.
  244. * uturns: {Boolean} Whether or not to allow U-turns. Default is true.
  245. * @return {wLib.Model.RouteSelection} The new RouteSelection object.
  246. * @example: // The following example will retrieve a route from the Live Map and select the segments in the route.
  247. * selection = W.selectionManager.selectedItems;
  248. * myRoute = new wLib.Model.RouteSelection(selection[0], selection[1], function(){this.selectRouteSegments();}, {fastest: true});
  249. */
  250. this.RouteSelection = function (firstSegment, lastSegment, callback, options) {
  251. var i,
  252. n,
  253. start = this.getSegmentCenterLonLat(firstSegment),
  254. end = this.getSegmentCenterLonLat(lastSegment);
  255. this.options = {
  256. fastest: options && options.fastest || false,
  257. freeways: options && options.freeways || false,
  258. dirt: options && options.dirt || false,
  259. longtrails: options && options.longtrails || false,
  260. uturns: options && options.uturns || true
  261. };
  262. this.requestData = {
  263. from: 'x:' + start.x + ' y:' + start.y + ' bd:true',
  264. to: 'x:' + end.x + ' y:' + end.y + ' bd:true',
  265. returnJSON: true,
  266. returnGeometries: true,
  267. returnInstructions: false,
  268. type: this.options.fastest ? 'HISTORIC_TIME' : 'DISTANCE',
  269. clientVersion: '4.0.0',
  270. timeout: 60000,
  271. nPaths: 3,
  272. options: this.setRequestOptions(this.options)
  273. };
  274. this.callbacks = [];
  275. if (callback) {
  276. if (!(callback instanceof Array)) {
  277. callback = [callback];
  278. }
  279. for (i = 0, n = callback.length; i < n; i++) {
  280. if ('function' === typeof callback[i]) {
  281. this.callbacks.push(callback[i]);
  282. }
  283. }
  284. }
  285. this.routeData = null;
  286. this.getRouteData();
  287. };
  288.  
  289. this.RouteSelection.prototype =
  290. /** @lends wLib.Model.RouteSelection.prototype */ {
  291. /**
  292. * Formats the routing options string for the ajax request.
  293. * @private
  294. * @param {Object} options Object containing the routing options.
  295. * @return {String} String containing routing options.
  296. */
  297. setRequestOptions: function (options) {
  298. return 'AVOID_TOLL_ROADS:' + (options.tolls ? 't' : 'f') + ',' +
  299. 'AVOID_PRIMARIES:' + (options.freeways ? 't' : 'f') + ',' +
  300. 'AVOID_TRAILS:' + (options.dirt ? 't' : 'f') + ',' +
  301. 'AVOID_LONG_TRAILS:' + (options.longtrails ? 't' : 'f') + ',' +
  302. 'ALLOW_UTURNS:' + (options.uturns ? 't' : 'f');
  303. },
  304. /**
  305. * Gets the center of a segment in LonLat form.
  306. * @private
  307. * @param segment A Waze model segment object.
  308. * @return {OpenLayers.LonLat} The LonLat object corresponding to the
  309. * center of the segment.
  310. */
  311. getSegmentCenterLonLat: function (segment) {
  312. var x, y, componentsLength, midPoint;
  313. if (segment) {
  314. componentsLength = segment.geometry.components.length;
  315. midPoint = Math.floor(componentsLength / 2);
  316. if (componentsLength % 2 === 1) {
  317. x = segment.geometry.components[midPoint].x;
  318. y = segment.geometry.components[midPoint].y;
  319. } else {
  320. x = (segment.geometry.components[midPoint - 1].x +
  321. segment.geometry.components[midPoint].x) / 2;
  322. y = (segment.geometry.components[midPoint - 1].y +
  323. segment.geometry.components[midPoint].y) / 2;
  324. }
  325. return new OL.Geometry.Point(x, y).
  326. transform(W.map.getProjectionObject(), 'EPSG:4326');
  327. }
  328.  
  329. },
  330. /**
  331. * Gets the route from Live Map and executes any callbacks upon success.
  332. * @private
  333. * @returns The ajax request object. The responseJSON property of the
  334. * returned object
  335. * contains the route information.
  336. *
  337. */
  338. getRouteData: function () {
  339. var i,
  340. n,
  341. that = this;
  342. return $.ajax({
  343. dataType: 'json',
  344. url: this.getURL(),
  345. data: this.requestData,
  346. dataFilter: function (data, dataType) {
  347. return data.replace(/NaN/g, '0');
  348. },
  349. success: function (data) {
  350. that.routeData = data;
  351. for (i = 0, n = that.callbacks.length; i < n; i++) {
  352. that.callbacks[i].call(that);
  353. }
  354. }
  355. });
  356. },
  357. /**
  358. * Extracts the IDs from all segments on the route.
  359. * @private
  360. * @return {Array} Array containing an array of segment IDs for
  361. * each route alternative.
  362. */
  363. getRouteSegmentIDs: function () {
  364. var i, j, route, len1, len2, segIDs = [],
  365. routeArray = [],
  366. data = this.routeData;
  367. if ('undefined' !== typeof data.alternatives) {
  368. for (i = 0, len1 = data.alternatives.length; i < len1; i++) {
  369. route = data.alternatives[i].response.results;
  370. for (j = 0, len2 = route.length; j < len2; j++) {
  371. routeArray.push(route[j].path.segmentId);
  372. }
  373. segIDs.push(routeArray);
  374. routeArray = [];
  375. }
  376. } else {
  377. route = data.response.results;
  378. for (i = 0, len1 = route.length; i < len1; i++) {
  379. routeArray.push(route[i].path.segmentId);
  380. }
  381. segIDs.push(routeArray);
  382. }
  383. return segIDs;
  384. },
  385. /**
  386. * Gets the URL to use for the ajax request based on country.
  387. * @private
  388. * @return {String} Relative URl to use for route ajax request.
  389. */
  390. getURL: function () {
  391. if (W.model.countries.get(235) || W.model.countries.get(40)) {
  392. return '/RoutingManager/routingRequest';
  393. } else if (W.model.countries.get(106)) {
  394. return '/il-RoutingManager/routingRequest';
  395. } else {
  396. return '/row-RoutingManager/routingRequest';
  397. }
  398. },
  399. /**
  400. * Selects all segments on the route in the editor.
  401. * @param {Integer} routeIndex The index of the alternate route.
  402. * Default route to use is the first one, which is 0.
  403. */
  404. selectRouteSegments: function (routeIndex) {
  405. var i, n, seg,
  406. segIDs = this.getRouteSegmentIDs()[Math.floor(routeIndex) || 0],
  407. segments = [];
  408. if ('undefined' === typeof segIDs) {
  409. return;
  410. }
  411. for (i = 0, n = segIDs.length; i < n; i++) {
  412. seg = W.model.segments.get(segIDs[i]);
  413. if ('undefined' !== seg) {
  414. segments.push(seg);
  415. }
  416. }
  417. return W.selectionManager.select(segments);
  418. }
  419. };
  420. };
  421.  
  422. /*** INTERFACE ***/
  423. function Interface() {
  424. /**
  425. * Generates id for message bars.
  426. * @private
  427. */
  428. var getNextID = function () {
  429. var id = 1;
  430. return function () {
  431. return id++;
  432. };
  433. } ();
  434.  
  435. this.MessageBar = OL.Class(this, /** @lends wLib.Interface.MessageBar.prototype */ {
  436. $el: null,
  437. id: null,
  438. elementID: null,
  439. divStyle: {
  440. 'margin': 'auto',
  441. 'border-radius': '10px',
  442. 'text-align': 'center',
  443. 'width': '40%',
  444. 'font-size': '1em',
  445. 'font-weight': 'bold',
  446. 'color': 'white'
  447. },
  448. /**
  449. * Class to store individual message information.
  450. * @class {Backbone.Model} Message
  451. * @private
  452. */
  453. Message: Backbone.Model.extend({
  454. defaults: {
  455. messageName: null,
  456. messageType: 'info',
  457. messageText: '',
  458. displayDuration: null,
  459. skipPrefix: false
  460. }
  461. }),
  462. /**
  463. * Class to display messages on page.
  464. * @class {Object} MessageView
  465. * @private
  466. */
  467. MessageView: Backbone.View.extend({
  468. styles: {
  469. defaultStyle: {
  470. 'border-radius': '20px',
  471. 'display': 'inline-block',
  472. 'padding': '5px',
  473. 'background-color': 'rgba(0,0,0,0.7)'
  474. },
  475. error: {
  476. 'background-color': 'rgba(180,0,0,0.9)',
  477. 'color': 'black'
  478. },
  479. warn: {
  480. 'background-color': 'rgba(230,230,0,0.9)',
  481. 'color': 'black'
  482. },
  483. info: {
  484. 'background-color': 'rgba(0,0,230,0.9)'
  485. }
  486. },
  487. template: function () {
  488. var messageText = '',
  489. style,
  490. $messageEl = $('<p/>');
  491.  
  492. if (!this.model.attributes.skipPrefix && this.messagePrefix) {
  493. messageText = this.messagePrefix + ' ';
  494. }
  495.  
  496. messageText += this.model.attributes.messageText;
  497.  
  498. style = (this.model.attributes.messageType &&
  499. this.styles[this.model.attributes.messageType]) ?
  500. this.styles[this.model.attributes.messageType] : this.styles.defaultStyle;
  501. style = _.defaults(style, this.styles.defaultStyle);
  502.  
  503. $messageEl.
  504. css(style).
  505. text(messageText);
  506.  
  507. return $messageEl;
  508. },
  509. initialize: function () {
  510. this.render();
  511. },
  512. render: function () {
  513. this.$el.
  514. append(this.template()).
  515. appendTo(this.messageBar.$el).
  516. fadeIn('fast').
  517. delay(this.model.attributes.displayDuration ||
  518. this.displayDuration || 5000).
  519. fadeOut('slow', function () {
  520. $(this).remove();
  521. });
  522. return this;
  523. }
  524. }),
  525. /**
  526. * Class to hold Messages.
  527. * @class {Object} MessageCollection
  528. * @private
  529. */
  530. MessageCollection: Backbone.Collection.extend(),
  531.  
  532. messages: null,
  533. /**
  534. * An object containing message attributes.
  535. * @typedef {Object} wLib.Interface.MessageBar.messageAttributes
  536. * @property {string} [messageName] The name of the message, used for
  537. * looking up the message.
  538. * @property {string} [messageType] The name of the type/style of
  539. * the message, used for looking up the message display css options.
  540. * Default styles include 'info', 'warn', and 'error'. You can also use
  541. * a custom-defined messageType (see {@link
  542. * wLib.Interface.MessageBar.addMessageType}).
  543. * @property {string} messageText The text to display when showing the
  544. * message.
  545. * @property {number} [displayDuration] The duration to display the
  546. * message in milliseconds.
  547. * @property {boolean} [skipPrefix] Whether or not to skip the default
  548. * message prefix when displaying the message.
  549. */
  550. /**
  551. * @typedef {object} wLib.Interface.MessageBar.options
  552. * @property {array<wLib.Interface.MessageBar.messageAttributes>}
  553. * [messages] An array of objects containing message attributes to save
  554. * during initialization.
  555. * @property {string} [messagePrefix] The prefix to prepend to each
  556. * message. This can be disabled per message by using skipPrefix in the
  557. * message attributes.
  558. * @property {number} [displayDuration] The duration to display message
  559. * by default in milliseconds.
  560. * @property {object} [styles] Hash with keys representing a name for
  561. * the style/messageType and values containing objects with css
  562. * properties that style/messageType.
  563. */
  564. /**
  565. * Creates a new message bar.
  566. * @class
  567. * @name wLib.Interface.MessageBar
  568. * @param [options] {wLib.Interface.MessageBar.options} Options to set
  569. * during initialization
  570. * @example myMessageBar = new wLib.Interface.MessageBar({
  571. * messages: [{
  572. * messageName: 'exampleMessage',
  573. * messageType: 'info',
  574. * displayDuration: 5000,
  575. * skipPrefix: true
  576. * }],
  577. * displayDuration: 10000,
  578. * messagePrefix: 'My Script:',
  579. * styles: {
  580. * 'purpleBackground': {
  581. * 'background-color': 'rgba(255,0,255,0.9)'
  582. * }
  583. * }
  584. * });
  585. */
  586. initialize: function (options) {
  587. var $insertTarget = $('#search');
  588.  
  589. options = _.defaults(options || {}, {
  590. messagePrefix: null,
  591. messages: [],
  592. styles: {},
  593. displayDuration: 5000
  594. });
  595.  
  596. this.messages = new this.MessageCollection({ model: this.Message });
  597. this.id = getNextID();
  598. this.elementID = 'wlib-messagebar-' + this.id;
  599.  
  600. _(options.styles).each(function (style, name) {
  601. this.addMessageType(name, style);
  602. }, this);
  603.  
  604. _(options.messages).each(function (message) {
  605. this.messages.add(message);
  606. }, this);
  607.  
  608. this.MessageView.prototype.messagePrefix = options.messagePrefix;
  609. this.MessageView.prototype.displayDuration = options.displayDuration;
  610. this.MessageView.prototype.messageBar = this;
  611.  
  612. this.$el = $('<div/>').
  613. css(this.divStyle).
  614. attr('id', this.elementID);
  615.  
  616. wLib.Util.waitForElement($insertTarget, function () {
  617. this.$el.insertAfter($insertTarget);
  618. }, this);
  619. },
  620. /**
  621. * Adds a style for a message type.
  622. * @param {String} name The name of the message type/style.
  623. * @param style {Object} Object containing css properties and
  624. * values to use for the new messageType.
  625. */
  626. addMessageType: function (name, style) {
  627. style = style || {};
  628.  
  629. if (name) {
  630. this.MessageView.prototype.styles[name] = style;
  631. }
  632.  
  633. return this;
  634. },
  635. /**
  636. * Removes the message bar from the page.
  637. */
  638. destroy: function () {
  639. this.messages.reset();
  640. this.$el.remove();
  641. },
  642. /**
  643. * Displays a message.
  644. * @param {(string|wLib.Interface.MessageBar.messageAttributes)} message A hash with message options for a new message or the name of a saved message to look up.
  645. * @example myMessageBar.displayMessage('previouslysavedmessage');
  646. * @example myMessageBar.displayMessage({
  647. * messageName: 'newMessage',
  648. * messageType: 'warn',
  649. * displayDuration: 5000,
  650. * skipPrefix: true
  651. * });
  652. */
  653. displayMessage: function (message) {
  654. if (typeof message === 'string') {
  655. // look up message by name and display
  656. message = this.messages.findWhere({ 'messageName': message });
  657. } else {
  658. // add the new message object to the collection and display
  659. message = this.messages.add(message);
  660. }
  661. new this.MessageView({
  662. model: message
  663. });
  664. if (!message.attributes.messageName) {
  665. this.messages.remove(message);
  666. }
  667. },
  668. /**
  669. * Adds a new message to the collection of saved messages.
  670. * @param {wLib.Interface.MessageBar.messageAttributes} messageObject
  671. * The attributes to use for the new message.
  672. * @example myMessageBar.saveMessage({
  673. * messageName: 'exampleMessage',
  674. * messageType: 'info',
  675. * displayDuration: 5000,
  676. * skipPrefix: true
  677. * });
  678. */
  679. saveMessage: function (messageObject) {
  680. this.messages.add(messageObject);
  681. return this;
  682. }
  683. });
  684.  
  685. this.Options = OL.Class(this,
  686. /** @lends wLib.Interface.Options.prototype */ {
  687. localStorageName: null,
  688. options: {},
  689. /**
  690. * Creates a new Options object to handle saving and retrieving
  691. * script options. During initialization, any options stored under
  692. * the named key in localStorage will be loaded. Any options
  693. * provided as a parameter to the constructor will be applied to
  694. * the retrieved options and thus may overwrite any stored values.
  695. * @name wLib.Interface.Options
  696. * @class
  697. * @param {String} name The string used as the localStorage key
  698. * under which to store the options.
  699. * @param {Object} options A hash containing options to set during
  700. * initialization.
  701. * @example var myOptions = new wLib.Interface.Options('thebestscriptever', {scriptVersion: x});
  702. * myOptions.set('option1', true);
  703. * myOptions.set({'option2': false, 'option3': 'very true'});
  704. * myOptions.get('option2') === false // true;
  705. */
  706. initialize: function (name, options) {
  707. var i = 1;
  708.  
  709. if (window.localStorage && typeof name === 'string') {
  710.  
  711. this.localStorageName = name.toLowerCase().
  712. replace(/[^a-z]/g, '');
  713.  
  714. if (localStorage.getItem(this.localStorageName)) {
  715. while (localStorage.getItem(
  716. this.localStorageName + i)) {
  717. i += 1;
  718. }
  719. this.localStorageName = this.localStorageName + i;
  720. }
  721.  
  722. this.retrieveOptions();
  723.  
  724. if (options && _.isObject(options)) {
  725. this.set(options);
  726. }
  727. }
  728. },
  729. /**
  730. * Clears all stored options.
  731. */
  732. clear: function () {
  733. this.options = null;
  734. this.saveOptions();
  735. return this;
  736. },
  737. /**
  738. * Retrieves a stored value. If no key is specified, the entire
  739. * options object is returned.
  740. * @param {String} key Optional. The key to retrieve.
  741. */
  742. get: function (key) {
  743. return key && this.options[key] || this.options;
  744. },
  745. /**
  746. * Saves the options to localStorage
  747. * @private
  748. */
  749. saveOptions: function () {
  750. localStorage[this.localStorageName] =
  751. JSON.stringify(this.options);
  752. },
  753. /**
  754. * Stores a value under the provided key. Provide either an object
  755. * hash of keys and values to store as a single parameter or
  756. * provide a key and value as two parameters.
  757. * @param {Object} key The name of the option. Can be string,
  758. * number if providing a value as the second parameter, or a hash
  759. * of multiple options (see function description).
  760. * @param {Any} value The value to store. Not used if providing a
  761. * hash as the first argument.
  762. * @example myOptions.set('option1', true); // or
  763. * myOptions.set({'option2': false, 'option3': 'very true'});
  764. */
  765. set: function (key, value) {
  766. var j;
  767. if ((typeof key === 'string' || !isNaN(key)) && value) {
  768. this.options[key] = value;
  769. } else if (_.isObject(key)) {
  770. for (j in key) {
  771. if (key.hasOwnProperty(j)) {
  772. this.options[j] = key[j];
  773. }
  774. }
  775. }
  776. this.saveOptions();
  777. return this;
  778. },
  779. /**
  780. * Retrieves options previously stored in localStorage.
  781. * @private
  782. */
  783. retrieveOptions: function () {
  784. var options = localStorage[this.localStorageName];
  785. if (options) {
  786. this.options = options;
  787. }
  788. }
  789. });
  790.  
  791. this.Shortcut = OL.Class(this, /** @lends wLib.Interface.Shortcut.prototype */ {
  792. name: null,
  793. group: null,
  794. shortcut: {},
  795. callback: null,
  796. scope: null,
  797. groupExists: false,
  798. actionExists: false,
  799. eventExists: false,
  800. defaults: {
  801. group: 'default'
  802. },
  803. /**
  804. * Creates a new {wLib.Interface.Shortcut}.
  805. * @class
  806. * @name wLib.Interface.Shortcut
  807. * @param name {String} The name of the shortcut.
  808. * @param group {String} The name of the shortcut group.
  809. * @param shortcut {String} The shortcut key(s). The shortcut
  810. * should be of the form 'i' where i is the keyboard shortuct or
  811. * include modifier keys such as 'CSA+i', where C = the control
  812. * key, S = the shift key, A = the alt key, and i = the desired
  813. * keyboard shortcut. The modifier keys are optional.
  814. * @param callback {Function} The function to be called by the
  815. * shortcut.
  816. * @param scope {Object} The object to be used as this by the
  817. * callback.
  818. * @return {wLib.Interface.Shortcut} The new shortcut object.
  819. * @example //Creates new shortcut and adds it to the map.
  820. * shortcut = new wLib.Interface.Shortcut('myName', 'myGroup', 'C+p', callbackFunc, null).add();
  821. */
  822. initialize: function (name, group, shortcut, callback, scope) {
  823. if ('string' === typeof name && name.length > 0 &&
  824. 'string' === typeof shortcut && shortcut.length > 0 &&
  825. 'function' === typeof callback) {
  826. this.name = name;
  827. this.group = group || this.defaults.group;
  828. this.callback = callback;
  829. this.shortcut[shortcut] = name;
  830. if ('object' !== typeof scope) {
  831. this.scope = null;
  832. } else {
  833. this.scope = scope;
  834. }
  835. return this;
  836. }
  837. },
  838. /**
  839. * Determines if the shortcut's group already exists.
  840. * @private
  841. */
  842. doesGroupExist: function () {
  843. this.groupExists = 'undefined' !== typeof W.accelerators.Groups[this.group] &&
  844. undefined !== typeof W.accelerators.Groups[this.group].members &&
  845. W.accelerators.Groups[this.group].length > 0;
  846. return this.groupExists;
  847. },
  848. /**
  849. * Determines if the shortcut's action already exists.
  850. * @private
  851. */
  852. doesActionExist: function () {
  853. this.actionExists = 'undefined' !== typeof W.accelerators.Actions[this.name];
  854. return this.actionExists;
  855. },
  856. /**
  857. * Determines if the shortcut's event already exists.
  858. * @private
  859. */
  860. doesEventExist: function () {
  861. this.eventExists = 'undefined' !== typeof W.accelerators.events.listeners[this.name] &&
  862. W.accelerators.events.listeners[this.name].length > 0 &&
  863. this.callback === W.accelerators.events.listeners[this.name][0].func &&
  864. this.scope === W.accelerators.events.listeners[this.name][0].obj;
  865. return this.eventExists;
  866. },
  867. /**
  868. * Creates the shortcut's group.
  869. * @private
  870. */
  871. createGroup: function () {
  872. W.accelerators.Groups[this.group] = [];
  873. W.accelerators.Groups[this.group].members = [];
  874. },
  875. /**
  876. * Registers the shortcut's action.
  877. * @private
  878. */
  879. addAction: function () {
  880. W.accelerators.addAction(this.name, { group: this.group });
  881. },
  882. /**
  883. * Registers the shortcut's event.
  884. * @private
  885. */
  886. addEvent: function () {
  887. W.accelerators.events.register(this.name, this.scope, this.callback);
  888. },
  889. /**
  890. * Registers the shortcut's keyboard shortcut.
  891. * @private
  892. */
  893. registerShortcut: function () {
  894. W.accelerators._registerShortcuts(this.shortcut);
  895. },
  896. /**
  897. * Adds the keyboard shortcut to the map.
  898. * @return {wLib.Interface.Shortcut} The keyboard shortcut.
  899. */
  900. add: function () {
  901. /* If the group is not already defined, initialize the group. */
  902. if (!this.doesGroupExist()) {
  903. this.createGroup();
  904. }
  905.  
  906. /* Clear existing actions with same name */
  907. if (this.doesActionExist()) {
  908. W.accelerators.Actions[this.name] = null;
  909. }
  910. this.addAction();
  911.  
  912. /* Register event only if it's not already registered */
  913. if (!this.doesEventExist()) {
  914. this.addEvent();
  915. }
  916.  
  917. /* Finally, register the shortcut. */
  918. this.registerShortcut();
  919. return this;
  920. },
  921. /**
  922. * Removes the keyboard shortcut from the map.
  923. * @return {wLib.Interface.Shortcut} The keyboard shortcut.
  924. */
  925. remove: function () {
  926. if (this.doesEventExist()) {
  927. W.accelerators.events.unregister(this.name, this.scope, this.callback);
  928. }
  929. if (this.doesActionExist()) {
  930. delete W.accelerators.Actions[this.name];
  931. }
  932. //remove shortcut?
  933. return this;
  934. },
  935. /**
  936. * Changes the keyboard shortcut and applies changes to the map.
  937. * @return {wLib.Interface.Shortcut} The keyboard shortcut.
  938. */
  939. change: function (shortcut) {
  940. if (shortcut) {
  941. this.shortcut = {};
  942. this.shortcut[shortcut] = this.name;
  943. this.registerShortcut();
  944. }
  945. return this;
  946. }
  947. }),
  948.  
  949. this.Tab = OL.Class(this, {
  950. /** @lends wLib.Interface.Tab */
  951. TAB_SELECTOR: '#user-tabs ul.nav-tabs',
  952. CONTENT_SELECTOR: '#user-info div.tab-content',
  953. callback: null,
  954. $content: null,
  955. context: null,
  956. $tab: null,
  957. /**
  958. * Creates a new wLib.Interface.Tab. The tab is appended to the WME
  959. * editor sidebar and contains the passed HTML content.
  960. * @class
  961. * @name wLib.Interface.Tab
  962. * @param {String} name The name of the tab. Should not contain any
  963. * special characters.
  964. * @param {String} content The HTML content of the tab.
  965. * @param {Function} [callback] A function to call upon successfully
  966. * appending the tab.
  967. * @param {Object} [context] The context in which to call the callback
  968. * function.
  969. * @return {wLib.Interface.Tab} The new tab object.
  970. * @example //Creates new tab and adds it to the page.
  971. * new wLib.Interface.Tab('thebestscriptever', '<div>Hello World!</div>');
  972. */
  973. initialize: function (name, content, callback, context) {
  974. var idName, i = 0;
  975. if (name && 'string' === typeof name &&
  976. content && 'string' === typeof content) {
  977. if (callback && 'function' === typeof callback) {
  978. this.callback = callback;
  979. this.context = context || callback;
  980. }
  981. /* Sanitize name for html id attribute */
  982. idName = name.toLowerCase().replace(/[^a-z-_]/g, '');
  983. /* Make sure id will be unique on page */
  984. while (
  985. $('#sidepanel-' + (i ? idName + i : idName)).length > 0) {
  986. i++;
  987. }
  988. if (i) {
  989. idName = idName + i;
  990. }
  991. /* Create tab and content */
  992. this.$tab = $('<li/>')
  993. .append($('<a/>')
  994. .attr({
  995. 'href': '#sidepanel-' + idName,
  996. 'data-toggle': 'tab',
  997. })
  998. .text(name));
  999. this.$content = $('<div/>')
  1000. .addClass('tab-pane')
  1001. .attr('id', 'sidepanel-' + idName)
  1002. .html(content);
  1003.  
  1004. this.appendTab();
  1005. }
  1006. },
  1007.  
  1008. append: function (content) {
  1009. this.$content.append(content);
  1010. },
  1011.  
  1012. appendTab: function () {
  1013. wLib.Util.waitForElement(
  1014. this.TAB_SELECTOR + ',' + this.CONTENT_SELECTOR,
  1015. function () {
  1016. $(this.TAB_SELECTOR).append(this.$tab);
  1017. $(this.CONTENT_SELECTOR).first().append(this.$content);
  1018. if (this.callback) {
  1019. this.callback.call(this.context);
  1020. }
  1021. }, this);
  1022. },
  1023.  
  1024. clearContent: function () {
  1025. this.$content.empty();
  1026. },
  1027.  
  1028. destroy: function () {
  1029. this.$tab.remove();
  1030. this.$content.remove();
  1031. }
  1032. });
  1033. };
  1034.  
  1035. /*** Utilities ***/
  1036. function Util() {
  1037. /**
  1038. * Function to track the ready state of the map.
  1039. * @function wLib.Util.mapReady
  1040. * @return {Boolean} Whether or not a map operation is pending or
  1041. * undefined if the function has not yet seen a map ready event fired.
  1042. */
  1043. this.mapReady = function () {
  1044. var mapReady = true;
  1045. W.vent.on('operationPending', function () {
  1046. mapReady = false;
  1047. });
  1048. W.vent.on('operationDone', function () {
  1049. mapReady = true;
  1050. });
  1051. return function () {
  1052. return mapReady;
  1053. };
  1054. } ();
  1055.  
  1056. /**
  1057. * Function to track the ready state of the model.
  1058. * @function wLib.Util.modelReady
  1059. * @return {Boolean} Whether or not the model has loaded objects or
  1060. * undefined if the function has not yet seen a model ready event fired.
  1061. */
  1062. this.modelReady = function () {
  1063. var modelReady = true;
  1064. W.model.events.register('mergestart', null, function () {
  1065. modelReady = false;
  1066. });
  1067. W.model.events.register('mergeend', null, function () {
  1068. modelReady = true;
  1069. });
  1070. return function () {
  1071. return modelReady;
  1072. };
  1073. } ();
  1074. /**
  1075. * Function to defer function execution until an element is present on
  1076. * the page.
  1077. * @function wLib.Util.waitForElement
  1078. * @param {String} selector The CSS selector string or a jQuery object
  1079. * to find before executing the callback.
  1080. * @param {Function} callback The function to call when the page
  1081. * element is detected.
  1082. * @param {Object} [context] The context in which to call the callback.
  1083. */
  1084. this.waitForElement = function (selector, callback, context) {
  1085. var jqObj;
  1086.  
  1087. if (!selector || typeof callback !== 'function') {
  1088. return;
  1089. }
  1090.  
  1091. jqObj = typeof selector === 'string' ?
  1092. $(selector) : selector instanceof $ ? selector : null;
  1093.  
  1094. if (!jqObj.size()) {
  1095. window.requestAnimationFrame(function () {
  1096. wLib.Util.waitForElement(selector, callback, context);
  1097. });
  1098. } else {
  1099. callback.call(context || callback);
  1100. }
  1101. };
  1102. /**
  1103. * Returns a callback function in the appropriate scope.
  1104. * @function wLib.Util.createCallback
  1105. * @private
  1106. * @param {function} func The callback function.
  1107. * @param {object} [scope] The scope in which to call the callback.
  1108. * @return {function} A function that returns the callback function
  1109. * called in the appropriate scope.
  1110. */
  1111. this.createCallback = function (func, scope) {
  1112. return typeof func === 'function' && function () {
  1113. return func.call(scope || func);
  1114. };
  1115. };
  1116.  
  1117. };
  1118.  
  1119. /*** API ***/
  1120. function API() {
  1121.  
  1122. this.UpdateFeatureAddress = function () {
  1123. return require('Waze/Action/UpdateFeatureAddress');
  1124. } ();
  1125.  
  1126. this.UpdateFeatureGeometry = function () {
  1127. return require('Waze/Action/UpdateFeatureGeometry');
  1128. } ();
  1129.  
  1130. this.UpdateObject = function () {
  1131. return require('Waze/Action/UpdateObject');
  1132. } ();
  1133.  
  1134. this.UpdateSegmentGeometry = function () {
  1135. return require('Waze/Action/UpdateSegmentGeometry');
  1136. } ();
  1137. /**
  1138. * Updates the address for a place (venue).
  1139. * @function wLib.api.updateFeatureAddress
  1140. * @param {(String|Venue)} place A string containing the id of
  1141. * the venue to change or the venue model object itself.
  1142. * @param address {Object} An object containing the country, state, city,
  1143. * and street objects to use as the new address.
  1144. * objects.
  1145. */
  1146. this.updateFeatureAddress = function (place, address) {
  1147. 'use strict';
  1148. var newAttributes,
  1149. UpdateFeatureAddress = require('Waze/Action/UpdateFeatureAddress');
  1150. if (typeof place === 'string') {
  1151. place = W.model.venues.get(place);
  1152. }
  1153. if (place && place.CLASS_NAME === 'Waze.Feature.Vector.Landmark' &&
  1154. address && address.state && address.country) {
  1155. newAttributes = {
  1156. countryID: address.country.id,
  1157. stateID: address.state.id,
  1158. cityName: address.city.name,
  1159. emptyCity: address.city.name ? null : true,
  1160. streetName: address.street.name,
  1161. emptyStreet: address.street.name ? null : true
  1162. };
  1163. W.model.actionManager.add(
  1164. new UpdateFeatureAddress(place, newAttributes));
  1165. }
  1166. };
  1167. /**
  1168. * Updates the geometry of a place (venue) in WME.
  1169. * @function wLib.api.updateFeatureGeometry
  1170. * @param {(String|Venue)} place A string containing the id of
  1171. * the venue to change or the venue model object itself.
  1172. * @param {(OL.Geometry.Point|OL.Geometry.Polygon)} newGeometry The
  1173. * geometry object to use as the new geometry for the venue.
  1174. */
  1175. this.updateFeatureGeometry = function (place, newGeometry) {
  1176. var oldGeometry,
  1177. model = W.model.venues,
  1178. WMEUPdateFeatureGeometry = require('Waze/Action/UpdateFeatureGeometry');
  1179. if (typeof place === 'string') {
  1180. place = W.model.venues.get(place);
  1181. }
  1182. if (place && place.CLASS_NAME === 'Waze.Feature.Vector.Landmark' &&
  1183. newGeometry && (newGeometry instanceof OL.Geometry.Point ||
  1184. newGeometry instanceof OL.Geometry.Polygon)) {
  1185. oldGeometry = place.attributes.geometry.clone();
  1186. W.model.actionManager.add(
  1187. new WMEUPdateFeatureGeometry(place, model, oldGeometry,
  1188. newGeometry));
  1189. }
  1190. };
  1191. };
  1192. }.call(this));