diff --git a/core/code/map_data_render.js b/core/code/map_data_render.js
index 62a5f79ba..fc1c423d9 100644
--- a/core/code/map_data_render.js
+++ b/core/code/map_data_render.js
@@ -46,7 +46,9 @@ window.Render.prototype.clearPortalsOutsideBounds = function (bounds) {
var p = window.portals[guid];
// clear portals outside visible bounds - unless it's the selected portal, or it's relevant to artifacts
if (!bounds.contains(p.getLatLng()) && guid !== window.selectedPortal && !window.artifact.isInterestingPortal(guid)) {
- this.deletePortalEntity(guid);
+ // remove the marker as a layer first
+ // deletion will be done at endRenderPass
+ p.remove();
}
}
};
@@ -208,10 +210,7 @@ window.Render.prototype.endRenderPass = function () {
// reorder portals to be after links/fields
this.bringPortalsToFront();
- // re-select the selected portal, to re-render the side-bar. ensures that any data calculated from the map data is up to date
- if (window.selectedPortal) {
- window.renderPortalDetails(window.selectedPortal);
- }
+ this.isRendering = false;
};
/**
@@ -305,6 +304,7 @@ window.Render.prototype.deleteFieldEntity = function (guid) {
* @param {number} latE6 - The latitude of the portal in E6 format.
* @param {number} lngE6 - The longitude of the portal in E6 format.
* @param {string} team - The team faction of the portal.
+ * @param {number} [timestamp=0] - Timestamp of the portal data. Defaults to 0 to allow newer data sources to override
* @param {number} [timestamp] - The timestamp of the portal data.
*/
window.Render.prototype.createPlaceholderPortalEntity = function (guid, latE6, lngE6, team, timestamp) {
@@ -319,7 +319,7 @@ window.Render.prototype.createPlaceholderPortalEntity = function (guid, latE6, l
var ent = [
guid, // ent[0] = guid
- -1, // ent[1] = timestamp - zero will mean any other source of portal data will have a higher timestamp
+ timestamp, // ent[1] = timestamp
// ent[2] = an array with the entity data
[
'p', // 0 - a portal
@@ -329,22 +329,7 @@ window.Render.prototype.createPlaceholderPortalEntity = function (guid, latE6, l
],
];
- // placeholder portals don't have a useful timestamp value - so the standard code that checks for updated
- // portal details doesn't apply
- // so, check that the basic details are valid and delete the existing portal if out of date
- var portalMoved = false;
- if (guid in window.portals) {
- var p = window.portals[guid];
- portalMoved = latE6 !== p.options.data.latE6 || lngE6 !== p.options.data.lngE6;
- if (team !== p.options.data.team && p.options.timestamp < timestamp) {
- // team - delete existing portal
- this.deletePortalEntity(guid);
- }
- }
-
- if (!portalMoved) {
- this.createPortalEntity(ent, 'core'); // placeholder
- }
+ this.createPortalEntity(ent, 'core'); // placeholder
};
/**
@@ -362,102 +347,95 @@ window.Render.prototype.createPortalEntity = function (ent, details) {
var previousData = undefined;
var data = window.decodeArray.portal(ent[2], details);
+ var guid = ent[0];
+
+ // add missing fields
+ data.guid = guid;
+ if (!data.timestamp) {
+ data.timestamp = ent[1];
+ }
+
+ // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead
+ data.ent = ent;
// check if entity already exists
- if (ent[0] in window.portals) {
+ const oldPortal = guid in window.portals;
+
+ if (oldPortal) {
// yes. now check to see if the entity data we have is newer than that in place
- var p = window.portals[ent[0]];
+ var p = window.portals[guid];
- if (!data.history || p.options.data.history === data.history)
- if (p.options.timestamp >= ent[1]) {
- return; // this data is identical or older - abort processing
- }
+ if (!p.willUpdate(data)) {
+ // this data doesn't bring new detail - abort processing
+ // re-add the portal to the relevant layer (does nothing if already in the correct layer)
+ // useful for portals outside the view
+ this.addPortalToMapLayer(p);
+ return p;
+ }
// the data we have is newer. many data changes require re-rendering of the portal
// (e.g. level changed, so size is different, or stats changed so highlighter is different)
- // so to keep things simple we'll always re-create the entity in this case
// remember the old details, for the callback
-
- previousData = p.options.data;
-
- // preserve history
- if (!data.history) {
- data.history = previousData.history;
- }
-
- this.deletePortalEntity(ent[0]);
+ previousData = $.extend(true, {}, p.getDetails());
}
- var portalLevel = parseInt(data.level) || 0;
- var team = window.teamStringToId(data.team);
- // the data returns unclaimed portals as level 1 - but IITC wants them treated as level 0
- if (team === window.TEAM_NONE) portalLevel = 0;
-
var latlng = L.latLng(data.latE6 / 1e6, data.lngE6 / 1e6);
- var dataOptions = {
- level: portalLevel,
- team: team,
- ent: ent, // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead
- guid: ent[0],
- timestamp: ent[1],
- data: data,
- };
-
- window.pushPortalGuidPositionCache(ent[0], data.latE6, data.lngE6);
-
- var marker = window.createMarker(latlng, dataOptions);
-
- function handler_portal_click(e) {
- window.renderPortalDetails(e.target.options.guid);
- }
- function handler_portal_dblclick(e) {
- window.renderPortalDetails(e.target.options.guid);
- window.map.setView(e.target.getLatLng(), window.DEFAULT_ZOOM);
- }
- function handler_portal_contextmenu(e) {
- window.renderPortalDetails(e.target.options.guid);
- if (window.isSmartphone()) {
- window.show('info');
- } else if (!$('#scrollwrapper').is(':visible')) {
- $('#sidebartoggle').click();
- }
- }
-
- marker.on('click', handler_portal_click);
- marker.on('dblclick', handler_portal_dblclick);
- marker.on('contextmenu', handler_portal_contextmenu);
-
- window.runHooks('portalAdded', { portal: marker, previousData: previousData });
-
- window.portals[ent[0]] = marker;
+ window.pushPortalGuidPositionCache(data.guid, data.latE6, data.lngE6);
// check for URL links to portal, and select it if this is the one
- if (window.urlPortalLL && window.urlPortalLL[0] === marker.getLatLng().lat && window.urlPortalLL[1] === marker.getLatLng().lng) {
+ if (window.urlPortalLL && window.urlPortalLL[0] === latlng.lat && window.urlPortalLL[1] === latlng.lng) {
// URL-passed portal found via pll parameter - set the guid-based parameter
- log.log('urlPortalLL ' + window.urlPortalLL[0] + ',' + window.urlPortalLL[1] + ' matches portal GUID ' + ent[0]);
+ log.log('urlPortalLL ' + window.urlPortalLL[0] + ',' + window.urlPortalLL[1] + ' matches portal GUID ' + data.guid);
- window.urlPortal = ent[0];
+ window.urlPortal = data.guid;
window.urlPortalLL = undefined; // clear the URL parameter so it's not matched again
}
- if (window.urlPortal === ent[0]) {
+ if (window.urlPortal === data.guid) {
// URL-passed portal found via guid parameter - set it as the selected portal
log.log('urlPortal GUID ' + window.urlPortal + ' found - selecting...');
- window.selectedPortal = ent[0];
+ window.selectedPortal = data.guid;
window.urlPortal = undefined; // clear the URL parameter so it's not matched again
}
- // (re-)select the portal, to refresh the sidebar on any changes
- if (ent[0] === window.selectedPortal) {
- log.log('portal guid ' + ent[0] + ' is the selected portal - re-rendering portal details');
- window.renderPortalDetails(window.selectedPortal);
+ let marker = undefined;
+ if (oldPortal) {
+ // update marker style/highlight and layer
+ marker = window.portals[data.guid];
+
+ marker.updateDetails(data);
+
+ window.runHooks('portalAdded', { portal: marker, previousData: previousData });
+ } else {
+ marker = window.createMarker(latlng, data);
+
+ // in case of incomplete data while having fresh details in cache, update the portal with those details
+ if (window.portalDetail.isFresh(guid)) {
+ var oldDetails = window.portalDetail.get(guid);
+ if (data.timestamp > oldDetails.timestamp) {
+ // data is more recent than the cached details so we remove them from the cache
+ window.portalDetail.remove(guid);
+ } else if (marker.willUpdate(oldDetails)) {
+ marker.updateDetails(oldDetails);
+ }
+ }
+
+ window.runHooks('portalAdded', { portal: marker });
+
+ window.portals[data.guid] = marker;
+
+ if (window.selectedPortal === data.guid) {
+ marker.renderDetails();
+ }
}
window.ornaments.addPortal(marker);
// TODO? postpone adding to the map layer
this.addPortalToMapLayer(marker);
+
+ return marker;
};
/**
@@ -482,7 +460,7 @@ window.Render.prototype.createFieldEntity = function (ent) {
// create placeholder portals for field corners. we already do links, but there are the odd case where this is useful
for (var i = 0; i < 3; i++) {
var p = data.points[i];
- this.createPlaceholderPortalEntity(p.guid, p.latE6, p.lngE6, data.team, data.timestamp);
+ this.createPlaceholderPortalEntity(p.guid, p.latE6, p.lngE6, data.team, 0);
}
// check if entity already exists
diff --git a/core/code/map_data_request.js b/core/code/map_data_request.js
index 1402c4235..91a1073c1 100644
--- a/core/code/map_data_request.js
+++ b/core/code/map_data_request.js
@@ -65,14 +65,6 @@ window.MapDataRequest = function () {
// ensure we have some initial map status
this.setStatus('startup', undefined, -1);
-
- // add a portalDetailLoaded hook, so we can use the extended details to update portals on the map
- var _this = this;
- window.addHook('portalDetailLoaded', function (data) {
- if (data.success) {
- _this.render.createPortalEntity(data.ent, 'detailed');
- }
- });
};
/**
diff --git a/core/code/portal_detail.js b/core/code/portal_detail.js
index 39f25db6e..aa38386ef 100644
--- a/core/code/portal_detail.js
+++ b/core/code/portal_detail.js
@@ -54,27 +54,24 @@ window.portalDetail.isFresh = function (guid) {
return cache.isFresh(guid);
};
+window.portalDetail.remove = function (guid) {
+ return cache.remove(guid);
+};
+
var handleResponse = function (deferred, guid, data, success) {
if (!data || data.error || !data.result) {
success = false;
}
if (success) {
- var dict = window.decodeArray.portal(data.result, 'detailed');
-
// entity format, as used in map data
- var ent = [guid, dict.timestamp, data.result];
+ var ent = [guid, data.result[13], data.result];
+ var portal = window.mapDataRequest.render.createPortalEntity(ent, 'detailed');
- cache.store(guid, dict);
-
- // FIXME..? better way of handling sidebar refreshing...
-
- if (guid === window.selectedPortal) {
- window.renderPortalDetails(guid);
- }
+ cache.store(guid, portal.options.data);
- deferred.resolve(dict);
- window.runHooks('portalDetailLoaded', { guid: guid, success: success, details: dict, ent: ent });
+ deferred.resolve(portal.options.data);
+ window.runHooks('portalDetailLoaded', { guid: guid, success: success, details: portal.options.data, ent: ent });
} else {
if (data && data.error === 'RETRY') {
// server asked us to try again
diff --git a/core/code/portal_detail_display.js b/core/code/portal_detail_display.js
index d58662943..9c2489b7e 100644
--- a/core/code/portal_detail_display.js
+++ b/core/code/portal_detail_display.js
@@ -69,9 +69,12 @@ window.renderPortalUrl = function (lat, lng, title, guid) {
*
* @function renderPortalDetails
* @param {string|null} guid - The globally unique identifier of the portal to display details for.
+ * @param {boolean} [forceSelect=false] - If true, forces the portal to be selected even if it's already the current portal.
*/
-window.renderPortalDetails = function (guid) {
- window.selectPortal(window.portals[guid] ? guid : null);
+window.renderPortalDetails = function (guid, forceSelect) {
+ if (forceSelect || window.selectedPortal !== guid) {
+ window.selectPortal(window.portals[guid] ? guid : null, 'renderPortalDetails');
+ }
if ($('#sidebar').is(':visible')) {
window.resetScrollOnNewPortal();
window.renderPortalDetails.lastVisible = guid;
@@ -95,34 +98,29 @@ window.renderPortalDetails = function (guid) {
}
var portal = window.portals[guid];
- var data = portal.options.data;
- var details = window.portalDetail.get(guid);
- var historyDetails = window.getPortalHistoryDetails(data);
-
- // details and data can get out of sync. if we have details, construct a matching 'data'
- if (details) {
- data = window.getPortalSummaryData(details);
- }
+ var details = portal.getDetails();
+ var hasFullDetails = portal.hasFullDetails();
+ var historyDetails = window.getPortalHistoryDetails(details);
- var modDetails = details ? '
' + window.getModDetails(details) + '
' : '';
- var miscDetails = details ? window.getPortalMiscDetails(guid, details) : '';
- var resoDetails = details ? window.getResonatorDetails(details) : '';
+ var modDetails = hasFullDetails ? '' + window.getModDetails(details) + '
' : '';
+ var miscDetails = hasFullDetails ? window.getPortalMiscDetails(guid, details) : '';
+ var resoDetails = hasFullDetails ? window.getResonatorDetails(details) : '';
// TODO? other status details...
- var statusDetails = details ? '' : 'Loading details...
';
+ var statusDetails = hasFullDetails ? '' : 'Loading details...
';
- var img = window.fixPortalImageUrl(details ? details.image : data.image);
- var title = (details && details.title) || (data && data.title) || 'null';
+ var img = window.fixPortalImageUrl(details.image);
+ var title = details.title || 'null';
- var lat = data.latE6 / 1e6;
- var lng = data.lngE6 / 1e6;
+ var lat = details.latE6 / 1e6;
+ var lng = details.lngE6 / 1e6;
var imgTitle = title + '\n\nClick to show full image.';
// portal level. start with basic data - then extend with fractional info in tooltip if available
- var levelInt = window.teamStringToId(data.team) === window.TEAM_NONE ? 0 : data.level;
+ var levelInt = portal.options.level;
var levelDetails = levelInt;
- if (details) {
+ if (hasFullDetails) {
levelDetails = window.getPortalLevel(details);
if (levelDetails !== 8) {
if (levelDetails === Math.ceil(levelDetails)) levelDetails += '\n8';
@@ -136,7 +134,7 @@ window.renderPortalDetails = function (guid) {
$('#portaldetails')
.html('') // to ensure it's clear
- .attr('class', window.TEAM_TO_CSS[window.teamStringToId(data.team)])
+ .attr('class', window.TEAM_TO_CSS[window.teamStringToId(details.team)])
.append(
$('', { class: 'title' })
.text(title)
@@ -147,7 +145,7 @@ window.renderPortalDetails = function (guid) {
style: 'float: left',
})
.click(function () {
- window.zoomToAndShowPortal(guid, [data.latE6 / 1e6, data.lngE6 / 1e6]);
+ window.zoomToAndShowPortal(guid, [details.latE6 / 1e6, details.lngE6 / 1e6]);
if (window.isSmartphone()) {
window.show('map');
}
@@ -187,9 +185,12 @@ window.renderPortalDetails = function (guid) {
window.renderPortalUrl(lat, lng, title, guid);
+ // compatibility
+ var data = hasFullDetails ? window.getPortalSummaryData(details) : details;
+
// only run the hooks when we have a portalDetails object - most plugins rely on the extended data
// TODO? another hook to call always, for any plugins that can work with less data?
- if (details) {
+ if (hasFullDetails) {
window.runHooks('portalDetailsUpdated', { guid: guid, portal: portal, portalDetails: details, portalData: data });
}
};
@@ -337,7 +338,7 @@ window.setPortalIndicators = function (p) {
* @param {string} guid - The GUID of the portal to select.
* @returns {boolean} True if the same portal is re-selected (just an update), false if a different portal is selected.
*/
-window.selectPortal = function (guid) {
+window.selectPortal = function (guid, event) {
var update = window.selectedPortal === guid;
var oldPortalGuid = window.selectedPortal;
window.selectedPortal = guid;
@@ -346,20 +347,18 @@ window.selectPortal = function (guid) {
var newPortal = window.portals[guid];
// Restore style of unselected portal
- if (!update && oldPortal) window.setMarkerStyle(oldPortal, false);
+ if (!update && oldPortal) oldPortal.setSelected(false);
// Change style of selected portal
- if (newPortal) {
- window.setMarkerStyle(newPortal, true);
-
- if (window.map.hasLayer(newPortal)) {
- newPortal.bringToFront();
- }
- }
+ if (newPortal) newPortal.setSelected(true);
window.setPortalIndicators(newPortal);
- window.runHooks('portalSelected', { selectedPortalGuid: guid, unselectedPortalGuid: oldPortalGuid });
+ window.runHooks('portalSelected', {
+ selectedPortalGuid: guid,
+ unselectedPortalGuid: oldPortalGuid,
+ event: event,
+ });
return update;
};
diff --git a/core/code/portal_highlighter.js b/core/code/portal_highlighter.js
index a55b1b138..c40f6c8bb 100644
--- a/core/code/portal_highlighter.js
+++ b/core/code/portal_highlighter.js
@@ -113,7 +113,7 @@ window.changePortalHighlights = function (name) {
*/
window.highlightPortal = function (p) {
if (window._highlighters !== null && window._highlighters[window._current_highlighter] !== undefined) {
- window._highlighters[window._current_highlighter].highlight({ portal: p });
+ return window._highlighters[window._current_highlighter].highlight({ portal: p });
}
};
diff --git a/core/code/portal_info.js b/core/code/portal_info.js
index 6cd62b16b..9a210901f 100644
--- a/core/code/portal_info.js
+++ b/core/code/portal_info.js
@@ -59,6 +59,21 @@ window.getCurrentPortalEnergy = function (d) {
return nrg;
};
+/**
+ * Calculates the health percentage of a portal based on its current and total energy.
+ *
+ * @function getPortalHealth
+ * @param {Object} d - The portal detail object containing resonator information.
+ * @returns {number} The portal health as a percentage (0-100).
+ * Returns 0 if the portal has no total energy.
+ */
+window.getPortalHealth = function (d) {
+ var max = window.getTotalPortalEnergy(d);
+ var cur = window.getCurrentPortalEnergy(d);
+
+ return max > 0 ? Math.floor((cur / max) * 100) : 0;
+};
+
/**
* Calculates the range of a portal for creating links. The range depends on portal level and any installed Link Amps.
*
diff --git a/core/code/portal_marker.js b/core/code/portal_marker.js
index 0ad3f5192..4a2abf4d9 100644
--- a/core/code/portal_marker.js
+++ b/core/code/portal_marker.js
@@ -1,10 +1,282 @@
-/* global L -- eslint */
+/* global IITC, L, log -- eslint */
/**
* @file This file contains the code related to creating and updating portal markers on the map.
* @module portal_marker
*/
+// portal hooks
+function handler_portal_click(e) {
+ window.selectPortal(e.target.options.guid, e.type);
+ window.renderPortalDetails(e.target.options.guid);
+}
+
+function handler_portal_dblclick(e) {
+ window.selectPortal(e.target.options.guid, e.type);
+ window.renderPortalDetails(e.target.options.guid);
+ window.map.setView(e.target.getLatLng(), window.DEFAULT_ZOOM);
+}
+
+function handler_portal_contextmenu(e) {
+ window.selectPortal(e.target.options.guid, e.type);
+ window.renderPortalDetails(e.target.options.guid);
+ if (window.isSmartphone()) {
+ window.show('info');
+ } else if (!$('#scrollwrapper').is(':visible')) {
+ $('#sidebartoggle').click();
+ }
+}
+
+L.PortalMarker = L.CircleMarker.extend({
+ options: {},
+
+ statics: {
+ // base style
+ portalBaseStyle: {
+ stroke: true,
+ opacity: 1,
+ fill: true,
+ fillOpacity: 0.5,
+ interactive: true,
+ },
+ // placeholder style
+ placeholderStyle: {
+ dashArray: '1,2',
+ weight: 1,
+ },
+ // portal level 0 1 2 3 4 5 6 7 8
+ LEVEL_TO_WEIGHT: [2, 2, 2, 2, 2, 3, 3, 4, 4],
+ LEVEL_TO_RADIUS: [7, 7, 7, 7, 8, 8, 9, 10, 11],
+ },
+
+ initialize: function (latlng, data) {
+ L.CircleMarker.prototype.initialize.call(this, latlng);
+ this._selected = data.guid === window.selectedPortal;
+ this.updateDetails(data);
+
+ this.on('click', handler_portal_click);
+ this.on('dblclick', handler_portal_dblclick);
+ this.on('contextmenu', handler_portal_contextmenu);
+ },
+
+ willUpdate: function (details) {
+ // details are from a placeholder
+ if (details.level === undefined) {
+ // if team differs and corresponding link is more recent (ignore field)
+ return this._details.timestamp < details.timestamp && this._details.team !== details.team;
+ }
+ // more recent timestamp, this occurs when the data has changed because of:
+ // - resonator deploy/upgrade
+ // - mod deploy
+ // - recharge/damage/decay
+ // - portal edit (title, location, portal main picture)
+ if (this._details.timestamp < details.timestamp) {
+ return true;
+ }
+ // current marker is a placeholder, and details is real data
+ if (this.isPlaceholder() && this._details.team === details.team) {
+ return true;
+ }
+ // even if we get history that was missing ? is it even possible ?
+ if (this._details.timestamp > details.timestamp) {
+ return false;
+ }
+
+ // this._details.timestamp === details.timestamp
+
+ // get new history
+ if (details.history) {
+ if (!this._details.history) {
+ return true;
+ }
+ if (this._details.history._raw !== details.history._raw) {
+ return true;
+ }
+ }
+
+ // get details portal data
+ if (!this._details.mods && details.mods) {
+ return true;
+ }
+
+ return false;
+ },
+
+ updateDetails: function (details) {
+ if (this._details) {
+ // portal has been moved
+ if (this._details.latE6 !== details.latE6 || this._details.lngE6 !== details.lngE6) {
+ this.setLatLng(L.latLng(details.latE6 / 1e6, details.lngE6 / 1e6));
+ }
+
+ // core data from a placeholder
+ if (details.level === undefined) {
+ // if team has changed
+ if (this._details.timestamp < details.timestamp && this._details.team !== details.team) {
+ // keep history, title, image
+ details.title = this._details.title;
+ details.image = this._details.image;
+ details.history = this._details.history;
+ this._details = details;
+ }
+ } else if (this._details.timestamp === details.timestamp) {
+ // we got more details (core/summary -> summary/detailed/extended)
+ var localThis = this;
+ [
+ 'level',
+ 'health',
+ 'resCount',
+ 'image',
+ 'title',
+ 'ornaments',
+ 'mission',
+ 'mission50plus',
+ 'artifactBrief',
+ 'mods',
+ 'resonators',
+ 'owner',
+ 'artifactDetail',
+ ].forEach(function (prop) {
+ if (details[prop]) localThis._details[prop] = details[prop];
+ });
+ // smarter update for history (cause it's missing sometimes)
+ if (details.history) {
+ if (!this._details.history) {
+ this._details.history = details.history;
+ } else {
+ if (this._details.history._raw && details.history._raw !== this._details.history._raw) {
+ log.warn('new portal data has lost some history');
+ }
+ this._details.history._raw |= details.history._raw;
+ ['visited', 'captured', 'scoutControlled'].forEach(function (prop) {
+ localThis._details.history[prop] ||= details.history[prop];
+ });
+ }
+ }
+ // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead
+ this._details.ent = details.ent;
+ } else {
+ // permanent data (history only)
+ if (!details.history) {
+ details.history = this._details.history;
+ }
+
+ this._details = details;
+ }
+ } else {
+ this._details = details;
+ }
+
+ this._level = parseInt(this._details.level) || 0;
+ this._team = IITC.utils.getTeamId(this._details.team);
+
+ // the data returns unclaimed portals as level 1 - but IITC wants them treated as level 0
+ if (this._team === window.TEAM_NONE) {
+ this._level = 0;
+ }
+
+ // compatibility
+ var dataOptions = {
+ guid: this._details.guid,
+ level: this._level,
+ team: this._team,
+ ent: this._details.ent, // LEGACY - TO BE REMOVED AT SOME POINT! use .guid, .timestamp and .data instead
+ timestamp: this._details.timestamp,
+ data: this._details,
+ };
+ L.setOptions(this, dataOptions);
+
+ if (this._selected) {
+ this.renderDetails();
+ }
+
+ this.setSelected();
+ },
+
+ renderDetails() {
+ if (!this._rendering) {
+ this._rendering = true;
+ window.renderPortalDetails(this._details.guid);
+ this._rendering = false;
+ }
+ },
+
+ getDetails: function () {
+ return this._details;
+ },
+
+ isPlaceholder: function () {
+ return this._details.level === undefined;
+ },
+
+ hasFullDetails: function () {
+ return !!this._details.mods;
+ },
+
+ setStyle: function (style) {
+ // stub for highlighters
+ L.Util.setOptions(this, style);
+ return this;
+ },
+
+ setMarkerStyle: function (style) {
+ var styleOptions = L.Util.extend(this._style(), style);
+ L.Util.setOptions(this, styleOptions);
+
+ L.Util.setOptions(this, window.highlightPortal(this));
+
+ var selected = L.extend({ radius: this.options.radius }, this._selected && { color: window.COLOR_SELECTED_PORTAL });
+ return L.CircleMarker.prototype.setStyle.call(this, selected);
+ },
+
+ setSelected: function (selected) {
+ if (selected === false) {
+ this._selected = false;
+ } else {
+ this._selected = this._selected || selected;
+ }
+
+ this.setMarkerStyle();
+
+ if (this._selected && window.map.hasLayer(this)) {
+ this.bringToFront();
+ }
+ },
+
+ _style: function () {
+ var dashArray = null;
+ // dashed outline for placeholder portals
+ if (this.isPlaceholder()) {
+ dashArray = L.PortalMarker.placeholderStyle.dashArray;
+ }
+
+ return L.extend(this._scale(), L.PortalMarker.portalBaseStyle, {
+ color: window.COLORS[this._team],
+ fillColor: window.COLORS[this._team],
+ dashArray: dashArray,
+ });
+ },
+
+ _scale: function () {
+ var scale = window.portalMarkerScale();
+
+ var level = Math.floor(this._level || 0);
+
+ var lvlWeight = L.PortalMarker.LEVEL_TO_WEIGHT[level] * Math.sqrt(scale);
+ var lvlRadius = L.PortalMarker.LEVEL_TO_RADIUS[level] * scale;
+
+ // thinner outline for placeholder portals
+ if (this.isPlaceholder()) {
+ lvlWeight = L.PortalMarker.placeholderStyle.weight;
+ }
+
+ return {
+ radius: lvlRadius,
+ weight: lvlWeight,
+ };
+ },
+});
+
/**
* Calculates the scale of portal markers based on the current zoom level of the map.
*
@@ -23,39 +295,21 @@ window.portalMarkerScale = function () {
* @function createMarker
* @param {L.LatLng} latlng - The latitude and longitude where the marker will be placed.
* @param {Object} data - The IITC-specific entity data to be stored in the marker options.
- * @returns {L.circleMarker} A Leaflet circle marker representing the portal.
+ * @returns {L.PortalMarker} A Leaflet circle marker representing the portal.
*/
window.createMarker = function (latlng, data) {
- var styleOptions = window.getMarkerStyleOptions(data);
-
- var options = L.extend({}, data, styleOptions, { interactive: true });
-
- var marker = L.circleMarker(latlng, options);
-
- window.highlightPortal(marker);
-
- return marker;
+ return new L.PortalMarker(latlng, data);
};
/**
* Sets the style of a portal marker, including options for when the portal is selected.
*
* @function setMarkerStyle
- * @param {L.circleMarker} marker - The portal marker whose style will be set.
+ * @param {L.PortalMarker} marker - The portal marker whose style will be set.
* @param {boolean} selected - Indicates if the portal is selected.
*/
window.setMarkerStyle = function (marker, selected) {
- var styleOptions = window.getMarkerStyleOptions(marker.options);
-
- marker.setStyle(styleOptions);
-
- // FIXME? it's inefficient to set the marker style (above), then do it again inside the highlighter
- // the highlighter API would need to be changed for this to be improved though. will it be too slow?
- window.highlightPortal(marker);
-
- if (selected) {
- marker.setStyle({ color: window.COLOR_SELECTED_PORTAL });
- }
+ marker.setSelected(selected);
};
/**
@@ -68,33 +322,30 @@ window.setMarkerStyle = function (marker, selected) {
window.getMarkerStyleOptions = function (details) {
var scale = window.portalMarkerScale();
- // portal level 0 1 2 3 4 5 6 7 8
- var LEVEL_TO_WEIGHT = [2, 2, 2, 2, 2, 3, 3, 4, 4];
- var LEVEL_TO_RADIUS = [7, 7, 7, 7, 8, 8, 9, 10, 11];
-
var level = Math.floor(details.level || 0);
- var lvlWeight = LEVEL_TO_WEIGHT[level] * Math.sqrt(scale);
- var lvlRadius = LEVEL_TO_RADIUS[level] * scale;
+ var lvlWeight = L.PortalMarker.LEVEL_TO_WEIGHT[level] * Math.sqrt(scale);
+ var lvlRadius = L.PortalMarker.LEVEL_TO_RADIUS[level] * scale;
var dashArray = null;
// thinner and dashed outline for placeholder portals
if (details.team !== window.TEAM_NONE && level === 0) {
- lvlWeight = 1;
- dashArray = '1,2';
+ lvlWeight = L.PortalMarker.placeholderStyle.weight;
+ dashArray = L.PortalMarker.placeholderStyle.dashArray;
}
- var options = {
- radius: lvlRadius,
- stroke: true,
- color: window.COLORS[details.team],
- weight: lvlWeight,
- opacity: 1,
- fill: true,
- fillColor: window.COLORS[details.team],
- fillOpacity: 0.5,
- dashArray: dashArray,
- };
+ var options = L.extend(
+ {
+ radius: lvlRadius,
+ weight: lvlWeight,
+ },
+ L.PortalMarker.portalBaseStyle,
+ {
+ color: window.COLORS[details.team],
+ fillColor: window.COLORS[details.team],
+ dashArray: dashArray,
+ }
+ );
return options;
};