diff --git a/code/artifact.js b/code/artifact.js index 7cd1f22d..f1e5b874 100644 --- a/code/artifact.js +++ b/code/artifact.js @@ -149,6 +149,7 @@ window.artifact.getArtifactDescriptions = function(type) { 'amar': { 'title': "Amar Artifacts", 'fragmentName': "artifacts" }, 'helios': { 'title': "Helios Artifacts", 'fragmentName': "artifacts" }, 'shonin': { 'title': "Sh\u014Dnin Shards", 'fragmentName': "shards" }, + 'lightman': { 'title': "Lightman Shards", 'fragmentName': "shards" }, }; return descriptions[type]; @@ -223,7 +224,6 @@ window.artifact.updateLayer = function() { iconSize = 60/2; // 60 pixels - half that size works better opacity = 0.6; // these often hide portals - let's make them semi transparent } - } // 2014-08-09 - helios artifacts. original guess was slightly wrong @@ -237,7 +237,6 @@ window.artifact.updateLayer = function() { iconSize = 60/2; // 60 pixels - half that size works better opacity = 0.6; // these often hide portals - let's make them semi transparent } - } // 2015-03-05 - shonin shards @@ -251,7 +250,19 @@ window.artifact.updateLayer = function() { iconSize = 60/2; // 60 pixels - half that size works better opacity = 0.6; // these often hide portals - let's make them semi transparent } + } + // 2015-04-22 - lightman fragments (guessed) + if (data.lightman) { + if (data.lightman.target) { + // target portal - show the target marker. + iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/lightman_shard_target.png'; + iconSize = 100/2; // 100 pixels - half that size works better + } else if (data.lightman.fragments) { + iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/lightman_shard.png'; + iconSize = 60/2; // 60 pixels - half that size works better + opacity = 0.6; // these often hide portals - let's make them semi transparent + } } if (iconUrl) { diff --git a/code/boot.js b/code/boot.js index 50441c6b..4ebce81f 100644 --- a/code/boot.js +++ b/code/boot.js @@ -165,7 +165,7 @@ window.setupMap = function() { center: [0,0], zoom: 1, zoomControl: (typeof android !== 'undefined' && android && android.showZoom) ? android.showZoom() : true, - minZoom: 5, + minZoom: MIN_ZOOM, // zoomAnimation: false, markerZoomAnimation: false, bounceAtZoomLimits: false @@ -591,6 +591,7 @@ function boot() { window.setupTaphold(); window.setupStyles(); window.setupDialogs(); + window.setupDataTileParams(); window.setupMap(); window.setupOMS(); window.search.setup(); diff --git a/code/botguard_interface.js b/code/botguard_interface.js index 41083936..e960723b 100644 --- a/code/botguard_interface.js +++ b/code/botguard_interface.js @@ -116,12 +116,9 @@ iitc_bg.process_key = function(key,serverEval) { if (serverEval) { // server wants us to eval some code! risky, and impossible to be certain we can do it safely - // however... reports say that it only interacts with the botguard.bg code, so we might be fine just running it - // (but this is only when we don't send the correct params to the server? no reports of this code triggering yet...) + // seems to always be the same javascript as already found in the web page source. try { console.warn('botguard: Server-generated javascript eval requested:\n'+serverEval); -debugger; -if (!confirm('The server asked IITC to run (eval) some javascript. This may or may not be safe. Run and continue?\n\nScript:\n'+serverEval)) { console.error('server javascript eval cancelled') } else iitc_bg.evalFunc(serverEval); console.log('botguard: Server-generated javascript ran OK'); } catch(e) { diff --git a/code/extract_niantic_parameters.js b/code/extract_niantic_parameters.js index 9561db8a..d55b1dc7 100644 --- a/code/extract_niantic_parameters.js +++ b/code/extract_niantic_parameters.js @@ -76,7 +76,7 @@ window.extractFromStock = function() { // a reasonable array length for tile parameters // need to find two types: // a. portal level limits. decreasing numbers, starting at 8 - // b. tiles per edge. increasing numbers. current max is 9000 + // b. tiles per edge. increasing numbers. current max is 36000, 9000 was the previous value - 18000 is a likely possibility too if (topObject[0] == 8) { // check for tile levels @@ -93,7 +93,7 @@ window.extractFromStock = function() { } } // end if (topObject[0] == 8) - if (topObject[topObject.length-1] == 9000) { + if (topObject[topObject.length-1] == 36000 || topObject[topObject.length-1] == 18000 || topObject[topObject.length-1] == 9000) { var increasing = true; for (var i=1; i topObject[i]) { diff --git a/code/map_data_calc_tools.js b/code/map_data_calc_tools.js index da273453..f1718e2b 100755 --- a/code/map_data_calc_tools.js +++ b/code/map_data_calc_tools.js @@ -10,38 +10,84 @@ // http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames -window.getMapZoomTileParameters = function(zoom) { +window.setupDataTileParams = function() { + // default values - used to fall back to if we can't detect those used in stock intel + var DEFAULT_ZOOM_TO_TILES_PER_EDGE = [256, 256, 256, 256, 512, 512, 512, 2048, 2048, 2048, 4096, 4096, 6500, 6500, 6500, 18e3, 18e3, 36e3]; + var DEFAULT_ZOOM_TO_LEVEL = [ 8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1 ]; -// var ZOOM_TO_TILES_PER_EDGE = [64, 64, 128, 128, 256, 256, 256, 1024, 1024, 1536, 4096, 4096, 6500, 6500, 6500]; -// var ZOOM_TO_TILES_PER_EDGE = [256, 256, 256, 256, 512, 512, 512, 2048, 2048, 2048, 4096, 4096, 6500, 6500, 6500]; - var ZOOM_TO_TILES_PER_EDGE = [256, 256, 256, 256, 512, 2048, 2048, 4096, 4096, 4096, 4096, 4096, 6500, 6500, 6500];; - var MAX_TILES_PER_EDGE = 9000; -// var ZOOM_TO_LEVEL = [8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1]; - var ZOOM_TO_LEVEL = [8, 8, 8, 8, 8, 8, 7, 7, 6, 6, 5, 4, 3, 2, 2, 1, 1]; + + window.TILE_PARAMS = {}; if (niantic_params.ZOOM_TO_LEVEL && niantic_params.TILES_PER_EDGE) { - ZOOM_TO_LEVEL = niantic_params.ZOOM_TO_LEVEL; - ZOOM_TO_TILES_PER_EDGE = niantic_params.TILES_PER_EDGE; + window.TILE_PARAMS.ZOOM_TO_LEVEL = niantic_params.ZOOM_TO_LEVEL; + window.TILE_PARAMS.TILES_PER_EDGE = niantic_params.TILES_PER_EDGE; + + + // lazy numerical array comparison + if ( JSON.stringify(niantic_params.ZOOM_TO_LEVEL) != JSON.stringify(DEFAULT_ZOOM_TO_LEVEL)) { + console.warn('Tile parameter ZOOM_TO_LEVEL have changed in stock intel. Detectec correct values, but code should be updated'); + debugger; + } + if ( JSON.stringify(niantic_params.TILES_PER_EDGE) != JSON.stringify(DEFAULT_ZOOM_TO_TILES_PER_EDGE)) { + console.warn('Tile parameter ZOOM_TO_LEVEL have changed in stock intel. Detectec correct values, but code should be updated'); + debugger; + } + + } else { + console.warn('Failed to detect both ZOOM_TO_LEVEL and TILES_PER_EDGE in the stock intel site - using internal defaults'); + debugger; + + window.TILE_PARAMS.ZOOM_TO_LEVEL = DEFAULT_ZOOM_TO_LEVEL; + window.TILE_PARAMS.TILES_PER_EDGE = DEFAULT_ZOOM_TO_TILES_PER_EDGE; } - // the current API allows the client to request a minimum portal level. the ZOOM_TO_LEVEL list are minimums + // disable SHOW_MORE_PORTALS if it would be unfriendly to the servers (i.e. result in more requests) + // needs to be fired a bit later, after plugins have been initialised + setTimeout(function(){ + if (window.CONFIG_ZOOM_SHOW_MORE_PORTALS) { + if (window.TILE_PARAMS.TILES_PER_EDGE[17] > window.TILE_PARAMS.TILES_PER_EDGE[15]) { + var edgeScale = window.TILE_PARAMS.TILES_PER_EDGE[17]/window.TILE_PARAMS.TILES_PER_EDGE[15]; + var mapScale = edgeScale*edgeScale; + + dialog({ + title: 'Show more portals plugin disabled', + width: 400, + text: 'The "show-more-portals" plugin has been disabled.\n\n' + +'Niantic have changed the intel site so that zoom level 17 (all portals) now needs '+mapScale+' times more requests than level 15 (L1+ portals), so fetching at the wrong zoom level will be unfriendly to the servers\n\n' + +'Don\'t like this? Ask Niantic to change the standard intel site, then IITC can match and all would benifit.' + }); + + window.CONFIG_ZOOM_SHOW_MORE_PORTALS=false; + } + } + }, 1); + +} + + + +window.getMapZoomTileParameters = function(zoom) { + + + // the current API allows the client to request a minimum portal level. the window.TILE_PARAMS.ZOOM_TO_LEVEL list are minimums // however, in my view, this can return excessive numbers of portals in many cases. let's try an optional reduction // of detail level at some zoom levels - var level = ZOOM_TO_LEVEL[zoom] || 0; // default to level 0 (all portals) if not in array + var level = window.TILE_PARAMS.ZOOM_TO_LEVEL[zoom] || 0; // default to level 0 (all portals) if not in array if (window.CONFIG_ZOOM_SHOW_LESS_PORTALS_ZOOMED_OUT) { if (level <= 7 && level >= 4) { // reduce portal detail level by one - helps reduce clutter level = level+1; } - } + var maxTilesPerEdge = window.TILE_PARAMS.TILES_PER_EDGE[window.TILE_PARAMS.TILES_PER_EDGE.length-1]; + return { level: level, - maxLevel: ZOOM_TO_LEVEL[zoom] || 0, // for reference, for log purposes, etc - tilesPerEdge: ZOOM_TO_TILES_PER_EDGE[zoom] || MAX_TILES_PER_EDGE, + maxLevel: window.TILE_PARAMS.ZOOM_TO_LEVEL[zoom] || 0, // for reference, for log purposes, etc + tilesPerEdge: window.TILE_PARAMS.TILES_PER_EDGE[zoom] || maxTilesPerEdge, zoom: zoom // include the zoom level, for reference }; } @@ -50,7 +96,7 @@ window.getMapZoomTileParameters = function(zoom) { window.getDataZoomForMapZoom = function(zoom) { // we can fetch data at a zoom level different to the map zoom. - //NOTE: the specifics of this are tightly coupled with the above ZOOM_TO_LEVEL and ZOOM_TO_TILES_PER_EDGE arrays + //NOTE: the specifics of this are tightly coupled with the above ZOOM_TO_LEVEL and TILES_PER_EDGE arrays // firstly, some of IITCs zoom levels, depending on base map layer, can be higher than stock. limit zoom level // (stock site max zoom may vary depending on google maps detail in the area - 20 or 21 max is common) @@ -66,7 +112,7 @@ window.getDataZoomForMapZoom = function(zoom) { // to avoid impacting server load, we keep ourselves restricted to a zoom level with the sane numbre // of tilesPerEdge and portal levels visible - while (zoom > 5) { + while (zoom > MIN_ZOOM) { var newTileParams = getMapZoomTileParameters(zoom-1); if (newTileParams.tilesPerEdge != origTileParams.tilesPerEdge || newTileParams.level != origTileParams.level) { // switching to zoom-1 would result in a different detail level - so we abort changing things diff --git a/code/map_data_request.js b/code/map_data_request.js index a1e9af30..6967bbe4 100644 --- a/code/map_data_request.js +++ b/code/map_data_request.js @@ -487,6 +487,7 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success) var errorTiles = []; var retryTiles = []; var timeoutTiles = []; + var unaccountedTiles = tiles.slice(0); // Clone if (!success || !data || !data.result) { console.warn('Request.handleResponse: request failed - requeuing...'+(data && data.error?' error: '+data.error:'')); @@ -513,7 +514,7 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success) window.runHooks('requestFinished', {success: false}); } - + unaccountedTiles = []; } else { // TODO: use result.minLevelOfDetail ??? stock site doesn't use it yet... @@ -522,7 +523,7 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success) for (var id in m) { var val = m[id]; - + unaccountedTiles.splice(unaccountedTiles.indexOf(id), 1); if ('error' in val) { // server returned an error for this individual data tile @@ -571,6 +572,7 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success) if (retryTiles.length) statusMsg += ', '+retryTiles.length+' retried'; if (timeoutTiles.length) statusMsg += ', '+timeoutTiles.length+' timed out'; if (errorTiles.length) statusMsg += ', '+errorTiles.length+' failed'; + if (unaccountedTiles.length) statusMsg += ', '+unaccountedTiles.length+' unaccounted'; statusMsg += '. delay '+nextQueueDelay+' seconds'; console.log (statusMsg); @@ -602,6 +604,14 @@ window.MapDataRequest.prototype.handleResponse = function (data, tiles, success) } } + if (unaccountedTiles.length > 0) { + for (var i in unaccountedTiles) { + var id = unaccountedTiles[i]; + delete this.requestedTiles[id]; + this.requeueTile(id, true); + } + } + for (var i in successTiles) { var id = successTiles[i]; delete this.requestedTiles[id]; diff --git a/code/utils_misc.js b/code/utils_misc.js index f0b5f547..f2f09ccb 100644 --- a/code/utils_misc.js +++ b/code/utils_misc.js @@ -93,11 +93,9 @@ window.getURLParam = function(param) { // read cookie by name. // http://stackoverflow.com/a/5639455/1684530 by cwolves -var cookies; -window.readCookie = function(name,c,C,i){ - if(cookies) return cookies[name]; - c = document.cookie.split('; '); - cookies = {}; +window.readCookie = function(name){ + var C, i, c = document.cookie.split('; '); + var cookies = {}; for(i=c.length-1; i>=0; i--){ C = c[i].split('='); cookies[C[0]] = unescape(C[1]); @@ -106,7 +104,8 @@ window.readCookie = function(name,c,C,i){ } window.writeCookie = function(name, val) { - document.cookie = name + "=" + val + '; expires=Thu, 31 Dec 2020 23:59:59 GMT; path=/'; + var d = new Date(Date.now() + 10 * 365 * 24 * 60 * 60 * 1000).toUTCString(); + document.cookie = name + "=" + val + '; expires='+d+'; path=/'; } window.eraseCookie = function(name) { diff --git a/images/mission-length.png b/images/mission-length.png new file mode 100644 index 00000000..31e6fa48 Binary files /dev/null and b/images/mission-length.png differ diff --git a/images/mission-type-hidden.png b/images/mission-type-hidden.png new file mode 100644 index 00000000..ceb1f14a Binary files /dev/null and b/images/mission-type-hidden.png differ diff --git a/images/mission-type-random.png b/images/mission-type-random.png new file mode 100644 index 00000000..f06d3f29 Binary files /dev/null and b/images/mission-type-random.png differ diff --git a/images/mission-type-sequential.png b/images/mission-type-sequential.png new file mode 100644 index 00000000..a8c811b3 Binary files /dev/null and b/images/mission-type-sequential.png differ diff --git a/images/mission-type-unknown.png b/images/mission-type-unknown.png new file mode 100644 index 00000000..dba67fb0 Binary files /dev/null and b/images/mission-type-unknown.png differ diff --git a/images/missions.svg b/images/missions.svg new file mode 100644 index 00000000..f9cf8429 --- /dev/null +++ b/images/missions.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/main.js b/main.js index 6ffbe3cb..8340074d 100644 --- a/main.js +++ b/main.js @@ -1,15 +1,19 @@ // ==UserScript== // @id ingress-intel-total-conversion@jonatkins // @name IITC: Ingress intel map total conversion -// @version 0.22.2.@@DATETIMEVERSION@@ +// @version 0.22.3.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ // @description [@@BUILDNAME@@-@@BUILDDATE@@] Total conversion for the ingress intel map. -// @include http://www.ingress.com/intel* // @include https://www.ingress.com/intel* -// @match http://www.ingress.com/intel* +// @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* +// @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== @@ -160,7 +164,8 @@ window.MOD_TYPE = {RES_SHIELD:'Shield', MULTIHACK:'Multi-hack', FORCE_AMP:'Force window.ACCESS_INDICATOR_COLOR = 'orange'; window.RANGE_INDICATOR_COLOR = 'red' - +// min zoom for intel map - should match that used by stock intel +window.MIN_ZOOM = 3; window.DEFAULT_PORTAL_IMG = '//commondatastorage.googleapis.com/ingress.com/img/default-portal-image.png'; //window.NOMINATIM = '//nominatim.openstreetmap.org/search?format=json&limit=1&q='; diff --git a/mobile/AndroidManifest.xml b/mobile/AndroidManifest.xml index 5a7efee2..09c63aa4 100644 --- a/mobile/AndroidManifest.xml +++ b/mobile/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="96" + android:versionName="0.22.3"> + + diff --git a/mobile/plugins/user-location.user.js b/mobile/plugins/user-location.user.js index f4ea3e42..695a0af9 100644 --- a/mobile/plugins/user-location.user.js +++ b/mobile/plugins/user-location.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/mobile/res/drawable-hdpi/ic_missions.png b/mobile/res/drawable-hdpi/ic_missions.png new file mode 100644 index 00000000..4ce99216 Binary files /dev/null and b/mobile/res/drawable-hdpi/ic_missions.png differ diff --git a/mobile/res/drawable-mdpi/ic_missions.png b/mobile/res/drawable-mdpi/ic_missions.png new file mode 100644 index 00000000..0cf7a468 Binary files /dev/null and b/mobile/res/drawable-mdpi/ic_missions.png differ diff --git a/mobile/res/drawable-xhdpi/ic_missions.png b/mobile/res/drawable-xhdpi/ic_missions.png new file mode 100644 index 00000000..b314730e Binary files /dev/null and b/mobile/res/drawable-xhdpi/ic_missions.png differ diff --git a/mobile/res/drawable-xxhdpi/ic_missions.png b/mobile/res/drawable-xxhdpi/ic_missions.png new file mode 100644 index 00000000..e45cb367 Binary files /dev/null and b/mobile/res/drawable-xxhdpi/ic_missions.png differ diff --git a/mobile/res/layout/js_prompt.xml b/mobile/res/layout/js_prompt.xml new file mode 100644 index 00000000..6014282d --- /dev/null +++ b/mobile/res/layout/js_prompt.xml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/mobile/res/values/strings_js.xml b/mobile/res/values/strings_js.xml new file mode 100644 index 00000000..6bf67186 --- /dev/null +++ b/mobile/res/values/strings_js.xml @@ -0,0 +1,17 @@ + + + + + The page at \"%s\" says: + + JavaScript + + Confirm Navigation + + Leave this Page + + Stay on this Page + + %s\n\nAre you sure you want to navigate away from this page? + + \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_JsDialogHelper.java b/mobile/src/com/cradle/iitc_mobile/IITC_JsDialogHelper.java new file mode 100644 index 00000000..cd7a5d18 --- /dev/null +++ b/mobile/src/com/cradle/iitc_mobile/IITC_JsDialogHelper.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package android.webkit; +package com.cradle.iitc_mobile; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.URLUtil; +import android.webkit.WebView; +import android.widget.EditText; +import android.widget.TextView; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Helper class to create JavaScript dialogs. It is used by + * different WebView implementations. + * + * @hide Helper class for internal use + */ +public class IITC_JsDialogHelper { + // Dialog types + public static final int ALERT = 1; + public static final int CONFIRM = 2; + public static final int PROMPT = 3; + public static final int UNLOAD = 4; + + private final String mDefaultValue; + private final JsResult mResult; + private final String mMessage; + private final int mType; + private final String mUrl; + + public IITC_JsDialogHelper(final int type, final WebView view, final String url, + final String message, final String defaultValue, final JsResult result) { + mResult = result; + mDefaultValue = defaultValue; + mMessage = message; + mType = type; + mUrl = url; + + showDialog(view.getContext()); + } + + @SuppressLint("InflateParams") + private void showDialog(final Context context) { + String title, displayMessage; + int positiveTextId, negativeTextId; + if (mType == UNLOAD) { + title = context.getString(R.string.js_dialog_before_unload_title); + displayMessage = context.getString( + R.string.js_dialog_before_unload, mMessage); + positiveTextId = R.string.js_dialog_before_unload_positive_button; + negativeTextId = R.string.js_dialog_before_unload_negative_button; + } else { + title = getJsDialogTitle(context); + displayMessage = mMessage; + positiveTextId = android.R.string.ok; + negativeTextId = android.R.string.cancel; + } + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title); + builder.setOnCancelListener(new CancelListener()); + if (mType != PROMPT) { + builder.setMessage(displayMessage); + builder.setPositiveButton(positiveTextId, new PositiveListener(null)); + } else { + final View view = LayoutInflater.from(context).inflate( + R.layout.js_prompt, null); + final EditText edit = ((EditText) view.findViewById(R.id.value)); + edit.setText(mDefaultValue); + builder.setPositiveButton(positiveTextId, new PositiveListener(edit)); + ((TextView) view.findViewById(R.id.message)).setText(mMessage); + builder.setView(view); + } + if (mType != ALERT) { + builder.setNegativeButton(negativeTextId, new CancelListener()); + } + builder.show(); + } + + private class CancelListener implements DialogInterface.OnCancelListener, + DialogInterface.OnClickListener { + @Override + public void onCancel(final DialogInterface dialog) { + mResult.cancel(); + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + mResult.cancel(); + } + } + + private class PositiveListener implements DialogInterface.OnClickListener { + private final EditText mEdit; + + public PositiveListener(final EditText edit) { + mEdit = edit; + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (mEdit == null) { + mResult.confirm(); + } else { + ((JsPromptResult) mResult).confirm(mEdit.getText().toString()); + } + } + } + + private String getJsDialogTitle(final Context context) { + String title = mUrl; + if (URLUtil.isDataUrl(mUrl)) { + // For data: urls, we just display 'JavaScript' similar to Chrome. + title = context.getString(R.string.js_dialog_title_default); + } else { + try { + final URL alertUrl = new URL(mUrl); + // For example: "The page at 'http://www.mit.edu' says:" + title = context.getString(R.string.js_dialog_title, + alertUrl.getProtocol() + "://" + alertUrl.getHost()); + } catch (final MalformedURLException ex) { + // do nothing. just use the url as the title + } + } + return title; + } + + public boolean shouldInterrupt() { + return true; + } +} \ No newline at end of file diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java b/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java index 4e6e2965..9e6bb6d7 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_Mobile.java @@ -837,7 +837,7 @@ public class IITC_Mobile extends Activity final String js = "(function(obj){var result;" + "console.log('>>> ' + obj.code);" + "try{result=eval(obj.code);}catch(e){if(e.stack) console.error(e.stack);throw e;}" + - "if(result!==undefined) console.log(result.toString());" + + "if(result!==undefined) console.log(result===null?null:result.toString());" + "})(" + obj.toString() + ");"; mIitcWebView.loadJS(js); diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebChromeClient.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebChromeClient.java index 4cf7e581..2ec6cd37 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebChromeClient.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebChromeClient.java @@ -2,6 +2,8 @@ package com.cradle.iitc_mobile; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; import android.webkit.WebChromeClient; import android.webkit.WebView; @@ -52,4 +54,26 @@ public class IITC_WebChromeClient extends WebChromeClient { return super.onConsoleMessage(message); } + + @Override + public boolean onJsAlert(final WebView view, final String url, final String message, final JsResult result) { + return new IITC_JsDialogHelper(IITC_JsDialogHelper.ALERT, view, url, message, null, result).shouldInterrupt(); + } + + @Override + public boolean onJsBeforeUnload(final WebView view, final String url, final String message, final JsResult result) { + return new IITC_JsDialogHelper(IITC_JsDialogHelper.UNLOAD, view, url, message, null, result).shouldInterrupt(); + } + + @Override + public boolean onJsConfirm(final WebView view, final String url, final String message, final JsResult result) { + return new IITC_JsDialogHelper(IITC_JsDialogHelper.CONFIRM, view, url, message, null, result).shouldInterrupt(); + } + + @Override + public boolean onJsPrompt(final WebView view, final String url, final String message, final String defaultValue, + final JsPromptResult result) { + return new IITC_JsDialogHelper(IITC_JsDialogHelper.PROMPT, view, url, message, defaultValue, result) + .shouldInterrupt(); + } } diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java index e64b4ad6..76deb275 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebView.java @@ -168,6 +168,7 @@ public class IITC_WebView extends WebView { } } + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(final MotionEvent event) { getHandler().removeCallbacks(mNavHider); diff --git a/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java b/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java index 5ddb937f..4de8bac2 100644 --- a/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java +++ b/mobile/src/com/cradle/iitc_mobile/IITC_WebViewClient.java @@ -33,6 +33,14 @@ public class IITC_WebViewClient extends WebViewClient { "body, #dashboard_container, #map_canvas { background: #000 !important; }" .getBytes()); + public static final boolean isIntelUrl(String url) { + return + url.startsWith("http://www.ingress.com/intel") || + url.startsWith("https://www.ingress.com/intel") || + url.startsWith("http://www.ingress.com/mission/") || + url.startsWith("https://www.ingress.com/mission/"); + } + private final IITC_Mobile mIitc; private boolean mIitcInjected = false; private final String mIitcPath; @@ -119,8 +127,7 @@ public class IITC_WebViewClient extends WebViewClient { @Override public void onPageFinished(final WebView view, final String url) { - if (url.startsWith("http://www.ingress.com/intel") - || url.startsWith("https://www.ingress.com/intel")) { + if(isIntelUrl(url)) { if (mIitcInjected) return; Log.d("injecting iitc.."); loadScripts((IITC_WebView) view); @@ -229,7 +236,7 @@ public class IITC_WebViewClient extends WebViewClient { Log.d("Google login"); return false; } - else if (url.contains("ingress.com/intel")) { + else if (isIntelUrl(url)) { Log.d("intel link requested, reset app and load " + url); mIitc.reset(); mIitc.setLoadingState(true); diff --git a/plugins/add-kml.user.js b/plugins/add-kml.user.js index 8d46cf4a..b6b908b2 100644 --- a/plugins/add-kml.user.js +++ b/plugins/add-kml.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/ap-list.user.js b/plugins/ap-list.user.js index cd842aef..1216a5ec 100644 --- a/plugins/ap-list.user.js +++ b/plugins/ap-list.user.js @@ -11,5 +11,9 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-bing.user.js b/plugins/basemap-bing.user.js index 49d41333..4a8bc5bf 100644 --- a/plugins/basemap-bing.user.js +++ b/plugins/basemap-bing.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-blank.user.js b/plugins/basemap-blank.user.js index a505e611..5cc46d85 100644 --- a/plugins/basemap-blank.user.js +++ b/plugins/basemap-blank.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-cloudmade.user.js b/plugins/basemap-cloudmade.user.js index 1afacd07..4602fa7a 100644 --- a/plugins/basemap-cloudmade.user.js +++ b/plugins/basemap-cloudmade.user.js @@ -9,5 +9,9 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // ==/UserScript== diff --git a/plugins/basemap-gmaps-gray.user.js b/plugins/basemap-gmaps-gray.user.js index 26040501..0baac718 100644 --- a/plugins/basemap-gmaps-gray.user.js +++ b/plugins/basemap-gmaps-gray.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-kartverket.user.js b/plugins/basemap-kartverket.user.js index 1e140ffc..bfe9c6e8 100644 --- a/plugins/basemap-kartverket.user.js +++ b/plugins/basemap-kartverket.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-mapquest-open-aerial.user.js b/plugins/basemap-mapquest-open-aerial.user.js index 3f588a39..9dc2834a 100644 --- a/plugins/basemap-mapquest-open-aerial.user.js +++ b/plugins/basemap-mapquest-open-aerial.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-nokia-ovi.user.js b/plugins/basemap-nokia-ovi.user.js index bdeab297..bd0e3c8f 100644 --- a/plugins/basemap-nokia-ovi.user.js +++ b/plugins/basemap-nokia-ovi.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-opencyclemap.user.js b/plugins/basemap-opencyclemap.user.js index 58ff0e1f..2118bc81 100644 --- a/plugins/basemap-opencyclemap.user.js +++ b/plugins/basemap-opencyclemap.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-openstreetmap.user.js b/plugins/basemap-openstreetmap.user.js index f7610e01..701434db 100644 --- a/plugins/basemap-openstreetmap.user.js +++ b/plugins/basemap-openstreetmap.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-stamen.user.js b/plugins/basemap-stamen.user.js index d1e38475..243406ce 100644 --- a/plugins/basemap-stamen.user.js +++ b/plugins/basemap-stamen.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/basemap-yandex.user.js b/plugins/basemap-yandex.user.js index 78f0d079..f94ab3bb 100644 --- a/plugins/basemap-yandex.user.js +++ b/plugins/basemap-yandex.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/bookmarks-by-zaso.user.js b/plugins/bookmarks-by-zaso.user.js index 6ab48e41..02155ace 100644 --- a/plugins/bookmarks-by-zaso.user.js +++ b/plugins/bookmarks-by-zaso.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== @@ -810,7 +814,6 @@ if(latlngs.length >= 2 && latlngs.length <= 3) { // TODO: add an API to draw-tools rather than assuming things about its internals - window.plugin.drawTools.setOptions(); var layer, layerType; if(latlngs.length == 2) { diff --git a/plugins/canvas-render.user.js b/plugins/canvas-render.user.js index 9a1f6d69..ff823202 100644 --- a/plugins/canvas-render.user.js +++ b/plugins/canvas-render.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant unsafeWindow // ==/UserScript== diff --git a/plugins/compute-ap-stats.user.js b/plugins/compute-ap-stats.user.js index 6773d5a9..e34f4d9c 100644 --- a/plugins/compute-ap-stats.user.js +++ b/plugins/compute-ap-stats.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/cross_link.user.js b/plugins/cross_link.user.js index 71b6e282..2e073c1f 100644 --- a/plugins/cross_link.user.js +++ b/plugins/cross_link.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/debug-raw-portal-data.user.js b/plugins/debug-raw-portal-data.user.js index 280a13ea..aab8b6de 100644 --- a/plugins/debug-raw-portal-data.user.js +++ b/plugins/debug-raw-portal-data.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/default-intel-detail.user.js b/plugins/default-intel-detail.user.js index 107792a9..e25e019c 100644 --- a/plugins/default-intel-detail.user.js +++ b/plugins/default-intel-detail.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/distance-to-portal.css b/plugins/distance-to-portal.css index fe66b84d..794bdfdf 100644 --- a/plugins/distance-to-portal.css +++ b/plugins/distance-to-portal.css @@ -1,14 +1,14 @@ #portal-distance { text-align: center; } -#portal-distance-bearing { +.portal-distance-bearing { display: inline-block; vertical-align: top; position: relative; height: 1em; width: 1em; } -#portal-distance-bearing:before, #portal-distance-bearing:after { +.portal-distance-bearing:before, .portal-distance-bearing:after { border-color: transparent currentcolor transparent transparent; border-style: solid; border-width: 0.75em 0.4em 0 0; @@ -22,7 +22,7 @@ -moz-transform: skewY(-30deg); -webkit-transform: skewY(-30deg); } -#portal-distance-bearing:after { +.portal-distance-bearing:after { left: auto; right: 0.15em; transform: scaleX(-1) skewY(-30deg); diff --git a/plugins/distance-to-portal.user.js b/plugins/distance-to-portal.user.js index 8180ced7..40d366e3 100644 --- a/plugins/distance-to-portal.user.js +++ b/plugins/distance-to-portal.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== @@ -67,7 +71,7 @@ window.plugin.distanceToPortal.updateDistance = function() { $('#portal-distance') .text('Distance: ' + dist + ' ') .append($('') - .attr('id', 'portal-distance-bearing') + .addClass('portal-distance-bearing') .css({ 'transform': 'rotate('+bearing+'deg)', '-moz-transform': 'rotate('+bearing+'deg)', @@ -115,7 +119,6 @@ window.plugin.distanceToPortal.setupPortalsList = function() { $(cell).addClass('alignR').text(dist?window.plugin.distanceToPortal.formatDistance(dist):'-'); } }); - } diff --git a/plugins/done-links.user.js b/plugins/done-links.user.js index d4948d49..ecb33fab 100644 --- a/plugins/done-links.user.js +++ b/plugins/done-links.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/draw-resonators.user.js b/plugins/draw-resonators.user.js index 7d2c44c4..85d515be 100644 --- a/plugins/draw-resonators.user.js +++ b/plugins/draw-resonators.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/draw-tools.user.js b/plugins/draw-tools.user.js index 83a2f8c2..d0239406 100644 --- a/plugins/draw-tools.user.js +++ b/plugins/draw-tools.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/favorite-portals.user.js b/plugins/favorite-portals.user.js index d3b9c888..b4cd4003 100644 --- a/plugins/favorite-portals.user.js +++ b/plugins/favorite-portals.user.js @@ -11,5 +11,9 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/fix-googlemap-china-offset.user.js b/plugins/fix-googlemap-china-offset.user.js index a25b271a..59d7dafd 100644 --- a/plugins/fix-googlemap-china-offset.user.js +++ b/plugins/fix-googlemap-china-offset.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/fly-links.user.js b/plugins/fly-links.user.js index 584a3ab6..e9a98356 100644 --- a/plugins/fly-links.user.js +++ b/plugins/fly-links.user.js @@ -10,6 +10,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/force-https.user.js b/plugins/force-https.user.js index c29ae7cd..6bf07aa6 100644 --- a/plugins/force-https.user.js +++ b/plugins/force-https.user.js @@ -6,11 +6,15 @@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description [@@BUILDNAME@@-@@BUILDDATE@@] Force https access for ingress.com/intel. If the intel site is accessed via http, it redirects to the https version. +// @description [@@BUILDNAME@@-@@BUILDDATE@@] Force https access for the intel map. If the intel map is accessed via http, it redirects to the https version. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/guess-player-levels.user.js b/plugins/guess-player-levels.user.js index 15b6272a..538db0ab 100644 --- a/plugins/guess-player-levels.user.js +++ b/plugins/guess-player-levels.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/iitc-ditigal-bumper-sticker.user.js b/plugins/iitc-ditigal-bumper-sticker.user.js index 489e39ae..a7e14567 100644 --- a/plugins/iitc-ditigal-bumper-sticker.user.js +++ b/plugins/iitc-ditigal-bumper-sticker.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/ipas-link.user.js b/plugins/ipas-link.user.js index 1c853c53..79bf9412 100644 --- a/plugins/ipas-link.user.js +++ b/plugins/ipas-link.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/keys-on-map.user.js b/plugins/keys-on-map.user.js index 924960f6..e8a8289b 100644 --- a/plugins/keys-on-map.user.js +++ b/plugins/keys-on-map.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/keys.user.js b/plugins/keys.user.js index eed0ffc9..3dc7d4ee 100644 --- a/plugins/keys.user.js +++ b/plugins/keys.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/layer-count.user.js b/plugins/layer-count.user.js index 72538947..af803fc6 100644 --- a/plugins/layer-count.user.js +++ b/plugins/layer-count.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/layer-farms-find.user.js b/plugins/layer-farms-find.user.js index fcee25ad..6f81fa27 100644 --- a/plugins/layer-farms-find.user.js +++ b/plugins/layer-farms-find.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/link-show-direction.user.js b/plugins/link-show-direction.user.js index b972082e..d84d1d77 100644 --- a/plugins/link-show-direction.user.js +++ b/plugins/link-show-direction.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/max-links.user.js b/plugins/max-links.user.js index 91143952..d930fc09 100644 --- a/plugins/max-links.user.js +++ b/plugins/max-links.user.js @@ -10,6 +10,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/minimap.user.js b/plugins/minimap.user.js index 691aece2..328692a8 100644 --- a/plugins/minimap.user.js +++ b/plugins/minimap.user.js @@ -11,6 +11,10 @@ // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== diff --git a/plugins/missions.css b/plugins/missions.css new file mode 100644 index 00000000..6ca45056 --- /dev/null +++ b/plugins/missions.css @@ -0,0 +1,162 @@ +.plugin-mission-pane { + background: transparent; + border: 0 none !important; + height: 100% !important; + width: 100% !important; + left: 0 !important; + top: 0 !important; + position: absolute; + overflow: auto; +} + +.plugin-mission-summary { + padding: 5px; + border-top: black solid 1px; + min-height: 50px; + position: relative; + clear: left; +} +.plugin-mission-summary.checked::after { + content: "✓"; + display: block; + pointer-events: none; + position: absolute; + text-align: center; + color: rgba(255, 187, 0, 0.3); + left: 5px; + top: 5px; + font-size: 50px; + line-height: 50px; + width: 50px; +} +.plugin-mission-summary:first-child { + border-top-width: 0px; +} + +.plugin-mission-summary.checked { + background-color: rgba(255, 187, 0, 0.3); +} + +.plugin-mission-summary > img { + float: left; + cursor: pointer; + width: 50px; + margin-right: 10px; + margin-bottom: 5px; +} + +.plugin-mission-summary > a { + display: block; + font-weight: bold; + font-size: 1.3em; + margin: 0 0 2px 60px; +} + +.plugin-mission-summary > br { + margin-bottom: 2px; +} + +.plugin-mission-summary > .nickname { + display: inline-block; + box-sizing: border-box; + min-width: 8em; /* to align with time */ + padding-right: 0.2em; +} + +.plugin-mission-info .portal-distance-bearing { + font-size: 14px; + margin-right: 8px; + color: #b2fbff; +} + +.plugin-mission-info { + display: inline-block; +} +.plugin-mission-info.length { min-width: 6em; } +.plugin-mission-info.distance { min-width: 6em; } +.plugin-mission-info.time { min-width: 8em; } +.plugin-mission-info.rating { min-width: 6em; } +.plugin-mission-info.players { min-width: 4em; } +.plugin-mission-info.type { min-width: 4em; } + +.plugin-mission-info img { + height: 14px; + margin-right: 8px; + vertical-align: top; +} +.plugin-mission-info.players img { + padding: 0 3px; /* the icon is 12x18 */ +} + +.plugin-mission-details .plugin-mission-summary > a, +.plugin-mission-details .plugin-mission-summary .description { + white-space: pre-line; + margin-left: 110px; +} + +.plugin-mission-details .plugin-mission-summary.checked::after { + left: 0px; + top: 0px; + font-size: 100px; + line-height: 100px; + width: 100px; +} + +.plugin-mission-details .plugin-mission-summary { + padding: 0; + background-color: transparent; +} + +.plugin-mission-details .plugin-mission-summary > img { + width: 100px; +} + +.plugin-mission-details ol { + clear: left; + list-style: none; + margin: 10px 0 0; + padding: 0; +} + +.plugin-mission-portal-indicator { + position: relative; + text-align: center; + float: left; + line-height: 18px; + height: 18px; + width: 18px; + margin-right: 5px; +} + +.plugin-mission-portal-indicator div { + border-color: currentcolor transparent transparent; + border-style: solid; + border-width: 2px 1px; + box-sizing: border-box; + height: 0; + left: 6px; + position: absolute; + top: 0; + /* Firefox supports transform* without vendor prefix, but Android does not yet */ + transform-origin: 4px 9px 0; + -webkit-transform-origin: 4px 9px 0; + width: 8px; +} + +.plugin-mission-waypoint.unavailable { + text-decoration: line-through; +} +.plugin-mission-waypoint .title { + font-size: 18px; + font-weight: bold; +} +.plugin-mission-waypoint label { + clear: left; + display: block; +} +.plugin-mission-waypoint input { + box-sizing: border-box; + margin: 3px 5px 8px 0; + width: 18px; +} + diff --git a/plugins/missions.user.js b/plugins/missions.user.js index 97b94a4b..72522d53 100644 --- a/plugins/missions.user.js +++ b/plugins/missions.user.js @@ -2,15 +2,19 @@ // @id iitc-plugin-missions@jonatkins // @name IITC plugin: Missions // @category Info -// @version 0.0.1.@@DATETIMEVERSION@@ +// @version 0.1.1.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ -// @description [@@BUILDNAME@@-@@BUILDDATE@@] WORK IN PROGRESS: view missions. Currently, only adds a mission start portal highlighter +// @description [@@BUILDNAME@@-@@BUILDDATE@@] View missions. Marking progress on waypoints/missions basis. Showing mission paths on the map. // @include https://www.ingress.com/intel* // @include http://www.ingress.com/intel* // @match https://www.ingress.com/intel* // @match http://www.ingress.com/intel* +// @include https://www.ingress.com/mission/* +// @include http://www.ingress.com/mission/* +// @match https://www.ingress.com/mission/* +// @match http://www.ingress.com/mission/* // @grant none // ==/UserScript== @@ -19,32 +23,958 @@ // PLUGIN START //////////////////////////////////////////////////////// -// use own namespace for plugin -window.plugin.missions = function() {}; +var decodeWaypoint = function(data) { + var result = { + hidden: data[0], + guid: data[1], + title: data[2], + typeNum: data[3], + type: [null, 'Portal', 'Field Trip'][data[3]], + objectiveNum: data[4], + objective: [null, 'Hack this Portal', 'Capture or Upgrade Portal', 'Create Link from Portal', 'Create Field from Portal', 'Install a Mod on this Portal', 'Take a Photo', 'View this Field Trip Waypoint', 'Enter the Passphrase'][data[4]], + }; + if (result.typeNum === 1 && data[5]) { + result.portal = window.decodeArray.portalSummary(data[5]); + // Portal waypoints have the same guid as the respective portal. + result.portal.guid = result.guid; + } + return result; +}; +var decodeMission = function(data) { + return { + guid: data[0], + title: data[1], + description: data[2], + authorNickname: data[3], + authorTeam: data[4], + // Notice: this format is weird(100%: 1.000.000) + ratingE6: data[5], + medianCompletionTimeMs: data[6], + numUniqueCompletedPlayers: data[7], + typeNum: data[8], + type: [null, 'Sequential', 'Non Sequential', 'Hidden'][data[8]], + waypoints: data[9].map(decodeWaypoint), + image: data[10] + }; +}; +var decodeMissionSummary = function(data) { + return { + guid: data[0], + title: data[1], + image: data[2], + ratingE6: data[3], + medianCompletionTimeMs: data[4] + }; +}; +var timeToRemaining = function(t) { + var data = parseInt(t / 86400) + 'd ' + (new Date(t % 86400 * 1000)).toUTCString().replace(/.*(\d{2}):(\d{2}):(\d{2}).*/, '$1h $2m $3s'); + data = data.replace('0d', ''); + data = data.replace('00h', ''); + data = data.replace('00m', ''); + return data.trim(); +}; +window.plugin.missions = { + // 3 days. + missionCacheTime: 3 * 24 * 3600 * 1E3, + // 3 weeks. + portalMissionsCacheTime: 21 * 24 * 3600 * 1E3, + SYNC_DELAY: 5000, + enableSync: false, -window.plugin.missions.highlight = function(data) { - var opacity = 0.7; - var color = undefined; + missionTypeImages: [ + '@@INCLUDEIMAGE:images/mission-type-unknown.png@@', + '@@INCLUDEIMAGE:images/mission-type-sequential.png@@', + '@@INCLUDEIMAGE:images/mission-type-random.png@@', + '@@INCLUDEIMAGE:images/mission-type-hidden.png@@', + ], - if (data.portal.options.data.mission50plus) { - color='red'; - } else if (data.portal.options.data.mission) { - color='darkorange'; - } + onPortalSelected: function(event) { + /*if(event.selectedPortalGuid === event.unselectedPortalGuid) { + return; + }*/ + if (window.selectedPortal === null) { + return; + } + var portal = window.portals[window.selectedPortal]; + if (!portal || (!portal.options.data.mission && !portal.options.data.mission50plus)) { + return; + } + // After select. + setTimeout(function() { + // #resodetails + $('.linkdetails').append(''); + }, 0); + }, - if (color) { - data.portal.setStyle({fillColor: color, fillOpacity: opacity}); - } -} + openTopMissions: function(bounds) { + bounds = bounds || window.map.getBounds(); + this.loadMissionsInBounds(bounds, this.showMissionListDialog.bind(this)); + }, -window.plugin.missions.setup = function() { + openPortalMissions: function() { + var me = this, + portal = window.portals[window.selectedPortal]; + if (!portal) { + return; + } + this.loadPortalMissions(window.selectedPortal, function(missions) { + if (!missions.length) { + return; + } - window.addPortalHighlighter('Mission start point', window.plugin.missions.highlight); + if (missions.length === 1) { + me.loadMission(missions[0].guid, me.showMissionDialog.bind(me)); + } else { + me.showMissionListDialog(missions); + } + }); + }, + openMission: function(guid) { + this.loadMission(guid, this.showMissionDialog.bind(this)); + }, + + showMissionDialog: function(mission) { + var me = this; + var markers = this.highlightMissionPortals(mission); + var content = this.renderMission(mission); + var id = mission.guid.replace(/\./g, '_'); // dots irritate the dialog framework and are not allowed in HTML IDs + + if(useAndroidPanes()) { + if(this.tabHeaders[id]) { + this.tabHeaders[id].parentNode.querySelector('.ui-icon-close').click(); + } + + var li = this.tabBar.appendChild(document.createElement('li')); + var a = li.appendChild(document.createElement('a')); + a.textContent = mission.title; + a.href = '#mission_pane_'+id; + this.tabHeaders[id] = a; + var span = li.appendChild(document.createElement('span')); + span.className = 'ui-icon ui-icon-close'; + span.textContent = 'Close mission'; + span.addEventListener('click', function() { + this.unhighlightMissionPortals(markers); + li.parentNode.removeChild(li); + content.parentNode.removeChild(content); + delete this.tabHeaders[id]; + $(this.tabs) + .tabs('refresh') + .find('.ui-tabs-nav') + .sortable('refresh'); + }.bind(this), false); + + this.tabs.appendChild(content); + content.id = 'mission_pane_'+id; + var tabs = $(this.tabs); + tabs.tabs('refresh'); + tabs.find('.ui-tabs-nav').sortable('refresh'); + tabs.tabs('option','active', -1); + if(window.isSmartphone()) + show('plugin-missions'); + } else { + dialog({ + id: 'plugin-mission-details-' + id, + title: mission.title, + height: 'auto', + html: content, + width: '450px', + closeCallback: function() { + me.unhighlightMissionPortals(markers); + }, + collapseCallback: this.collapseFix, + expandCallback: this.collapseFix, + }).dialog('option', 'buttons', { + 'Zoom to mission': function() { + me.zoomToMission(mission); + }, + 'OK': function() { $(this).dialog('close'); }, + }); + } + }, + + showMissionListDialog: function(missions) { + dialog({ + html: this.renderMissionList(missions), + height: 'auto', + width: '400px', + collapseCallback: this.collapseFix, + expandCallback: this.collapseFix, + }).dialog('option', 'buttons', { + 'Create new mission': function() { open('//mission-author-dot-betaspike.appspot.com'); }, + 'OK': function() { $(this).dialog('close'); }, + }); + }, + + collapseFix: function() { + if (this && this.parentNode) { + this.parentNode.style.height = 'auto'; + } + }, + + zoomToMission: function(mission) { + var latlngs = mission.waypoints.filter(function(waypoint) { + return !!waypoint.portal; + }).map(function(waypoint) { + return [waypoint.portal.latE6/1E6, waypoint.portal.lngE6/1E6]; + }); + + map.fitBounds(L.latLngBounds(latlngs), {maxZoom: 17}); + }, + + loadMissionsInBounds: function(bounds, callback, errorcallback) { + var me = this; + window.postAjax('getTopMissionsInBounds', { + northE6: ((bounds.getNorth() * 1000000) | 0), + southE6: ((bounds.getSouth() * 1000000) | 0), + westE6: ((bounds.getWest() * 1000000) | 0), + eastE6: ((bounds.getEast() * 1000000) | 0) + }, function(data) { + var missions = data.result.map(decodeMissionSummary); + if (!missions) { + if (errorcallback) { + errorcallback('Invalid data'); + } + return; + } + callback(missions); + }, function(error) { + console.log('Error loading missions in bounds', arguments); + if (errorcallback) { + errorcallback(error); + } + }); + }, + + loadPortalMissions: function(guid, callback, errorcallback) { + var me = this; + // Mission summary rarely goes stale. + if (me.cacheByPortalGuid[guid] && this.cacheByPortalGuid[guid].time > (Date.now() - this.portalMissionsCacheTime)) { + callback(me.cacheByPortalGuid[guid].data); + return; + } + window.postAjax('getTopMissionsForPortal', { + guid: window.selectedPortal + }, function(data) { + var missions = data.result.map(decodeMissionSummary); + if (!missions) { + if (errorcallback) { + errorcallback('Invalid data'); + } + return; + } + + window.runHooks('plugin-missions-on-portal-loaded', { missions: missions, portalguid: guid }); + + me.cacheByPortalGuid[guid] = { + time: Date.now(), + data: missions + }; + me.storeCache(); + callback(missions); + }, function(error) { + console.log('Error loading portal missions', arguments); + if (errorcallback) { + errorcallback(error); + } + // awww + }); + }, + loadMission: function(guid, callback, errorcallback) { + var me = this; + // TODO: we need to refresh data often enough, portal data can quickly go stale + if (this.cacheByMissionGuid[guid] && this.cacheByMissionGuid[guid].time > (Date.now() - this.missionCacheTime)) { + callback(this.getMissionCache(guid, true)); + return; + } + window.postAjax('getMissionDetails', { + guid: guid + }, function(data) { + var mission = decodeMission(data.result); + if (!mission) { + if (errorcallback) { + errorcallback('Invalid data'); + } + return; + } + + window.runHooks('plugin-missions-loaded-mission', { mission: mission }); + + me.cacheByMissionGuid[guid] = { + time: Date.now(), + data: mission + }; + me.storeCache(); + + callback(mission); + }, function() { + console.error('Error loading mission data: ' + guid + ', ' + Array.prototype.slice.call(arguments)); + + if (errorcallback) { + errorcallback(error); + } + // awww + }); + }, + + renderMissionList: function(missions) { + var container = document.createElement('div'); + missions.forEach(function(mission) { + container.appendChild(this.renderMissionSummary(mission)); + }, this); + return container; + }, + + renderMissionSummary: function(mission) { + var cachedMission = this.getMissionCache(mission.guid); + + var checked = this.checkedMissions[mission.guid]; + + var container = document.createElement('div'); + container.className = 'plugin-mission-summary'; + container.dataset['mission_mid'] = mission.guid; + if(checked) + container.classList.add('checked'); + + var img = container.appendChild(document.createElement('img')); + img.src = mission.image; + img.addEventListener('click', function(ev) { + plugin.missions.toggleMission(mission.guid); + }, false); + + var title = container.appendChild(document.createElement('a')); + title.textContent = mission.title; + title.href = '/mission/' + mission.guid; + title.addEventListener('click', function(ev) { + this.openMission(mission.guid); + // prevent browser from following link + ev.preventDefault(); + return false; + }.bind(this), false); + + if(cachedMission) { + var span = container.appendChild(document.createElement('span')); + span.className = 'nickname ' + (cachedMission.authorTeam === 'R' ? 'res' : 'enl') + span.textContent = cachedMission.authorNickname; + + var len = cachedMission.waypoints.filter(function(waypoint) { + return !!waypoint.portal; + }).map(function(waypoint) { + return L.latLng(waypoint.portal.latE6/1E6, waypoint.portal.lngE6/1E6); + }).map(function(latlng1, i, latlngs) { + if(i == 0) return 0; + var latlng2 = latlngs[i - 1]; + return latlng1.distanceTo(latlng2); + }).reduce(function(a, b) { + return a + b; + }); + + if(len > 0) { + if(len > 1000) + len = Math.round(len / 100) / 10 + 'km'; + else + len = Math.round(len * 10) / 10 + 'm'; + + var infoLength = container.appendChild(document.createElement('span')); + infoLength.className = 'plugin-mission-info length help'; + infoLength.title = 'Length of this mission.\n\nNOTE: The actual distance required to cover may vary depending on several factors!'; + infoLength.textContent = len; + img = infoLength.insertBefore(document.createElement('img'), infoLength.firstChild); + img.src = '@@INCLUDEIMAGE:images/mission-length.png@@'; + } + + if(window.plugin.distanceToPortal && window.plugin.distanceToPortal.currentLoc) { + var infoDistance = container.appendChild(document.createElement('span')); + infoDistance.className = 'plugin-mission-info distance help'; + infoDistance.title = 'Distance to this mission. Click to update.'; + infoDistance.addEventListener('click', function() { + plugin.missions.renderMissionDistance(cachedMission, infoDistance); + }, false); + this.renderMissionDistance(cachedMission, infoDistance); + } + } + + container.appendChild(document.createElement('br')); + + var infoTime = container.appendChild(document.createElement('span')); + infoTime.className = 'plugin-mission-info time help'; + infoTime.title = 'Typical duration'; + infoTime.textContent = timeToRemaining((mission.medianCompletionTimeMs / 1000) | 0) + ' '; + img = infoTime.insertBefore(document.createElement('img'), infoTime.firstChild); + img.src = 'https://commondatastorage.googleapis.com/ingress.com/img/tm_icons/time.png'; + + var infoRating = container.appendChild(document.createElement('span')); + infoRating.className = 'plugin-mission-info rating help'; + infoRating.title = 'Average rating'; + infoRating.textContent = (((mission.ratingE6 / 100) | 0) / 100) + '%' + ' '; + img = infoRating.insertBefore(document.createElement('img'), infoRating.firstChild); + img.src = 'https://commondatastorage.googleapis.com/ingress.com/img/tm_icons/like.png'; + + if (cachedMission) { + var infoPlayers = container.appendChild(document.createElement('span')); + infoPlayers.className = 'plugin-mission-info players help'; + infoPlayers.title = 'Unique players who have completed this mission'; + infoPlayers.textContent = cachedMission.numUniqueCompletedPlayers + ' '; + img = infoPlayers.insertBefore(document.createElement('img'), infoPlayers.firstChild); + img.src = 'https://commondatastorage.googleapis.com/ingress.com/img/tm_icons/players.png'; + + var infoWaypoints = container.appendChild(document.createElement('span')); + infoWaypoints.className = 'plugin-mission-info waypoints help'; + infoWaypoints.title = (cachedMission.type ? cachedMission.type + ' mission' : 'Unknown mission type') + + ' with ' + cachedMission.waypoints.length + ' waypoints'; + infoWaypoints.textContent = cachedMission.waypoints.length + ' '; + img = infoWaypoints.insertBefore(document.createElement('img'), infoWaypoints.firstChild); + img.src = this.missionTypeImages[cachedMission.typeNum] || this.missionTypeImages[0]; + } + + return container; + }, + + renderMissionDistance: function(mission /* cached mission, full details*/, container) { + if(!(plugin.distanceToPortal && plugin.distanceToPortal.currentLoc)) return; + + var distances = mission.waypoints + .filter(function(waypoint) { + return !!waypoint.portal; + }) + .map(function(waypoint) { + var position = L.latLng(waypoint.portal.latE6/1E6, waypoint.portal.lngE6/1E6); + var distance = position.distanceTo(plugin.distanceToPortal.currentLoc); + return { + waypoint: waypoint, + distance: distance, + position: position, + }; + }); + + if(!distances.length) return; + + if(mission.typeNum == 2) { // non-sequential + distances.sort(function(a, b) { return a.distance - b.distance; }); + } + + var position = distances[0].position; + var distance = distances[0].distance; + + var bearing = window.plugin.distanceToPortal.currentLoc.bearingTo(position); + + $(container) + .text(window.plugin.distanceToPortal.formatDistance(distance)) + .prepend($('') + .addClass('portal-distance-bearing') + .css({ + 'transform': 'rotate('+bearing+'deg)', + '-moz-transform': 'rotate('+bearing+'deg)', + '-webkit-transform': 'rotate('+bearing+'deg)', + })); + }, + + renderMission: function(mission) { + var container = document.createElement('div'); + container.className = 'plugin-mission-details'; + + var summary = container.appendChild(this.renderMissionSummary(mission)); + + var desc = summary.appendChild(document.createElement('p')); + desc.className = 'description'; + desc.textContent = mission.description; + + var list = container.appendChild(document.createElement('ol')) + mission.waypoints.forEach(function(waypoint, index) { + list.appendChild(this.renderMissionWaypoint(waypoint, index, mission)); + }, this); + + return container; + }, + + renderMissionWaypoint: function(waypoint, index, mission) { + var container = document.createElement('li'); + container.className = 'plugin-mission-waypoint'; + + if (waypoint.portal) { + container.appendChild(this.renderPortalCircle(waypoint.portal)); + + var title = container.appendChild(document.createElement('a')); + + var lat = waypoint.portal.latE6/1E6; + var lng = waypoint.portal.lngE6/1E6; + var perma = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng; + + title.href = perma; + title.addEventListener('click', function(ev) { + if(window.isSmartphone()) + show('map'); + selectPortalByLatLng(lat, lng); + ev.preventDefault(); + return false; + }, false); + title.addEventListener('dblclick', function(ev) { + if(window.isSmartphone()) + show('map'); + zoomToAndShowPortal(waypoint.portal.guid, [lat, lng]); + ev.preventDefault(); + return false; + }, false); + } else if(waypoint.typeNum === 1) { + // if typeNum === 1 but portal is undefined, this waypoint is a deleted portal. + var title = container.appendChild(document.createElement('span')); + container.classList.add('unavailable'); + } else { + var title = container.appendChild(document.createElement('span')); + } + + title.className = 'title'; + if(waypoint.title) + title.textContent = waypoint.title; + else if(waypoint.portal && waypoint.portal.title) + title.textContent = waypoint.portal.title; + else + title.textContent = 'Unknown'; + + var mwpid = mission.guid + '-' + index + '-' + waypoint.guid; + var checked = this.checkedWaypoints[mwpid]; + + var label = container.appendChild(document.createElement('label')); + + var checkbox = label.appendChild(document.createElement('input')); + checkbox.type = 'checkbox'; + checkbox.addEventListener('change', function() { + plugin.missions.toggleWaypoint(mission.guid, mwpid); + }, false); + checkbox.dataset['mission_mwpid'] = mwpid; + + var objective = label.appendChild(document.createElement('span')); + objective.textContent = waypoint.objective ? waypoint.objective : '?'; + + return container; + }, + + renderPortalCircle: function(portal) { + var team = TEAM_TO_CSS[getTeam(portal)]; + var resCount = portal.resCount; + var level = resCount == 0 ? 0 : portal.level; // we want neutral portals to be level 0 + + var container = document.createElement('div'); + container.className = 'plugin-mission-portal-indicator help ' + team; + container.textContent = level; + container.title = 'Level:\t'+level+'\nResonators:\t'+resCount+'\nHealth:\t'+portal.health+'%'; + + for(var i = 0; i< resCount; i++) { + var resonator = container.appendChild(document.createElement('div')); + /* Firefox supports transform* without vendor prefix, but Android does not yet */ + resonator.style.transform = 'rotate(' + i*45 + 'deg)'; + resonator.style.webkitTransform = 'rotate(' + i*45 + 'deg)'; + } + return container; + }, + + toggleWaypoint: function(mid, mwpid, dontsave) { + if(this.checkedWaypoints[mwpid]) + delete this.checkedWaypoints[mwpid]; + else + this.checkedWaypoints[mwpid] = true; + + window.runHooks('plugin-missions-waypoint-changed', { mwpid: mwpid, }); + if (!dontsave) { + this.checkedWaypointsUpdateQueue[mwpid] = true; + this.storeLocal('checkedWaypoints'); + this.storeLocal('checkedWaypointsUpdateQueue'); + this.syncQueue(); + } + }, + + onWaypointChanged: function(data) { + var mwpid = data.mwpid; + + var checked = !!this.checkedWaypoints[mwpid]; + + $('[data-mission_mwpid="'+mwpid+'"]').prop('checked', checked); + }, + + onWaypointsRefreshed: function() { + $('[data-mission_mwpid]').each(function(i, element) { + var mwpid = element.dataset['mission_mwpid']; + var checked = !!this.checkedWaypoints[mwpid]; + element.checked = checked; + }); + }, + + toggleMission: function(mid) { + if(this.checkedMissions[mid]) + delete this.checkedMissions[mid]; + else + this.checkedMissions[mid] = true; + + window.runHooks('plugin-missions-mission-changed', { mid: mid, }); + this.checkedMissionsUpdateQueue[mid] = true; + this.storeLocal('checkedMissions'); + this.storeLocal('checkedMissionsUpdateQueue'); + this.syncQueue(); + }, + + onMissionChanged: function(data) { + var mid = data.mid; + + var checked = !!this.checkedMissions[mid]; + + $('[data-mission_mid="'+mid+'"]').toggleClass('checked', checked); + }, + + onMissionsRefreshed: function() { + $('[data-mission_mid]').each(function(i, element) { + var mid = element.dataset['mission_mid']; + var checked = !!this.checkedMissions[mid]; + $(element).toggleClass('checked', checked); + }); + }, + + getMissionCache: function(guid, updatePortals) { + if (this.cacheByMissionGuid[guid]) { + var cache = this.cacheByMissionGuid[guid]; + // Update portal data from map if older then 2 minutes. + if (updatePortals && cache.time < (Date.now() - (2 * 60 * 1000))) { + cache.data.waypoints.map(function(waypoint) { + if (!waypoint.portal) { + return; + } + var wp = window.portals[waypoint.portal.guid]; + if (!wp) { + return; + } + $.extend(waypoint.portal, wp.options.data); + }); + } + return cache.data; + } + return null; + }, + + getPortalCache: function(guid) { + if (this.cacheByPortalGuid[guid]) { + return this.cacheByPortalGuid[guid].data; + } + return null; + }, + + storeCache: function() { + this.checkCacheSize(); + localStorage['plugins-missions-portalcache'] = JSON.stringify(this.cacheByPortalGuid); + localStorage['plugins-missions-missioncache'] = JSON.stringify(this.cacheByMissionGuid); + }, + + storeLocal: function(key) { + localStorage['plugins-missions-' + key] = JSON.stringify(this[key]); + }, + + loadData: function() { + this.cacheByPortalGuid = JSON.parse(localStorage['plugins-missions-portalcache'] || '{}'); + this.cacheByMissionGuid = JSON.parse(localStorage['plugins-missions-missioncache'] || '{}'); + + if('plugins-missions-settings' in localStorage) { + var settings = JSON.parse(localStorage['plugins-missions-settings'] || '{}'); + localStorage['plugins-missions-checkedMissions'] = JSON.stringify(settings.checkedMissions); + localStorage['plugins-missions-checkedWaypoints'] = JSON.stringify(settings.checkedWaypoints); + delete localStorage['plugins-missions-settings']; + } + + this.loadLocal('checkedMissions'); + this.loadLocal('checkedMissionsUpdateQueue'); + this.loadLocal('checkedMissionsUpdatingQueue'); + this.loadLocal('checkedWaypoints'); + this.loadLocal('checkedWaypointsUpdateQueue'); + this.loadLocal('checkedWaypointsUpdatingQueue'); + }, + + loadLocal: function(key) { + this[key] = JSON.parse(localStorage['plugins-missions-' + key] || '{}'); + }, + + checkCacheSize: function() { + if (JSON.stringify(this.cacheByPortalGuid).length > 1e6) { // 1 MB not MiB ;) + this.cleanupPortalCache(); + } + if (JSON.stringify(this.cacheByMissionGuid).length > 2e6) { // 2 MB not MiB ;) + this.cleanupMissionCache(); + } + }, + + // Cleanup oldest half of the data. + cleanupPortalCache: function() { + var me = this; + var cache = Object.keys(this.cacheByPortalGuid); + cache.sort(function(a, b) { + return me.cacheByPortalGuid[a].time - me.cacheByPortalGuid[b].time; + }); + var toDelete = (cache.length / 2) | 0; + cache.splice(0, toDelete + 1).forEach(function(el) { + delete me.cacheByPortalGuid[el]; + }); + }, + + // Cleanup oldest half of the data. + cleanupMissionCache: function() { + var me = this; + var cache = Object.keys(this.cacheByMissionGuid); + cache.sort(function(a, b) { + return me.cacheByMissionGuid[a].time - me.cacheByMissionGuid[b].time; + }); + var toDelete = (cache.length / 2) | 0; + cache.splice(0, toDelete + 1).forEach(function(el) { + delete me.cacheByMissionGuid[el]; + }); + }, + + highlightMissionPortals: function(mission) { + var markers = []; + var latlngs = []; + + mission.waypoints.forEach(function(waypoint) { + if (!waypoint.portal) { + return; + } + + var radius = window.portals[waypoint.portal.guid] ? window.portals[waypoint.portal.guid].options.radius * 1.5 : 5; + var ll = [waypoint.portal.latE6 / 1E6, waypoint.portal.lngE6 / 1E6]; + latlngs.push(ll); + + var marker = L.circleMarker(ll, { + radius: radius, + weight: 3, + opacity: 1, + color: '#222', + fill: false, + dashArray: null, + clickable: false + } + ); + this.missionLayer.addLayer(marker); + markers.push(marker); + }, this); + + var line = L.geodesicPolyline(latlngs, { + color: '#222', + opacity: 1, + weight: 2, + clickable: false, + dashArray: (mission.typeNum == 2 /* non-sequential */ ? '1,5' : undefined), + }); + this.missionLayer.addLayer(line); + markers.push(line); + return markers; + }, + + unhighlightMissionPortals: function(markers) { + markers.forEach(function(marker) { + this.missionLayer.removeLayer(marker); + }, this); + }, + + onPortalChanged: function(type, guid, oldval) { + var portal; + if (type === 'add' || type === 'update') { + // Compatibility + portal = window.portals[guid] || oldval; + if (!portal.options.data.mission && !portal.options.data.mission50plus) { + return; + } + if (this.markedStarterPortals[guid]) { + return; + } + + this.markedStarterPortals[guid] = L.circleMarker( + L.latLng(portal.options.data.latE6 / 1E6, portal.options.data.lngE6 / 1E6), { + radius: portal.options.radius + Math.ceil(portal.options.radius / 2), + weight: 3, + opacity: 1, + color: '#555', + fill: false, + dashArray: null, + clickable: false + } + ); + this.missionStartLayer.addLayer(this.markedStarterPortals[guid]); + } else if (type === 'delete') { + portal = oldval; + if (!this.markedStarterPortals[guid]) { + return; + } + + this.missionStartLayer.removeLayer(this.markedStarterPortals[guid]); + delete this.markedStarterPortals[guid]; + } + }, + + // sync the queue, but delay the actual sync to group a few updates in a single request + syncQueue: function() { + if(!this.enableSync) return; + + clearTimeout(this.syncTimer); + + this.syncTimer = setTimeout(function() { + this.syncTimer = null; + + $.extend(this.checkedMissionsUpdatingQueue, this.checkedMissionsUpdateQueue); + this.checkedMissionsUpdateQueue = {}; + this.storeLocal('checkedMissionsUpdatingQueue'); + this.storeLocal('checkedMissionsUpdateQueue'); + plugin.sync.updateMap('missions', 'checkedMissions', Object.keys(this.checkedMissionsUpdatingQueue)); + + $.extend(this.checkedWaypointsUpdatingQueue, this.checkedWaypointsUpdateQueue); + this.checkedWaypointsUpdateQueue = {}; + this.storeLocal('checkedWaypointsUpdatingQueue'); + this.storeLocal('checkedWaypointsUpdateQueue'); + plugin.sync.updateMap('missions', 'checkedWaypoints', Object.keys(this.checkedWaypointsUpdatingQueue)); + + }.bind(this), this.SYNC_DELAY); + }, + + // called after IITC and all plugin loaded + registerFieldForSyncing: function() { + if(!window.plugin.sync) return; + window.plugin.sync.registerMapForSync('missions', 'checkedMissions', this.syncCallback.bind(this), this.syncInitialed.bind(this)); + window.plugin.sync.registerMapForSync('missions', 'checkedWaypoints', this.syncCallback.bind(this), this.syncInitialed.bind(this)); + }, + + // called after local or remote change uploaded + syncCallback: function(pluginName, fieldName, e, fullUpdated) { + this.storeLocal(fieldName); + // All data is replaced if another client updates the data while this client was offline, + // fire a complete refresh + if(fullUpdated) { + if(fieldName === 'checkedMissions') { + window.runHooks('plugin-missions-missions-refreshed'); + } else if(fieldName === 'checkedWaypoints') { + window.runHooks('plugin-missions-waypoints-refreshed'); + } + return; + } + + if(!e) return; + if(e.isLocal) { + // Update pushed successfully, remove it from updatingQueue + delete this[fieldName + 'UpdatingQueue'][e.property]; + } else { + // Remote update + delete this[fieldName + 'UpdateQueue'][e.property] + this.storeLocal(fieldName + 'UpdateQueue'); + + if(fieldName === 'checkedMissions') { + window.runHooks('plugin-missions-mission-changed', { mid: e.property, }); + } else if(fieldName === 'checkedWaypoints') { + window.runHooks('plugin-missions-waypoint-changed', { mwpid: e.property, }); + } + } + }, + + // syncing of the field is initialed, upload all queued update + syncInitialed: function(pluginName, fieldName) { + this.enableSync = true; + if(Object.keys(this[fieldName + 'UpdateQueue']).length > 0) { + this.syncQueue(); + } + }, + + onPaneChanged: function(pane) { + if(pane == 'plugin-missions') { + document.body.appendChild(this.mobilePane); + } else if(this.mobilePane.parentNode) { + this.mobilePane.parentNode.removeChild(this.mobilePane); + } + }, + + setup: function() { + this.cacheByPortalGuid = {}; + this.cacheByMissionGuid = {}; + + this.markedStarterPortals = {}; + this.markedMissionPortals = {}; + + this.loadData(); + + $('