Greasy Fork

WazeWrap (test)

A base library for WME script writers

目前为 2017-03-22 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.icu/scripts/24923/182620/WazeWrap%20%28test%29.js

// ==UserScript==
// @name         WazeWrap (test)
// @namespace    https://greasyfork.org/users/30701-justins83-waze
// @version      0.1.5
// @description  A base library for WME script writers
// @author       JustinS83/MapOMatic
// @include      https://beta.waze.com/*editor/*
// @include      https://www.waze.com/*editor/*
// @exclude      https://www.waze.com/*user/editor/*
// @grant        none
// ==/UserScript==

/* global W */
/* global WazeWrap */

var WazeWrap;

(function() {

    function bootstrap(tries) {
        //if (typeof(window.WazeWrap)==='undefined') {
        tries = tries || 1;
        if (window.W &&
            window.W.map &&
            window.W.model &&
            window.W.loginManager &&
            $) {
            init();
        } else if (tries < 1000) {
            setTimeout(function () { bootstrap(tries++); }, 200);
        } else {
            console.log('WazeWrap failed to load');
        }
        //}
    }

    bootstrap();

    function init(){
        //extendModel();
        //extendGeometry();

        var oldLib = WazeWrap;

        var root = this;
        WazeWrap = {};

        WazeWrap.Version = "0.1.5";

        SetUpRequire();

        WazeWrap.isBetaEditor = /beta/.test(location.href);
        WazeWrap.test = "test";

        WazeWrap.Geometry = new Geometry;
        WazeWrap.Model = new Model;
        WazeWrap.Interface = new Interface;
        WazeWrap.User = new User;
        WazeWrap.Util = new Util;

        root.WazeWrap = WazeWrap;

        console.log('WazeWrap Loaded');
    };


    function SetUpRequire(){
        // setup one global var and put all in
        var WMEAPI = {};

        // detect URL of WME source code
        WMEAPI.scripts = document.getElementsByTagName('script');
        WMEAPI.url=null;
        for (i=0;i<WMEAPI.scripts.length;i++){
            if (WMEAPI.scripts[i].src.indexOf('/assets-editor/js/app')!=-1)
            {
                WMEAPI.url=WMEAPI.scripts[i].src;
                break;
            }
        }
        if (WMEAPI.url==null)
            throw new Error("WME Hack: can't detect WME main JS");


        // setup a fake require and require.define
        WMEAPI.require=function (e) {
            if (WMEAPI.require.define.modules.hasOwnProperty(e))
                return WMEAPI.require.define.modules[e];
            else
                console.error('Require failed on ' + e, WMEAPI.require.define.modules);
            return null;
        };

        WMEAPI.require.define=function (m) {
            if (WMEAPI.require.define.hasOwnProperty('modules')==false)
                WMEAPI.require.define.modules={};
            for (var p in m){
                WMEAPI.require.define.modules[p]=m[p];
            }
        };

        // save the original webpackJsonp function
        WMEAPI.tmp = window.webpackJsonp;

        // taken from WME code: this function is a wrapper that setup the API and may call recursively other functions
        WMEAPI.t = function (n) {
            if (WMEAPI.s[n]) return WMEAPI.s[n].exports;
            var r = WMEAPI.s[n] = {
                exports: {},
                id: n,
                loaded: !1
            };
            return WMEAPI.e[n].call(r.exports, r, r.exports, WMEAPI.t), r.loaded = !0, r.exports;
        };

        // e is a copy of all WME funcs because function t need to access to this list
        WMEAPI.e=[];

        // the patch
        window.webpackJsonp = function(a, i) {
            // our API but we will use it only to build the require stuffs
            var api={};
            // taken from WME code. a is [1], so...
            for (var o, d, u = 0, l = []; u < a.length; u++) d = a[u], WMEAPI.r[d] && l.push.apply(l, WMEAPI.r[d]), WMEAPI.r[d] = 0;

            var unknownCount=0;
            var classname, funcStr;

            // copy i in e and keep a link from classname to index in e
            for (o in i){
                WMEAPI.e[o] = i[o];
                funcStr = i[o].toString();
                classname = funcStr.match(/CLASS_NAME:\"([^\"]*)\"/);
                if (classname){
                    // keep the link.
                    api[classname[1].replace(/\./g,'/').replace(/^W\//, 'Waze/')]={index: o, func: WMEAPI.e[o]};
                }
                else{
                    api['Waze/Unknown/' + unknownCount]={index: o, func: WMEAPI.e[o]};
                    unknownCount++;
                }

            }

            // taken from WME code: it calls the original webpackJsonp and do something else, but I don't really know what.
            // removed the call to the original webpackJsonp: still works...
            //for (tmp && tmp(a, i); l.length;) l.shift().call(null, t);
            for (; l.length;) l.shift().call(null, WMEAPI.t);
            WMEAPI.s[0] = 0;

            // run the first func of WME. This first func will call recusrsively all funcs needed to setup the API.
            // After this call, s will contain all instanciables classes.
            //var ret = WMEAPI.t(0);

            // now, build the requires thanks to the link we've built in var api.
            var module={};
            var apiFuncName;
            unknownCount=0;

            for (o in i){
                funcStr = i[o].toString();
                classname = funcStr.match(/CLASS_NAME:\"([^\"]*)\"/);
                if (classname){
                    module={};
                    apiFuncName = classname[1].replace(/\./g,'/').replace(/^W\//, 'Waze/');
                    module[apiFuncName]=WMEAPI.t(api[apiFuncName].index);
                    WMEAPI.require.define(module);
                }
                else{
                    var matches = funcStr.match(/SEGMENT:"segment",/);
                    if (matches){
                        module={};
                        apiFuncName='Waze/Model/ObjectType';
                        module[apiFuncName]=WMEAPI.t(api['Waze/Unknown/' + unknownCount].index);
                        WMEAPI.require.define(module);
                    }
                    unknownCount++;
                }
            }


            // restore the original func
            window.webpackJsonp=WMEAPI.tmp;

            // set the require public if needed
            // if so: others scripts must wait for the window.require to be available before using it.
            window.require = WMEAPI.require;
            // all available functions are in WMEAPI.require.define.modules
            // console.debug this variable to read it:
            // console.debug('Modules: ', WMEAPI.require.define.modules);

            // run your script here:
            // setTimeout(yourscript);

            // again taken from WME code. Not sure about what it does.
            //if (i[0]) return ret;
        };

        // some kind of global vars and init
        WMEAPI.s = {};
        WMEAPI.r = {
            0: 0
        };

        // hacking finished

        // load again WME through our patched func
        WMEAPI.WMEHACK_Injected_script = document.createElement("script");
        WMEAPI.WMEHACK_Injected_script.setAttribute("type", "application/javascript");
        WMEAPI.WMEHACK_Injected_script.src = WMEAPI.url;
        document.body.appendChild(WMEAPI.WMEHACK_Injected_script);
    }

    function Geometry(){
        //var geometry = WazeWrap.Geometry;

        //Converts to "normal" GPS coordinates
        this.ConvertTo4326 = function (long, lat){
            var projI=new OpenLayers.Projection("EPSG:900913");
            var projE=new OpenLayers.Projection("EPSG:4326");
            return (new OpenLayers.LonLat(long, lat)).transform(projI,projE);
        };

        this.ConvertTo900913 = function (long, lat){
            var projI=new OpenLayers.Projection("EPSG:900913");
            var projE=new OpenLayers.Projection("EPSG:4326");
            return (new OpenLayers.LonLat(long, lat)).transform(projE,projI);
        };

        //Converts the Longitudinal offset to an offset in 4326 gps coordinates
        this.CalculateLongOffsetGPS = function(longMetersOffset, long, lat)
        {
            var R= 6378137; //Earth's radius
            var dLon = longMetersOffset / (R * Math.cos(Math.PI * lat / 180)); //offset in radians
            var lon0 = dLon * (180 / Math.PI); //offset degrees

            return lon0;
        };

        //Converts the Latitudinal offset to an offset in 4326 gps coordinates
        this.CalculateLatOffsetGPS = function(latMetersOffset, lat)
        {
            var R= 6378137; //Earth's radius
            var dLat = latMetersOffset/R;
            var lat0 = dLat * (180  /Math.PI); //offset degrees

            return lat0;
        };

        this.isLonLatInMapExtent = function (lonLat) {
            'use strict';
            return lonLat && W.map.getExtent().containsLonLat(lonLat);
        };

        this.isGeometryInMapExtent = function (geometry) {
            'use strict';
            return geometry && geometry.getBounds &&
                W.map.getExtent().intersectsBounds(geometry.getBounds());
        };
    };

    function Model(){
        //var model = WazeWrap.Model;

        this.getPrimaryStreetID = function(segmentID){
            return W.model.segments.get(segmentID).attributes.primaryStreetID;
        };

        this.getStreetName = function(primaryStreetID){
            return W.model.streets.get(PrimaryStreetID).name;
        };

        this.getCityID = function(primaryStreetID){
            return W.model.streets.get(primaryStreetID).cityID;
        };

        this.getCityName = function(primaryStreetID){
            return W.model.cities.get(this.getCityID(primaryStreetID)).attributes.Name;
        };

        this.getStateName = function(primaryStreetID){
            return W.model.states.get(getStateID(primaryStreetID)).Name;   
        };

        this.getStateID = function(primaryStreetID){
            return W.model.cities.get(primaryStreetID).attributes.stateID;
        };

        this.getCountryID = function(primaryStreetID){
            return W.model.cities.get(this.getCityID(primaryStreetID)).attributes.CountryID;
        };

        this.getCountryName = function(primaryStreetID){
            return W.model.countries.get(getCountryID(primaryStreetID)).name;
        };

        this.getCityNameFromSegmentObj = function(segObj){
            return this.getCityName(segObj.attributes.primaryStreetID);
        };

        this.getStateNameFromSegmentObj = function(segObj){
            return this.getStateName(segObj.attributes.primaryStreetID);
        };

        //returns an array of segmentIDs for all segments that are part of the same roundabout as the passed segment
        this.getAllRoundaboutSegmentsFromObj = function(segObj){
            if(segObj.model.attributes.junctionID === null)
                return null;

            return W.model.junctions.objects[segObj.model.attributes.junctionID].segIDs;
        };

        this.getAllRoundaboutJunctionNodesFromObj = function(segObj){
            var RASegs = this.getAllRoundaboutSegmentsFromObj(segObj);
            var RAJunctionNodes = [];
            for(i=0; i< RASegs.length; i++){
                RAJunctionNodes.push(W.model.nodes.objects[W.model.segments.get(RASegs[i]).attributes.toNodeID]);

            }
            return RAJunctionNodes;
        };

        this.isRoundaboutSegmentID = function(segmentID){
            if(W.model.segments.get(segmentID).attributes.junctionID === null)
                return false;
            else
                return true;
        };

        this.isRoundaboutSegmentObj = function(segObj){
            if(segObj.model.attributes.junctionID === null)
                return false;
            else
                return true;
        };

        /**
         * Defers execution of a callback function until the WME map and data 
         * model are ready. Call this function before calling a function that 
         * causes a map and model reload, such as W.map.moveTo(). After the 
         * move is completed the callback function will be executed.
         * @function WazeWrap.Model.onModelReady
         * @param {Function} callback The callback function to be executed.
         * @param {Boolean} now Whether or not to call the callback now if the
         * model is currently ready.
         * @param {Object} context The context in which to call the callback.
         */
        this.onModelReady = function (callback, now, context) {
            var deferModelReady = function () {
                return $.Deferred(function (dfd) {
                    var resolve = function () {
                        dfd.resolve();
                        W.model.events.unregister('mergeend', null, resolve);
                    };
                    W.model.events.register('mergeend', null, resolve);
                }).promise();
            };
            var deferMapReady = function () {
                return $.Deferred(function (dfd) {
                    var resolve = function () {
                        dfd.resolve();
                        W.vent.off('operationDone', resolve);
                    };
                    W.vent.on('operationDone', resolve);
                }).promise();
            };

            if (typeof callback === 'function') {
                context = context || callback;
                if (now && WazeWrap.Util.mapReady() && WazeWrap.Util.modelReady()) {
                    callback.call(context);
                } else {
                    $.when(deferMapReady() && deferModelReady()).
                    then(function () {
                        callback.call(context);
                    });
                }
            }
        };
    };

    function User(){
        this.Rank = function(){
            return W.loginManager.user.normalizedLevel;
        };

        this.Username = function(){
            return W.loginManager.user.userName;
        };

        this.isCM = function(){
            if(W.loginManager.user.editableCountryIDs.length > 0)
                return true;
            else
                return false;
        };

        this.isAM = function(){
            return W.loginManager.user.isAreaManager;
        };
    };

    function Util(){
        /**
         * Function to defer function execution until an element is present on 
         * the page.
         * @function WazeWrap.Util.waitForElement
         * @param {String} selector The CSS selector string or a jQuery object 
         * to find before executing the callback.
         * @param {Function} callback The function to call when the page 
         * element is detected.
         * @param {Object} [context] The context in which to call the callback.
         */
        this.waitForElement = function (selector, callback, context) {
            var jqObj;

            if (!selector || typeof callback !== 'function') {
                return;
            }

            jqObj = typeof selector === 'string' ?
                $(selector) : selector instanceof $ ? selector : null;

            if (!jqObj.size()) {
                window.requestAnimationFrame(function () {
                    WazeWrap.Util.waitForElement(selector, callback, context);
                });
            } else {
                callback.call(context || callback);
            }
        };

        /**
         * Function to track the ready state of the map.
         * @function WazeWrap.Util.mapReady
         * @return {Boolean} Whether or not a map operation is pending or 
         * undefined if the function has not yet seen a map ready event fired.
         */
        this.mapReady = function () {
            var mapReady = true;
            W.vent.on('operationPending', function () {
                mapReady = false;
            });
            W.vent.on('operationDone', function () {
                mapReady = true;
            });
            return function () {
                return mapReady;
            };
        } ();

        /**
         * Function to track the ready state of the model.
         * @function WazeWrap.Util.modelReady
         * @return {Boolean} Whether or not the model has loaded objects or 
         * undefined if the function has not yet seen a model ready event fired.
         */
        this.modelReady = function () {
            var modelReady = true;
            W.model.events.register('mergestart', null, function () {
                modelReady = false;
            });
            W.model.events.register('mergeend', null, function () {
                modelReady = true;
            });
            return function () {
                return modelReady;
            };
        } ();
    };

    function Interface() {
        /**
         * Generates id for message bars.
         * @private
         */
        var getNextID = function () {
            var id = 1;
            return function () {
                return id++;
            };
        } ();

        this.Shortcut = OL.Class(this, /** @lends WazeWrap.Interface.Shortcut.prototype */ {
            name: null,
            group: null,
            shortcut: {},
            callback: null,
            scope: null,
            groupExists: false,
            actionExists: false,
            eventExists: false,
            defaults: {
                group: 'default'
            },

            /**
             * Creates a new {WazeWrap.Interface.Shortcut}.
             * @class
             * @name WazeWrap.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 {WazeWrap.Interface.Shortcut} The new shortcut object.
             * @example //Creates new shortcut and adds it to the map.
             * shortcut = new WazeWrap.Interface.Shortcut('myName', 'myGroup', 'C+p', callbackFunc, null).add();
             */
            initialize: function (name, group, shortcut, callback, scope) {
                if ('string' === typeof name && name.length > 0 &&
                    'string' === typeof shortcut && shortcut.length > 0 &&
                    'function' === typeof callback) {
                    this.name = name;
                    this.group = group || this.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 {WazeWrap.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 {WazeWrap.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 {WazeWrap.Interface.Shortcut} The keyboard shortcut.
            */
            change: function (shortcut) {
                if (shortcut) {
                    this.shortcut = {};
                    this.shortcut[shortcut] = this.name;
                    this.registerShortcut();
                }
                return this;
            }
        }),

            this.Tab = OL.Class(this, {
            /** @lends WazeWrap.Interface.Tab */
            TAB_SELECTOR: '#user-tabs ul.nav-tabs',
            CONTENT_SELECTOR: '#user-info div.tab-content',
            callback: null,
            $content: null,
            context: null,
            $tab: null,

            /**
             * Creates a new WazeWrap.Interface.Tab. The tab is appended to the WME 
             * editor sidebar and contains the passed HTML content.
             * @class
             * @name WazeWrap.Interface.Tab
             * @param {String} name The name of the tab. Should not contain any 
             * special characters.
             * @param {String} content The HTML content of the tab.
             * @param {Function} [callback] A function to call upon successfully 
             * appending the tab.
             * @param {Object} [context] The context in which to call the callback 
             * function.
                     * @return {WazeWrap.Interface.Tab} The new tab object.
             * @example //Creates new tab and adds it to the page.
             * new WazeWrap.Interface.Tab('thebestscriptever', '<div>Hello World!</div>');
             */
            initialize: function (name, content, callback, context) {
                var idName, i = 0;
                if (name && 'string' === typeof name &&
                    content && 'string' === typeof content) {
                    if (callback && 'function' === typeof callback) {
                        this.callback = callback;
                        this.context = context || callback;
                    }
                    /* Sanitize name for html id attribute */
                    idName = name.toLowerCase().replace(/[^a-z-_]/g, '');
                    /* 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();
                }
            },

            append: function (content) {
                this.$content.append(content);
            },

            appendTab: function () {
                WazeWrap.Util.waitForElement(
                    this.TAB_SELECTOR + ',' + this.CONTENT_SELECTOR,
                    function () {
                        $(this.TAB_SELECTOR).append(this.$tab);
                        $(this.CONTENT_SELECTOR).first().append(this.$content);
                        if (this.callback) {
                            this.callback.call(this.context);
                        }
                    }, this);
            },

            clearContent: function () {
                this.$content.empty();
            },

            destroy: function () {
                this.$tab.remove();
                this.$content.remove();
            }
        });
    };

}.call(this));