WazeWrap

A base library for WME script writers

目前為 2016-11-14 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/24851/157879/WazeWrap.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         WazeWrap
// @namespace    https://greasyfork.org/users/30701-justins83-waze
// @version      0.1.2
// @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.2";

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