wLib

A WME developers library

目前為 2015-09-24 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/9794/76140/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://greasyfork.org/users/9321
// ==/UserScript==

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

(function () {
	/**
	* The wLib namespace.
	* @namespace
	* @global
	*/
	this.wLib = { VERSION: '0.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').remove();
			},
			/**
			 * 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);