WazeWrap Beta

A base library for WME script writers

Version vom 16.11.2016. Aktuellste Version

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/24870/158075/WazeWrap%20Beta.js

// ==UserScript==
// @name         WazeWrap Beta
// @namespace    https://greasyfork.org/users/30701-justins83-waze
// @version      0.1.3
// @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 */

(function() {

	function bootstrap(tries) {
		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 = window.WazeWrap;

        var root = this;
		var WazeWrap = {};

		WazeWrap.Version = "0.1.3";

        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 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));