wLib

A WME developers library

Tính đến 24-09-2015. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/9794/76142/wLib.js

// ==UserScript==
// @name            wLib
// @description     A WME developers library
// @version         0.1.1
// @author          SAR85
// @copyright       SAR85
// @license         CC BY-NC-ND
// @grant           none
// @include         https://www.waze.com/editor/*
// @include         https://www.waze.com/*/editor/*
// @include         https://editor-beta.waze.com/*
// @namespace       https://greatest.deepsurf.us/users/9321
// ==/UserScript==

/* global W */
/* global OL */
/* global wLib */
/* global $ */

(function () {
	/**
	* The wLib namespace.
	* @namespace
	* @global
	*/
	this.wLib = { VERSION: '0.1.1' };
}).call(this);

/*** GEOMETRY ***/
(function () {
	/**
	* Namespace for functions related to geometry.
	* @memberof wLib
	* @namespace
	* @name wLib.Geometry
	*/
	this.Geometry = {
		/**
		* Determines if an {OpenLayers.Geometry} is within the map view.
		* @memberof wLib.Geometry
		* @param geometry {OpenLayers.Geometry}
		* @return {Boolean} Whether or not the geometry is in the map extent.
		*/
		isGeometryInMapExtent: function (geometry) {
			'use strict';
			return geometry && geometry.getBounds &&
				W.map.getExtent().intersectsBounds(geometry.getBounds());
		},
		/**
		 * Determines if an {OpenLayers.LonLat} is within the map view.
		 * @memberof wLib.Geometry
		 * @param {OpenLayers.LonLat} lonlat
		 * @return {Boolean} Whether or not the LonLat is in the map extent.
		 */
		isLonLatInMapExtent: function (lonlat) {
			'use strict';
			return lonlat && W.map.getExtent().containsLonLat(lonlat);
		}
	};
}).call(wLib);

/*** MODEL ***/
(function () {
	/**
	* Namespace for functions related to the model.
	* @memberof wLib
	* @namespace
	* @name wLib.Model
	*/
	this.Model = {};

	/**
	* Gets the IDs of any selected segments.
	* @memberof wLib.Model
	* @return {Array} Array containing the IDs of selected segments.
	*/
	this.Model.getSelectedSegmentIDs = function () {
		'use strict';
		var i, n, selectedItems, item, segments = [];
		if (!W.selectionManager.hasSelectedItems()) {
			return false;
		} else {
			selectedItems = W.selectionManager.selectedItems;
			for (i = 0, n = selectedItems.length; i < n; i++) {
				item = selectedItems[i].model;
				if ('segment' === item.type) {
					segments.push(item.attributes.id);
				}
			}
			return segments.length === 0 ? false : segments;
		}
	};

	/**
	 * Retrives a route from the Waze Live Map.
	 * @class
	 * @name wLib.Model.RouteSelection
	 * @param firstSegment The segment to use as the start of the route.
	 * @param lastSegment The segment to use as the destination for the route.
	 * @param {Array|Function} callback A function or array of funcitons to be executed after the route
	 * is retrieved. 'This' in the callback functions will refer to the RouteSelection object.
	 * @param {Object} options A hash of options for determining route. Valid options are:
	 * fastest: {Boolean} Whether or not the fastest route should be used. Default is false,
	 * which selects the shortest route.
	 * freeways: {Boolean} Whether or not to avoid freeways. Default is false.
	 * dirt: {Boolean} Whether or not to avoid dirt roads. Default is false.
	 * longtrails: {Boolean} Whether or not to avoid long dirt roads. Default is false.
	 * uturns: {Boolean} Whether or not to allow U-turns. Default is true.
	 * @return {wLib.Model.RouteSelection} The new RouteSelection object.
	 * @example: // The following example will retrieve a route from the Live Map and select the segments in the route.
	 * selection = W.selectionManager.selectedItems;
	 * myRoute = new wLib.Model.RouteSelection(selection[0], selection[1], function(){this.selectRouteSegments();}, {fastest: true});
	 */
	this.Model.RouteSelection = function (firstSegment, lastSegment, callback, options) {
		var i,
			n,
			start = this.getSegmentCenterLonLat(firstSegment),
			end = this.getSegmentCenterLonLat(lastSegment);
		this.options = {
			fastest: options && options.fastest || false,
			freeways: options && options.freeways || false,
			dirt: options && options.dirt || false,
			longtrails: options && options.longtrails || false,
			uturns: options && options.uturns || true
		};
		this.requestData = {
			from: 'x:' + start.x + ' y:' + start.y + ' bd:true',
			to: 'x:' + end.x + ' y:' + end.y + ' bd:true',
			returnJSON: true,
			returnGeometries: true,
			returnInstructions: false,
			type: this.options.fastest ? 'HISTORIC_TIME' : 'DISTANCE',
			clientVersion: '4.0.0',
			timeout: 60000,
			nPaths: 3,
			options: this.setRequestOptions(this.options)
		};
		this.callbacks = [];
		if (callback) {
			if (!(callback instanceof Array)) {
				callback = [callback];
			}
			for (i = 0, n = callback.length; i < n; i++) {
				if ('function' === typeof callback[i]) {
					this.callbacks.push(callback[i])
				}
			}
		}
		this.routeData = null;
		this.getRouteData();
	};
	this.Model.RouteSelection.prototype = /** @lends wLib.Model.RouteSelection.prototype */ {
		/**
		 * Formats the routing options string for the ajax request.
		 * @private
		 * @param {Object} options Object containing the routing options.
		 * @return {String} String containing routing options.
		 */
		setRequestOptions: function (options) {
			return 'AVOID_TOLL_ROADS:' + (options.tolls ? 't' : 'f') + ',' +
				'AVOID_PRIMARIES:' + (options.freeways ? 't' : 'f') + ',' +
				'AVOID_TRAILS:' + (options.dirt ? 't' : 'f') + ',' +
				'AVOID_LONG_TRAILS:' + (options.longtrails ? 't' : 'f') + ',' +
				'ALLOW_UTURNS:' + (options.uturns ? 't' : 'f');
		},
		/**
		 * Gets the center of a segment in LonLat form.
		 * @private
		 * @param segment A Waze model segment object.
		 * @return {OpenLayers.LonLat} The LonLat object corresponding to the
		 * center of the segment.
		 */
		getSegmentCenterLonLat: function (segment) {
			var x, y, componentsLength, midPoint;
			if (segment) {
				componentsLength = segment.geometry.components.length;
				midPoint = Math.floor(componentsLength / 2);
				if (componentsLength % 2 === 1) {
					x = segment.geometry.components[midPoint].x;
					y = segment.geometry.components[midPoint].y;
				} else {
					x = (segment.geometry.components[midPoint - 1].x +
						segment.geometry.components[midPoint].x) / 2;
					y = (segment.geometry.components[midPoint - 1].y +
						segment.geometry.components[midPoint].y) / 2;
				}
				return new OL.Geometry.Point(x, y).transform(W.map.getProjectionObject(), 'EPSG:4326');
			}

		},
		/**
		 * Gets the route from Live Map and executes any callbacks upon success.
		 * @private
		 * @returns The ajax request object. The responseJSON property of the returned object
		 * contains the route information.
		 *
		 */
		getRouteData: function () {
			var i,
				n,
				that = this;
			return $.ajax({
				dataType: "json",
				url: this.getURL(),
				data: this.requestData,
				dataFilter: function (data, dataType) {
					return data.replace(/NaN/g, '0');
				},
				success: function (data) {
					that.routeData = data;
					for (i = 0, n = that.callbacks.length; i < n; i++) {
						that.callbacks[i].call(that);
					}
				}
			});
		},
		/**
		 * Extracts the IDs from all segments on the route.
		 * @private
		 * @return {Array} Array containing an array of segment IDs for
		 * each route alternative.
		 */
		getRouteSegmentIDs: function () {
			var i, j, route, len1, len2, segIDs = [],
				routeArray = [],
				data = this.routeData;
			if ('undefined' !== typeof data.alternatives) {
				for (i = 0, len1 = data.alternatives.length; i < len1; i++) {
					route = data.alternatives[i].response.results;
					for (j = 0, len2 = route.length; j < len2; j++) {
						routeArray.push(route[j].path.segmentId);
					}
					segIDs.push(routeArray);
					routeArray = [];
				}
			} else {
				route = data.response.results;
				for (i = 0, len1 = route.length; i < len1; i++) {
					routeArray.push(route[i].path.segmentId);
				}
				segIDs.push(routeArray);
			}
			return segIDs;
		},
		/**
		 * Gets the URL to use for the ajax request based on country.
		 * @private
		 * @return {String} Relative URl to use for route ajax request.
		 */
		getURL: function () {
			if (W.model.countries.get(235) || W.model.countries.get(40)) {
				return '/RoutingManager/routingRequest';
			} else if (W.model.countries.get(106)) {
				return '/il-RoutingManager/routingRequest';
			} else {
				return '/row-RoutingManager/routingRequest';
			}
		},
		/**
		 * Selects all segments on the route in the editor.
		 * @param {Integer} routeIndex The index of the alternate route.
		 * Default route to use is the first one, which is 0.
		 */
		selectRouteSegments: function (routeIndex) {
			var i, n, seg,
				segIDs = this.getRouteSegmentIDs()[Math.floor(routeIndex) || 0],
				segments = [];
			if (undefined === typeof segIDs) {
				return;
			}
			for (i = 0, n = segIDs.length; i < n; i++) {
				seg = W.model.segments.get(segIDs[i])
				if (undefined !== seg) {
					segments.push(seg);
				}
			}
			return W.selectionManager.select(segments);
		}
	};
}).call(wLib);

/*** INTERFACE ***/
(function () {
	/**
	* Namespace for functions related to the WME interface
	* @memberof wLib
	* @namespace
	* @name wLib.Interface
	*/
	this.Interface = {};

	this.Interface.MessageBar = OL.Class(this.Interface,
		/** @lends wLib.Interface.MessageBar.prototype */ {
			$el: null,
			messages: {
				exampleMessage: {
					messageType: 'info',
					messageText: 'This is an example message.',
					displayDuration: 5000,
					skipPrefix: true
				}
			},
			options: {
				messagePrefix: null,
				displayDuration: 2000
			},
			styles: {
				defaultStyle: {
					'border-radius': 'inherit',
					'background-color': 'rgba(0,0,0,0.7)'
				},
				error: {
					'border-radius': 'inherit',
					'background-color': 'rgba(180,0,0,0.9)',
					'color': 'black'
				},
				warn: {
					'border-radius': 'inherit',
					'background-color': 'rgba(230,230,0,0.9)',
					'color': 'black'
				},
				info: {
					'border-radius': 'inherit',
					'background-color': 'rgba(0,0,230,0.9)'
				}
			},
			/**
			 * Creates a new {wLib.Interface.MessageBar}.
			 * @class
			 * @name wLib.Interface.MessageBar
			 * @param options {Object} Object containing options to use for the message bar.
			 * Valid options are: messagePrefix (prefix to prepend to each message; can be
			 * disabled per message by using skipPrefix), displayDuration (default duration to
			 * display messages, styles (object with keys representing messageType names and values
			 * containing objects with css properties for the messageType.)
			 */
			initialize: function (options) {
				var $insertTarget = $('#search'),
					divStyle = {
						'margin': 'auto',
						'border-radius': '10px',
						'text-align': 'center',
						'width': '40%',
						'font-size': '1em',
						'font-weight': 'bold',
						'color': 'white'
					};
				OL.Util.extend(this.options, options);
				if (this.options.styles) {
					OL.Util.extend(this.styles, this.options.styles);
				}
				if (this.options.messages) {
					OL.Util.extend(this.messages, this.options.messages);
				}
				this.$el = $('<div/>').css(divStyle);
				if ($insertTarget.length > 0) {
					this.$el.insertAfter($insertTarget);
				} else {
					console.error('wLib: Unable to find insertTarget for MessageBar.');
				}
			},
			/**
			 * Adds a style for a message type.
			 * @param name {String} The name of the messageType.
			 * @param style {Object} Object containing css properties and values to use
			 * for the new messageType.
			 */
			addMessageType: function (name, style) {
				this.styles[name] = style;
				return this;
			},
			/**
			 * Removes the message bar from the page.
			 */
			destroy: function () {
				this.$el.remove();
			},
			/**
			 * Displays a message.
			 * @private
			 * @param message The message object or the name of the message to look up.
			 * @param lookupName {Boolean} If true, message parameter should be string
			 * and this string is used as message name to look up in saved messages.
			 */
			displayMessage: function (message, lookupName) {
				var messageText = '',
					style,
					duration,
					$messageEl = $('<p/>');
				// Lookup saved message by name if option is specified.
				if (lookupName) {
					if (this.messages[message]) {
						message = this.messages[message];
					} else {
						console.debug('wLib: MessageBar: saved message not found.');
						return;
					}
				}
				// Make sure message has at least text.
				if (!message.messageText) {
					return;
				}
				// Append prefix if one exists and skipPrefix is not specified.
				if (!message.skipPrefix && this.options.messagePrefix) {
					messageText = this.options.messagePrefix + ' ';
				}
				// Add messageText
				messageText += message.messageText;
				// Set style
				style = (message.messageType && this.styles[message.messageType]) ?
					this.styles[message.messageType] : this.styles.defaultStyle;
				// Set duration
				duration = (message.displayDuration && !isNaN(message.displayDuration)) ?
					message.displayDuration : this.options.displayDuration;
				// Update element attributes and add to page
				$messageEl.css(style).text(messageText).appendTo(this.$el);
				// Display message
				$messageEl.fadeIn('fast').delay(duration).fadeOut('slow');
			},
			/**
			 * Hides the display.
			 * @private
			 */
			hideMessage: function ($messageEl, wait) {
				setTimeout(function () {
					this.$el.fadeOut('slow');
					$messageEl.remove();
				}, wait);
			},
			/**
			 * Displays a message in the message bar.
			 * @param messageText {String} The text of the message to display.
			 * @param options {Object} Object containing message options. Valid
			 * options are messageType (corresponds to the style to use), and
			 * displayDuration (how long to display message in milliseconds).
			 */
			postNewMessage: function (messageText, options) {
				var newMessage = {};
				if (messageText) {
					newMessage.messageText = messageText;
					if (options) {
						OL.Util.extend(newMessage, options);
					}
				}
				this.displayMessage(newMessage);
				return this;
			},
			/**
			 * Displays a saved message in the message bar.
			 * @param messageName {String} The name of the saved message to display.
			 */
			postSavedMessage: function (messageName) {
				if (messageName) {
					this.displayMessage(messageName, true);
				}
				return this;
			},
			/**
			 * Adds message to saved messages.
			 * @param messageName {String} The name of the new message to save.
			 * @param options {Object} Object containing message options. Valid
			 * options are messageType (corresponds to the style to use), messageText
			 * (the content of the message), and displayDuration (how long to display
			 * message in milliseconds).
			 */
			saveMessage: function (messageName, options) {
				var newMessage = {};
				if (messageName && options && options.messageText) {
					OL.Util.extend(newMessage, options);
					this.messages[messageName] = newMessage;
				} else {
					console.debug('wLib: MessageBar: error saving message.')
				}
				return this;
			},
		});

	this.Interface.Shortcut = OL.Class(this.Interface,
		/** @lends wLib.Interface.Shortcut.prototype */ {
			name: null,
			group: null,
			shortcut: {},
			callback: null,
			scope: null,
			groupExists: false,
			actionExists: false,
			eventExists: false,
			/**
			* Creates a new {wLib.Interface.Shortcut}.
			* @class
			* @name wLib.Interface.Shortcut
			* @param name {String} The name of the shortcut.
			* @param group {String} The name of the shortcut group.
			* @param shortcut {String} The shortcut key(s). The shortcut should be of the form
			* 'i' where i is the keyboard shortuct or include modifier keys such as'CSA+i',
			* where C = the control key, S = the shift key, A = the alt key, and
			* i = the desired keyboard shortcut. The modifier keys are optional.
			* @param callback {Function} The function to be called by the shortcut.
			* @param scope {Object} The object to be used as this by the callback.
			* @return {wLib.Interface.Shortcut} The new shortcut object.
			* @example //Creates new shortcut and adds it to the map.
			* shortcut = new wLib.Interface.Shortcut('myName', 'myGroup', 'C+p', callbackFunc, null).add();
			*/
			initialize: function (name, group, shortcut, callback, scope) {
				var defaults = { group: 'default' };
				this.CLASS_NAME = 'wLib Shortcut';
				if ('string' === typeof name && name.length > 0 &&
					'string' === typeof shortcut && shortcut.length > 0 &&
					'function' === typeof callback) {
					this.name = name;
					this.group = group || defaults.group;
					this.callback = callback;
					this.shortcut[shortcut] = name;
					if ('object' !== typeof scope) {
						this.scope = null;
					} else {
						this.scope = scope;
					}
					return this;
				}
			},
			/**
			* Determines if the shortcut's group already exists.
			* @private
			*/
			doesGroupExist: function () {
				this.groupExists = 'undefined' !== typeof W.accelerators.Groups[this.group] &&
				undefined !== typeof W.accelerators.Groups[this.group].members &&
				W.accelerators.Groups[this.group].length > 0;
				return this.groupExists;
			},
			/**
			* Determines if the shortcut's action already exists.
			* @private
			*/
			doesActionExist: function () {
				this.actionExists = 'undefined' !== typeof W.accelerators.Actions[this.name];
				return this.actionExists;
			},
			/**
			* Determines if the shortcut's event already exists.
			* @private
			*/
			doesEventExist: function () {
				this.eventExists = 'undefined' !== typeof W.accelerators.events.listeners[this.name] &&
				W.accelerators.events.listeners[this.name].length > 0 &&
				this.callback === W.accelerators.events.listeners[this.name][0].func &&
				this.scope === W.accelerators.events.listeners[this.name][0].obj;
				return this.eventExists;
			},
			/**
			* Creates the shortcut's group.
			* @private
			*/
			createGroup: function () {
				W.accelerators.Groups[this.group] = [];
				W.accelerators.Groups[this.group].members = [];
			},
			/**
			* Registers the shortcut's action.
			* @private
			*/
			addAction: function () {
				W.accelerators.addAction(this.name, { group: this.group });
			},
			/**
			* Registers the shortcut's event.
			* @private
			*/
			addEvent: function () {
				W.accelerators.events.register(this.name, this.scope, this.callback);
			},
			/**
			* Registers the shortcut's keyboard shortcut.
			* @private
			*/
			registerShortcut: function () {
				W.accelerators.registerShortcuts(this.shortcut);
			},
			/**
			* Adds the keyboard shortcut to the map.
			* @return {wLib.Interface.Shortcut} The keyboard shortcut.
			*/
			add: function () {
				/* If the group is not already defined, initialize the group. */
				if (!this.doesGroupExist()) {
					this.createGroup();
				}

				/* Clear existing actions with same name */
				if (this.doesActionExist()) {
					W.accelerators.Actions[this.name] = null;
				}
				this.addAction();

				/* Register event only if it's not already registered */
				if (!this.doesEventExist()) {
					this.addEvent();
				}

				/* Finally, register the shortcut. */
				this.registerShortcut();
				return this;
			},
			/**
			* Removes the keyboard shortcut from the map.
			* @return {wLib.Interface.Shortcut} The keyboard shortcut.
			*/
			remove: function () {
				if (this.doesEventExist()) {
					W.accelerators.events.unregister(this.name, this.scope, this.callback);
				}
				if (this.doesActionExist()) {
					delete W.accelerators.Actions[this.name];
				}
				//remove shortcut?
				return this;
			},
			/**
			* Changes the keyboard shortcut and applies changes to the map.
			* @return {wLib.Interface.Shortcut} The keyboard shortcut.
			*/
			change: function (shortcut) {
				if (shortcut) {
					this.shortcut = {};
					this.shortcut[shortcut] = this.name;
					this.registerShortcut();
				}
				return this;
			}
		});
}).call(wLib);

/*** Utilities ***/
(function () {
	/**
	 * Namespace for utility functions.
	 * @memberof wLib
	 * @namespace
	 * @name wLib.Util
	 */
	this.Util = {};

	/**
	 * Checks if the argument is a plain object.
	 * @memberof wLib.Util
	 * @param item {Any} The item to test.
	 * @return {Boolean} True if item is plain object.
	 */
	this.Util.isObject = function (item) {
		return item && 'object' === typeof item && !Array.isArray(item);
	}
}).call(wLib);

/*** API ***/
(function () {

	/**
	* Namespace for functions related to WME actions.
	* @memberof wLib
	* @namespace
	* @name wLib.api
	*/
	this.api = {};
}).call(wLib);