wLib

A WME developers library

目前为 2016-01-17 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/9794/101707/wLib.js

// ==UserScript==
// @name            wLib
// @description     A WME developers library
// @version         1.0.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: '1.0.0' };
}).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: 5000
            },
            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) {
                if (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', function () {
                        $(this).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;
            }
        }),
    this.Interface.Tab = OL.Class(this.Interface, {
        /** @lends wLib.Interface.Tab */
        TAB_SELECTOR: '#user-tabs ul.nav-tabs',
        CONTENT_SELECTOR: '#user-info > div.tab-content',
        /**
			* Creates a new {wLib.Interface.Tab}. The tab is appended to the WME editor sidebar and
            * contains the passed HTML content.
			* @class
			* @name wLib.Interface.Tab
			* @param name {String} The name of the tab. Should not contain any special characters.
			* @param content {String} The HTML content of the tab.
            * @param callback {Function} A function to call upon successfully appending the tab.
			* @return {wLib.Interface.Tab} The new tab object.
			* @example //Creates new tab and adds it to the page.
			* new wLib.Interface.Tab('thebestscriptever', '<div>Hello World!</div>');
			*/
        initialize: function (name, content, callback) {
            var idName, i = 0;
            if (name && 'string' === typeof name &&
                content && 'string' === typeof content) {
                if (callback && 'function' === typeof callback) {
                    this.callback = callback;
                } else {
                    this.callback = null;
                }
                /* Sanitize name for html id attribute */
                idName = name.replace(/\s/, '').toLowerCase();
                /* Make sure id will be unique on page */
                while (
                    $('#sidepanel-' + (i ? idName + i : idName)).length > 0) {
                    i++;
                }
                if (i) {
                    idName = idName + i;
                }
                /* Create tab and content */
                this.$tab = $('<li/>')
                    .append($('<a/>')
                        .attr({
                            'href': '#sidepanel-' + idName,
                            'data-toggle': 'tab',
                        })
                        .text(name));
                this.$content = $('<div/>')
                    .addClass('tab-pane')
                    .attr('id', 'sidepanel-' + idName)
                    .html(content);

                this.appendTab();
            }
        },
        appendTab: function (tries) {
            tries = tries || 1;
            if (tries > 20) {
                return;
            }
            if ($(this.TAB_SELECTOR).length > 0 &&
                $(this.CONTENT_SELECTOR).length > 0) {
                $(this.TAB_SELECTOR).append(this.$tab);
                $(this.CONTENT_SELECTOR).append(this.$content);
                if (this.callback) {
                    this.callback();
                }
            } else {
                window.setTimeout(function () {
                    this.appendTab(tries + 1);
                }, 500);
            }
        }
    });
}).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);