Merge branch 'Main-App-Pota-Integration' into 'master'

POTA integration and a slew of bug fixes

See merge request gridtracker.org/gridtracker!204

If this is changing anything in the UI or operational behavior, please prepare to update the wiki!
merge-requests/206/head
T Loomis 2022-10-01 19:40:07 +00:00
commit 835f88d3eb
39 zmienionych plików z 1204 dodań i 561 usunięć

Wyświetl plik

@ -26,6 +26,7 @@ module.exports = {
semi: 0,
"space-before-function-paren": 0,
"one-var": 0,
"no-trailing-spaces": ["error", { skipBlankLines: true, ignoreComments: true }],
/* ESLint checks we should consider fixing in the future */
eqeqeq: 0,

4
debian/changelog vendored
Wyświetl plik

@ -1,3 +1,7 @@
gridtracker (1.22.0930) unstable; urgency=low
- Test build for POTA Enhancements
-- Matthew Chambers <nr0q@gridtracker.org> Mon, 26 Sep 2022 19:00:00 -0000
gridtracker (1.22.0903) unstable; urgency=low
- Fixed a bug that displayed 1.25m band QSOs incorrectly.
- Fixed broken DXCC CQ highlighting and Statistics CQ highlighting.

Wyświetl plik

@ -695,6 +695,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<div id="radarButton" onclick="toggleNexrad()" class="iconButton" title="Toggle US Nexrad">
<img src="./img/radar-button.png" class="buttonImg" />
</div>
<div id="potaButton" onClick="togglePota();" class="iconButton" title="Parks On The Air">
<img id="potaImg" src="./img/pota.png" class="buttonImg" />
</div>
<div id="gridOverlayButton" onclick="toggleAllGrids()" class="iconButton" title="Toggle All Grid Overlay">
<img src="./img/grid-overlay.png" class="buttonImg" />
</div>
@ -753,6 +756,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
></div>
</div>
<div id="myFlagtip" class="myFlagtipEnd"></div>
<div id="myParktip" style="
-webkit-user-select: text;
user-select: text;
background-color: black;
padding: 2px;
text-align: center;
display: block;
position: absolute;
z-index: -500;
left: -1000px;
top: -1000px;
border: 2px solid white;
white-space: nowrap;
overflow: hidden;
"
class="roundBorder"></div>
<div
id="myTooltip"
style="

Wyświetl plik

@ -4,7 +4,7 @@
"AMTORFEC": false,
"AM": true,
"ARDOP": false,
"ASCII": false,
"ASCI": false,
"ATV": false,
"BPSK31": false,
"BPSK63": false,

Wyświetl plik

@ -4,7 +4,7 @@
"AMTORFEC": true,
"AM": false,
"ARDOP": true,
"ASCII": true,
"ASCI": true,
"ATV": false,
"BPSK31": true,
"BPSK63": true,

Wyświetl plik

@ -172,7 +172,7 @@
<div>
<label><input type="checkbox" id="huntPX" onchange="wantedChanged(this);" /> WPX</label>
</div>
<div>
<div id="huntingMatrixPotaDiv">
<label title="Parks On The Air">
<input type="checkbox" id="huntPOTA" onchange="wantedChanged(this);" /> POTA
</label>
@ -201,9 +201,6 @@
<div>
<label><input type="checkbox" id="huntCont" onchange="wantedChanged(this);" /> Cont</label>
</div>
<div>
<label><input type="checkbox" id="huntRR73" onchange="wantedChanged(this);" /> RR73</label>
</div>
</div>
</div>
@ -320,7 +317,10 @@
<div id="allOnlyNewDiv">
<label><input type="checkbox" id="allOnlyNew" onchange="valuesChanged();" /> Only New Calls</label>
</div>
<div>
<label><input type="checkbox" id="wantRRCQ" onchange="valuesChanged();" /> RR73 as CQ</label>
</div>
<div>
<label title="No Decodes Containing...">
<input type="checkbox" id="noMsg" onchange="valuesChanged();" />
@ -376,15 +376,14 @@
</div>
</div>
</div>
<div id="instancesWrapper" class="secondaryControlGroup" >
<h3>Instances</h3>
<div id="instancesDiv"></div>
</div>
</div>
</header>
</header>
<div id="instancesWrapper">
<div id="instancesDiv"></div>
</div>
<main id="RosterTable"></main>
</div>

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 690 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 383 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 754 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 332 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 754 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 351 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 437 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 367 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 872 B

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 868 B

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 37 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 552 B

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 2.1 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 7.8 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 17 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 1.0 KiB

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 155 KiB

Wyświetl plik

@ -249,14 +249,18 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
{
isPhone = g_modes_phone[finalMode];
}
// TODO: Revisit when we support more than one park ID
let finalPOTA = findAdiField(activeAdifArray[x], "POTA").toUpperCase();
if (finalPOTA.length == 0)
{
finalPOTA = null;
}
if (finalDXcall != "")
{
addDeDx(
finalGrid,
finalDXcall,
false,
false,
false,
finalDEcall,
finalRSTsent,
finalTime,
@ -264,7 +268,7 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
finalMode,
finalBand,
confirmed,
false,
true,
finalRSTrecv,
finalDxcc,
finalState,
@ -277,7 +281,8 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
isDigital,
isPhone,
finalIOTA,
finalSatName
finalSatName,
finalPOTA
);
}
}
@ -332,9 +337,6 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
addDeDx(
finalMyGrid,
finalDEcall,
false,
false,
false,
finalDXcall,
null,
finalTime,
@ -342,7 +344,7 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
finalMode,
finalBand,
false,
true,
false,
finalRSTsent,
finalDxcc,
null,
@ -357,9 +359,6 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
addDeDx(
finalGrid,
finalDXcall,
false,
false,
false,
"-",
finalRSTsent,
finalTime,
@ -367,7 +366,7 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
finalMode,
finalBand,
false,
true,
false,
null,
finalDxcc,
null,
@ -382,9 +381,6 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
addDeDx(
finalGrid,
finalDXcall,
false,
false,
false,
finalDEcall,
finalRSTsent,
finalTime,
@ -392,7 +388,7 @@ function onAdiLoadComplete(adiBuffer, saveAdifFile, adifFileName, newFile)
finalMode,
finalBand,
false,
true,
false,
null,
finalDxcc,
null,
@ -1822,14 +1818,22 @@ function sendToLogger(ADIF)
localMode = record.SUBMODE;
}
let localHash = record.CALL + record.BAND + localMode;
if (
(!("GRIDSQUARE" in record) || record.GRIDSQUARE.length == 0) &&
record.CALL + record.BAND + localMode in g_liveCallsigns
localHash in g_liveCallsigns
)
{
record.GRIDSQUARE = g_liveCallsigns[
record.CALL + record.BAND + localMode
].grid.substr(0, 4);
record.GRIDSQUARE = g_liveCallsigns[localHash].grid.substr(0, 4);
}
if (g_potaEnabled == 1 && localHash in g_liveCallsigns && g_liveCallsigns[localHash].pota.length > 0)
{
let pota = g_liveCallsigns[localHash].pota[0];
if (pota != "?-????")
{
record.POTA = pota;
}
}
if ("TX_PWR" in record)
@ -1922,17 +1926,33 @@ function sendToLogger(ADIF)
function finishSendingReport(record, localMode)
{
let report = "";
for (let key in record)
for (const key in record)
{
report += "<" + key + ":" + Buffer.byteLength(record[key]) + ">" + record[key] + " ";
if (key != "POTA")
{
report += "<" + key + ":" + Buffer.byteLength(record[key]) + ">" + record[key] + " ";
}
}
report += "<EOR>";
// this report is for internal use ONLY!
let reportWithPota = "";
for (const key in record)
{
reportWithPota += "<" + key + ":" + Buffer.byteLength(record[key]) + ">" + record[key] + " ";
}
reportWithPota += "<EOR>";
// Full record dupe check
if (report != g_lastReport)
{
g_lastReport = report;
if (g_potaEnabled == 1 && "POTA" in record)
{
reportPotaQSO(record);
addLastTraffic("<font style='color:white'>Spotted to POTA</font>");
}
if (
g_N1MMSettings.enable == true &&
@ -1966,7 +1986,7 @@ function finishSendingReport(record, localMode)
try
{
onAdiLoadComplete("GT<EOH>" + report);
onAdiLoadComplete("GT<EOH>" + reportWithPota);
}
catch (e)
{
@ -1978,7 +1998,7 @@ function finishSendingReport(record, localMode)
if (logGTqsoCheckBox.checked == true)
{
var fs = require("fs");
fs.appendFileSync(g_qsoLogFile, report + "\r\n");
fs.appendFileSync(g_qsoLogFile, reportWithPota + "\r\n");
addLastTraffic(
"<font style='color:white'>Logged to GridTracker backup</font>"
);
@ -2795,7 +2815,7 @@ function getPostJSONBuffer(
});
req.on("error", function (err) // eslint-disable-line node/handle-callback-err
{
if (typeof timeoutCallback != "undefined")
if (typeof timeoutCallback === "function")
{
timeoutCallback(
file_url,

Wyświetl plik

@ -744,15 +744,13 @@ function lookupUsCallsign(object, writeState = false)
{
g_ulsDatabase.transaction(function (tx)
{
var qry = "SELECT * FROM calls where callsign = \"" + object.DEcall + "\"";
let qry = "SELECT * FROM calls where callsign = \"" + object.DEcall + "\"";
tx.executeSql(
qry,
[],
function (tx, results)
{
var len = results.rows.length,
i;
if (len == 1)
if (results.rows.length == 1)
{
if (object.state == null)
{
@ -764,15 +762,16 @@ function lookupUsCallsign(object, writeState = false)
{
object.state = "US-" + results.rows[0].state;
}
if (writeState) setState(object);
if (writeState)
{
setState(object);
}
}
object.zipcode = String(results.rows[0].zip);
if (object.cnty == null)
{
let request = g_Idb
.transaction(["lookups"], "readwrite")
.objectStore("lookups")
.get(object.DEcall);
let request = g_Idb.transaction(["lookups"], "readwrite").objectStore("lookups").get(object.DEcall);
request.onsuccess = function (event)
{
@ -785,21 +784,40 @@ function lookupUsCallsign(object, writeState = false)
if (object.cnty == null && object.zipcode in g_zipToCounty)
{
var counties = g_zipToCounty[object.zipcode];
if (counties.length > 1) object.qual = false;
else object.qual = true;
if (counties.length > 1)
{
object.qual = false;
}
else
{
object.qual = true;
}
object.cnty = counties[0];
}
else object.qual = false;
if (writeState) setState(object);
else
{
object.qual = false;
}
if (writeState)
{
setState(object);
}
};
request.onerror = function (event)
{
object.qual = false;
if (writeState) setState(object);
if (writeState)
{
setState(object);
}
};
}
if (writeState) setState(object);
if (writeState)
{
setState(object);
}
}
},
null

Wyświetl plik

@ -47,6 +47,7 @@ var def_appSettings = {
gtPropFilter: "mixed",
gtMsgEnable: true,
gtShareEnable: true,
gtSpotEnable: true,
heatEnabled: 0,
loadAdifAtStartup: false,
lookupLoginCq: "",
@ -74,6 +75,7 @@ var def_appSettings = {
myRawFreq: "",
myRawGrid: "",
pathWidthWeight: 1.0,
potaEnabled: 0,
pushPinMode: false,
qrzPathWidthWeight: 1.2,
sixWideMode: 0,
@ -90,8 +92,7 @@ var def_appSettings = {
workingCallsignEnable: false,
workingCallsigns: {},
workingDateEnable: false,
workingDate: 0,
gtSpotEnable: true
workingDate: 0
};
var def_mapSettings = {
@ -210,32 +211,38 @@ var def_N1MMSettings = {
port: 2333,
ip: "127.0.0.1"
};
var def_log4OMSettings = {
enable: false,
port: 2236,
ip: "127.0.0.1"
};
var def_dxkLogSettings = {
enable: false,
port: 52000,
ip: "127.0.0.1"
};
var def_HRDLogbookLogSettings = {
enable: false,
port: 7826,
ip: "127.0.0.1"
};
var def_acLogSettings = {
enable: false,
port: 1100,
ip: "127.0.0.1"
};
var def_trustedQslSettings = {
stationFile: "",
stationFileValid: false,
binaryFile: "",
binaryFileValid: false
};
var def_callsignLookups = {
lotwUseEnable: true,
lotwWeeklyEnable: true,

Plik diff jest za duży Load Diff

Wyświetl plik

@ -337,7 +337,9 @@ function gtChatUpdateCall(jsmesg)
{
makeGtPin(g_gtFlagPins[cid]);
if (g_gtFlagPins[cid].pin != null)
{ g_layerSources.gtflags.addFeature(g_gtFlagPins[cid].pin); }
{
g_layerSources.gtflags.addFeature(g_gtFlagPins[cid].pin);
}
}
g_gtChatlistChangeCount++;
g_gtCallsigns[g_gtFlagPins[cid].call] = cid;
@ -382,7 +384,9 @@ function makeGtPin(obj)
delete obj.pin;
obj.pin = null;
}
if (obj.src != "GT") return;
if (typeof obj.grid == "undefined" || obj.grid == null) return;
if (obj.grid.length != 4 && obj.grid.length != 6) return;
@ -453,9 +457,13 @@ function gtChatNewList(jsmesg)
g_gtFlagPins[cid].dxcc = callsignToDxcc(g_gtFlagPins[cid].call);
g_gtFlagPins[cid].live = true;
g_gtCallsigns[g_gtFlagPins[cid].call] = cid;
makeGtPin(g_gtFlagPins[cid]);
if (g_gtFlagPins[cid].pin != null)
{ g_layerSources.gtflags.addFeature(g_gtFlagPins[cid].pin); }
{
g_layerSources.gtflags.addFeature(g_gtFlagPins[cid].pin);
}
}
}
g_gtChatlistChangeCount++;

Wyświetl plik

@ -2,66 +2,625 @@
// All rights reserved.
// See LICENSE for more information.
var g_potaPlaces = null;
var g_potaSpots = null;
var g_pota = {
parks: {},
locations: {},
parksTimeout: null,
callSchedule: {},
parkSchedule: {},
scheduleTimeout: null,
callSpots: {},
parkSpots: {},
spotsTimeout: null,
mapParks: {},
rbnReportTimes: {},
rbnFrequency: 600000
};
function ingestPotaPlaces(buffer)
var g_potaSpotTemplate = {
activator: "",
frequency: 0,
mode: "",
band: "",
reference: "",
spotTime: 0,
spotter: "",
comments: "",
source: "GT",
count: 1,
activatorGrid: "",
spotterGrid: ""
};
var g_parkTemplate = {
feature: null
}
var g_potaUnknownPark = {
name: "Unknown park (not yet spotted)",
active: "0",
entityId: "-1",
locationDesc: "??-??",
latitude: "0.0",
longitude: "0.0",
grid: ""
};
var g_gtParkIconActive = new ol.style.Icon({
src: "./img/pota_icon_active.png",
anchorYUnits: "pixels",
anchorXUnits: "pixels",
anchor: [10, 19]
});
var g_gtParkIconInactive = new ol.style.Icon({
src: "./img/pota_icon_inactive.png",
anchorYUnits: "pixels",
anchorXUnits: "pixels",
anchor: [10, 19]
});
function initPota()
{
try
potaImg.style.filter = g_potaEnabled == 1 ? "" : "grayscale(1)";
getPotaParks();
}
function togglePota()
{
g_potaEnabled ^= 1;
g_appSettings.potaEnabled = g_potaEnabled;
potaImg.style.filter = g_potaEnabled == 1 ? "" : "grayscale(1)";
saveAppSettings();
if (g_potaEnabled == 1)
{
g_potaPlaces = JSON.parse(buffer);
getPotaParks();
}
catch (e)
else
{
// can't write, somethings broke
g_layerSources.pota.clear();
g_pota.mapParks = {};
}
goProcessRoster();
}
function redrawParks()
{
g_layerSources.pota.clear();
if (g_potaEnabled == 1)
{
g_pota.mapParks = {};
makeParkFeatures();
}
}
function getPotaPlaces()
function makeParkFeatures()
{
if (g_mapSettings.offlineMode == false)
try
{
for (const park in g_pota.parkSpots)
{
if (park in g_pota.parks)
{
let parkObj = Object.assign({}, g_parkTemplate);
for (const call in g_pota.parkSpots[park])
{
let report = g_pota.parkSpots[park][call];
if (parkObj.feature == null && validateMapBandAndMode(report.band, report.mode))
{
parkObj.feature = iconFeature(ol.proj.fromLonLat([Number(g_pota.parks[park].longitude), Number(g_pota.parks[park].latitude)]), g_gtParkIconActive, 1);
parkObj.feature.key = park;
parkObj.feature.size = 22;
g_pota.mapParks[park] = parkObj;
g_layerSources.pota.addFeature(parkObj.feature);
}
}
}
}
}
catch (e)
{
console.log("exception: makeParkFeature " + park);
console.log(e.message);
}
}
function potaSpotFromDecode(callObj)
{
for (const i in callObj.pota)
{
let park = callObj.pota[i];
let spotObj = null;
if (!(callObj.DEcall in g_pota.callSpots))
{
// new call and park
g_pota.callSpots[callObj.DEcall] = [park];
}
else if (!g_pota.callSpots[callObj.DEcall].includes(park))
{
// new park
g_pota.callSpots[callObj.DEcall].push(park);
}
if (!(park in g_pota.parkSpots))
{
g_pota.parkSpots[park] = {};
g_pota.parkSpots[park][callObj.DEcall] = spotFromCallObj(callObj, park, 0, 0);
}
else if (!(callObj.DEcall in g_pota.parkSpots[park]))
{
g_pota.parkSpots[park][callObj.DEcall] = spotFromCallObj(callObj, park, 0, 0);
}
else
{
// update spot
g_pota.parkSpots[park][callObj.DEcall] = spotFromCallObj(callObj, park, g_pota.parkSpots[park][callObj.DEcall].count);
}
// may or may not be on screen, so try
addParkSpotFeature(park, g_pota.parkSpots[park][callObj.DEcall]);
let hash = park + callObj.DEcall;
if (!(hash in g_pota.rbnReportTimes) || Date.now() > g_pota.rbnReportTimes[hash])
{
g_pota.rbnReportTimes[hash] = Date.now() + g_pota.rbnFrequency;
reportPotaRBN(g_pota.parkSpots[park][callObj.DEcall]);
}
}
}
function reportPotaRBN(callSpot)
{
let report = {
activator: callSpot.activator,
spotter: myDEcall + "-#",
frequency: String(parseInt(callSpot.frequency * 1000)),
reference: callSpot.reference,
mode: callSpot.mode,
source: "RBN",
comments: callSpot.comments,
activatorGrid: callSpot.activatorGrid,
spotterGrid: callSpot.spotterGrid
}
getPostJSONBuffer(
"https://api.pota.app/spot",
rbnReportResult,
null,
"https",
443,
report,
10000,
null,
null
);
}
function reportPotaQSO(record)
{
let report = {
activator: record.CALL,
spotter: record.STATION_CALLSIGN,
frequency: record.FREQ,
reference: record.POTA,
mode: record.MODE,
source: "GT",
comments: record.COMMENT ? record.COMMENT : "",
activatorGrid: record.GRIDSQUARE ? record.GRIDSQUARE : "",
spotterGrid: record.MY_GRIDSQUARE ? record.MY_GRIDSQUARE : ""
}
if ("SUBMODE" in record)
{
report.mode = record.SUBMODE;
}
getPostJSONBuffer(
"https://api.pota.app/spot",
rbnReportResult,
null,
"https",
443,
report,
10000,
null,
null
);
}
function rbnReportResult(buffer, flag, cookies)
{
// It worked! process latest spots!
if (g_pota.spotsTimeout)
{
clearTimeout(g_pota.spotsTimeout);
g_pota.spotsTimeout = null;
}
processPotaSpots(String(buffer));
g_pota.spotsTimeout = setTimeout(getPotaSpots, 300000);
}
function spotFromCallObj(callObj, park, inCount, rbnTime)
{
let callSpot = {
activator: callObj.DEcall,
activatorGrid: callObj.grid,
spotter: myDEcall + "-#",
spotterGrid: myDEGrid,
frequency: Number((g_instances[callObj.instance].status.Frequency / 1000000).toFixed(3)),
reference: park,
mode: callObj.mode,
band: callObj.band,
spotTime: Date.now(),
source: "GT",
count: inCount + 1,
comments: "GT " + callObj.RSTsent + " dB " + myDEGrid + " via " + myDEcall + "-#"
};
return callSpot;
}
function addParkSpotFeature(park, report)
{
let parkObj = Object.assign({}, g_parkTemplate);
if (park in g_pota.mapParks)
{
parkObj = g_pota.mapParks[park];
}
else
{
g_pota.mapParks[park] = parkObj;
}
if (parkObj.feature == null && validateMapBandAndMode(report.band, report.mode))
{
parkObj.feature = iconFeature(ol.proj.fromLonLat([Number(g_pota.parks[park].longitude), Number(g_pota.parks[park].latitude)]), g_gtParkIconActive, 1);
parkObj.feature.key = park;
parkObj.feature.size = 22;
g_layerSources.pota.addFeature(parkObj.feature);
}
}
function processPotaParks(buffer)
{
if (g_potaEnabled == 1)
{
try
{
let data = JSON.parse(buffer);
let newParks = data.parks;
for (const park in newParks)
{
let locations = newParks[park].locationDesc.split(",");
for (const i in locations)
{
if (locations[i] in data.locations)
{
locations[i] = data.locations[locations[i]];
}
}
newParks[park].locationDesc = locations.join(", ");
}
newParks["?-????"] = g_potaUnknownPark;
g_pota.parks = newParks;
g_pota.locations = data.locations;
getPotaSchedule();
getPotaSpots();
}
catch (e)
{
// can't write, somethings broke
console.log("Failed to load parks!");
console.log(e.message);
}
}
}
function getPotaParks()
{
if (g_pota.parksTimeout)
{
clearTimeout(g_pota.parksTimeout);
g_pota.spotsTimeout = null;
}
if (g_mapSettings.offlineMode == false && g_potaEnabled == 1)
{
getBuffer(
"https://storage.googleapis.com/gt_app/pota.json",
ingestPotaPlaces,
"https://storage.googleapis.com/gt_app/pota.json?cb=" + Date.now(),
processPotaParks,
null,
"https",
443
);
setTimeout(getPotaPlaces, 86400000)
}
g_pota.parksTimeout = setTimeout(getPotaParks, 86400000)
}
function ingestPotaSpots(buffer)
// This is a shallow copy, don't use with objects that contain other objects or arrays
function fillObjectFromTemplate(template, input)
{
try
let object = {};
for (const key in template)
{
g_potaSpots = JSON.parse(buffer);
if (key in input)
{
object[key] = input[key];
}
else
{
// missing, use the template value
object[key] = template[key];
}
}
catch (e)
return object;
}
function uniqueArrayFromArray(input)
{
let unique = [];
input.forEach((c) =>
{
// can't write, somethings broke
if (!unique.includes(c))
{
unique.push(c);
}
});
return unique;
}
function processPotaSpots(buffer)
{
if (g_potaEnabled == 1)
{
try
{
let spots = JSON.parse(buffer);
g_pota.callSpots = {};
g_pota.parkSpots = {};
for (const spot in spots)
{
if (spots[spot].reference in g_pota.parks)
{
let newSpot = fillObjectFromTemplate(g_potaSpotTemplate, spots[spot]);
newSpot.spotTime = Date.parse(newSpot.spotTime + "Z");
newSpot.frequency = parseInt(newSpot.frequency) / 1000;
newSpot.band = newSpot.frequency.formatBand();
(g_pota.callSpots[newSpot.activator] = g_pota.callSpots[newSpot.activator] || []).push(newSpot.reference);
if (!(newSpot.reference in g_pota.parkSpots))
{
g_pota.parkSpots[newSpot.reference] = {};
}
if (newSpot.activator in g_pota.parkSpots[newSpot.reference])
{
g_pota.parkSpots[newSpot.reference][newSpot.activator] = fillObjectFromTemplate(g_pota.parkSpots[newSpot.reference][newSpot.activator], newSpot);
}
else
{
g_pota.parkSpots[newSpot.reference][newSpot.activator] = newSpot;
}
}
else
{
console.log("PotaSpots: unknown park id: " + spots[spot].reference);
}
}
// Sanity dedupe checks
for (const spot in g_pota.callSpots)
{
g_pota.callSpots[spot] = uniqueArrayFromArray(g_pota.callSpots[spot]);
}
redrawParks();
}
catch (e)
{
// can't write, somethings broke
}
}
}
function getPotaSpots()
{
if (g_mapSettings.offlineMode == false && g_spotsEnabled == 1)
if (g_pota.spotsTimeout)
{
clearTimeout(g_pota.spotsTimeout);
g_pota.spotsTimeout = null;
}
if (g_mapSettings.offlineMode == false && g_potaEnabled == 1)
{
getBuffer(
"https://api.pota.app/spot/activator",
ingestPotaSpots,
processPotaSpots,
null,
"https",
443
);
}
setTimeout(getPotaSpots, 300000);
g_pota.spotsTimeout = setTimeout(getPotaSpots, 300000);
}
function processPotaSchedule(buffer)
{
if (g_potaEnabled == 1)
{
try
{
let schedules = JSON.parse(buffer);
g_pota.callSchedule = {};
g_pota.parkSchedule = {};
for (const i in schedules)
{
let newObj = {};
newObj.id = schedules[i].reference;
newObj.start = Date.parse(schedules[i].startDate + "T" + schedules[i].startTime + "Z");
newObj.end = Date.parse(schedules[i].endDate + "T" + schedules[i].endTime + "Z");
newObj.frequencies = schedules[i].frequencies;
newObj.comments = schedules[i].comments;
if (Date.now() < newObj.end)
{
if (newObj.id in g_pota.parks)
{
(g_pota.callSchedule[schedules[i].activator] = g_pota.callSchedule[schedules[i].activator] || []).push(newObj);
newObj = Object.assign({}, newObj);
newObj.id = schedules[i].activator;
(g_pota.parkSchedule[schedules[i].reference] = g_pota.parkSchedule[schedules[i].reference] || []).push(newObj);
}
else
{
console.log("PotaSchedule: unknown park id: " + newObj.id);
}
}
// else it is expired and no longer relevant
}
// Sanity dedupe checks
for (const key in g_pota.callSchedule)
{
g_pota.callSchedule[key] = uniqueArrayFromArray(g_pota.callSchedule[key]);
}
for (const key in g_pota.parkSchedule)
{
g_pota.parkSchedule[key] = uniqueArrayFromArray(g_pota.parkSchedule[key]);
}
}
catch (e)
{
// can't write, somethings broke
}
}
}
function g_sendPotaSpot()
function getPotaSchedule()
{
// if Pota spotting enabled, and we have enough info, send a spot to Pota
if (g_pota.scheduleTimeout)
{
clearTimeout(g_pota.scheduleTimeout);
g_pota.scheduleTimeout = null;
}
if (g_mapSettings.offlineMode == false && g_potaEnabled == 1)
{
getBuffer(
"https://api.pota.app/activation",
processPotaSchedule,
null,
"https",
443
);
}
g_pota.scheduleTimeout = setTimeout(getPotaSchedule, 900000);
}
var g_lastPark = null;
function mouseOverPark(feature)
{
if (g_lastPark && g_lastPark == feature)
{
mouseParkMove();
return;
}
g_lastPark = feature;
createParkTipTable(feature);
mouseParkMove();
myParktip.style.zIndex = 499;
myParktip.style.display = "block";
}
function mouseOutPark(mouseEvent)
{
g_lastPark = null;
myParktip.style.zIndex = -1;
}
function mouseParkMove()
{
var positionInfo = myParktip.getBoundingClientRect();
var windowWidth = window.innerWidth;
myParktip.style.left = getMouseX() - (positionInfo.width / 2) + "px";
if (windowWidth - getMouseX() < (positionInfo.width / 2))
{
myParktip.style.left = getMouseX() - (10 + positionInfo.width) + "px";
}
if (getMouseX() - (positionInfo.width / 2) < 0)
{
myParktip.style.left = getMouseX() + 10 + "px";
}
myParktip.style.top = getMouseY() - positionInfo.height - 12 + "px";
}
function createParkTipTable(toolElement)
{
let worker = "";
let key = toolElement.key;
let now = Date.now();
worker += "<div style='background-color:#000;color:lightgreen;font-weight:bold;font-size:12px;border:1px solid gray;margin:0px' class='roundBorder'>" +
key +
" : <font color='cyan'>" + g_pota.parks[key].name + "" +
" (<font color='yellow'>" + g_dxccToAltName[Number(g_pota.parks[key].entityId)] + "</font>)" +
"</font></br><font color='lightblue'>" + g_pota.parks[key].locationDesc + "</font></div>";
worker += "<table id='potaSpotsTable' class='darkTable' style='margin: 0 auto;'>";
worker += "<tr><th>Activator</th><th>Spotter</th><th>Freq</th><th>Mode</th><th>Count</th><th>When</th><th>Source</th><th>Comment</th></tr>";
for (const i in g_pota.parkSpots[key])
{
if (validateMapBandAndMode(g_pota.parkSpots[key][i].band, g_pota.parkSpots[key][i].mode))
{
worker += "<tr>";
worker += "<td style='color:yellow'>" + g_pota.parkSpots[key][i].activator + "</td>";
worker += "<td style='color:cyan'>" + ((g_pota.parkSpots[key][i].spotter == g_pota.parkSpots[key][i].activator) ? "Self" : g_pota.parkSpots[key][i].spotter) + "</td>";
worker += "<td style='color:lightgreen' >" + g_pota.parkSpots[key][i].frequency.formatMhz(3, 3) + " <font color='yellow'>(" + g_pota.parkSpots[key][i].band + ")</font></td>";
worker += "<td style='color:orange'>" + g_pota.parkSpots[key][i].mode + "</td>";
worker += "<td>" + g_pota.parkSpots[key][i].count + "</td>";
worker += "<td style='color:lightblue' >" + parseInt((now - g_pota.parkSpots[key][i].spotTime) / 1000).toDHMS() + "</td>";
worker += "<td>" + g_pota.parkSpots[key][i].source + "</td>";
worker += "<td>" + g_pota.parkSpots[key][i].comments + "</td>";
worker += "</tr>";
}
}
worker += "</table>";
/*
buffer += "<div style='background-color:#000;color:#fff;font-size:12px;border:1px solid gray;margin:1px' class='roundBorder'>Activations (scheduled)"
buffer += "<table id='potaScheduleTable' class='darkTable' style='margin: 0 auto;'>";
buffer += "<tr><th>Activator</th><th>Start</th><th>End</th><th>Frequencies</th><th>Comment</th></tr>";
for (const i in g_pota.parkSchedule[key])
{
let start = g_pota.parkSchedule[key][i].start;
let end = g_pota.parkSchedule[key][i].end;
if (now < end)
{
buffer += "<tr>";
buffer += "<td style='color:yellow'>" + g_pota.parkSchedule[key][i].id + "</td>";
buffer += "<td style='color:lightblue'>" + ((now >= start) ? "<font color='white'>Now</font>" : (userTimeString(start) + "</br><font color='lightgreen'>T- " + Number(start - now).msToDHMS() + "</font>")) + "</td>";
buffer += "<td style='color:lightblue'>" + (userTimeString(end) + "</br><font color='orange'>T- " + Number(end - now).msToDHMS() + "</font>") + "</td>";
buffer += "<td style='color:lightgreen'>" + g_pota.parkSchedule[key][i].frequencies + "</td>";
buffer += "<td>" + g_pota.parkSchedule[key][i].comments.substr(0, 40) + "</td>";
buffer += "</tr>";
active++;
}
}
*/
myParktip.innerHTML = worker;
return 1;
}

Wyświetl plik

@ -129,6 +129,25 @@ Number.prototype.toDHMS = function ()
return val;
};
Number.prototype.msToDHMS = function ()
{
var seconds = parseInt(this / 1000);
var days = Math.floor(seconds / (3600 * 24));
seconds -= days * 3600 * 24;
var hrs = Math.floor(seconds / 3600);
seconds -= hrs * 3600;
var mnts = Math.floor(seconds / 60);
seconds -= mnts * 60;
days = days ? days + "d " : "";
hrs = hrs ? hrs + "h " : "";
mnts = mnts ? mnts + "m " : "";
var first = days + hrs + mnts;
if (first == "") val = seconds + "s";
else val = first + (seconds > 0 ? seconds + "s" : "");
return val;
};
Number.prototype.toDHMS15 = function ()
{
// round to earliest 15 seconds

Wyświetl plik

@ -21,6 +21,7 @@ var r_currentDXCCs = -1;
var r_callsignManifest = null;
var g_rosterSettings = {};
var g_day = 0;
var g_dayAsString = "0";
var g_menu = null;
var g_callMenu = null;
var g_ageMenu = null;
@ -72,6 +73,7 @@ var g_defaultSettings = {
wantMinDB: false,
wantMinFreq: false,
wantMaxFreq: false,
wantRRCQ: false,
maxDT: 0.5,
minDb: -25,
minFreq: 0,
@ -91,7 +93,7 @@ var g_defaultSettings = {
allOnlyNew: false,
useRegex: false,
callsignRegex: "",
realtime: false,
realtime: true,
wanted: {
huntCallsign: false,
huntGrid: true,
@ -105,13 +107,14 @@ var g_defaultSettings = {
huntPX: false,
huntPOTA: false,
huntQRZ: true,
huntOAMS: false,
huntRR73: false
huntOAMS: false
},
columns: {
Callsign: true,
Band: false,
Mode: false,
Calling: true,
Grid: true,
Msg: false,
DXCC: true,
Flag: true,
@ -137,7 +140,7 @@ var g_defaultSettings = {
},
reference: 0,
controls: true,
controlsExtended: false,
controlsExtended: true,
compact: false,
settingProfiles: false,
@ -333,12 +336,13 @@ function viewRoster()
function realtimeRoster()
{
let now = timeNowSec();
g_day = now / 86400;
g_day = parseInt(now / 86400);
g_dayAsString = String(g_day);
if (g_rosterSettings.realtime == false) return;
let timeCols = document.getElementsByClassName("timeCol");
for (let x in timeCols)
for (const x in timeCols)
{
if ((typeof timeCols[x].id != "undefined") && (typeof callRoster[timeCols[x].id.substr(2)] != "undefined"))
{
@ -347,7 +351,7 @@ function realtimeRoster()
}
}
let lifeCols = document.getElementsByClassName("lifeCol");
for (let x in lifeCols)
for (const x in lifeCols)
{
if ((typeof lifeCols[x].id != "undefined") && (typeof callRoster[lifeCols[x].id.substr(2)] != "undefined"))
{
@ -358,7 +362,7 @@ function realtimeRoster()
if (g_rosterSettings.columns.Spot)
{
let spotCols = document.getElementsByClassName("spotCol");
for (let x in spotCols)
for (const x in spotCols)
{
if ((typeof spotCols[x].id != "undefined") && (typeof callRoster[spotCols[x].id.substr(2)] != "undefined"))
{
@ -448,7 +452,7 @@ function updateInstances()
let worker = "";
let keys = Object.keys(instances).sort();
for (let key in keys)
for (const key in keys)
{
let inst = keys[key];
let sp = inst.split(" - ");
@ -544,7 +548,7 @@ function createSelectOptions(
{
obj = Object.keys(forObject).sort();
}
for (let k in obj)
for (const k in obj)
{
let opt = obj[k];
let option = document.createElement("option");
@ -622,7 +626,7 @@ function updateAwardList(target = null)
let keys = Object.keys(g_awardTracker).sort();
for (let key in keys)
for (const key in keys)
{
let award = g_awardTracker[keys[key]];
let rule = g_awards[award.sponsor].awards[award.name].rule;
@ -842,15 +846,18 @@ function setVisual()
if (g_rosterSettings.controlsExtended)
{
RosterControls.className = "extended";
instancesWrapper.style.display = "";
}
else
{
RosterControls.className = "normal";
instancesWrapper.style.display = "none";
}
}
else
{
RosterControls.className = "hidden";
instancesWrapper.style.display = "none";
}
// Award Hunter
@ -1010,7 +1017,7 @@ function wantedChanged(element)
{
g_rosterSettings.columns[t] = true;
for (let i = 0; i < g_menu.items.length; ++i)
for (const i in g_menu.items)
{
if (
typeof g_menu.items[i].checked != "undefined" &&
@ -1024,7 +1031,7 @@ function wantedChanged(element)
writeRosterSettings();
g_scriptReport = Object();
for (let callHash in window.opener.g_callRoster)
for (const callHash in window.opener.g_callRoster)
{
window.opener.g_callRoster[callHash].callObj.alerted = false;
}
@ -1044,6 +1051,7 @@ function valuesChanged()
g_rosterSettings.wantMinDB = wantMinDB.checked;
g_rosterSettings.wantMinFreq = wantMinFreq.checked;
g_rosterSettings.wantMaxFreq = wantMaxFreq.checked;
g_rosterSettings.wantRRCQ = wantRRCQ.checked;
maxDTView.innerHTML = g_rosterSettings.maxDT = maxDT.value;
minDbView.innerHTML = g_rosterSettings.minDb = minDb.value;
@ -1480,6 +1488,7 @@ function openIgnoreEdit()
worker += "</table></div>";
editTables.innerHTML = worker;
editView.style.height = (window.innerHeight - 50) + "px";
}
function onMyKeyDown(event)
@ -1718,23 +1727,25 @@ function init()
for (let columnIndex in g_rosterSettings.columnOrder)
{
let key = g_rosterSettings.columnOrder[columnIndex];
if (key != "Callsign")
{
let itemx = new nw.MenuItem({
type: "checkbox",
label: key,
checked: g_rosterSettings.columns[key],
click: function ()
{
g_rosterSettings.columns[this.label] = this.checked;
if (this.label == "Spot")
{ window.opener.setRosterSpot(g_rosterSettings.columns.Spot); }
writeRosterSettings();
window.opener.goProcessRoster();
resize();
}
});
let itemx = new nw.MenuItem({
type: "checkbox",
label: key,
checked: g_rosterSettings.columns[key],
click: function ()
{
g_rosterSettings.columns[this.label] = this.checked;
if (this.label == "Spot")
{ window.opener.setRosterSpot(g_rosterSettings.columns.Spot); }
writeRosterSettings();
window.opener.goProcessRoster();
resize();
}
});
g_menu.append(itemx);
g_menu.append(itemx);
}
}
item = new nw.MenuItem({ type: "separator" });
@ -2027,6 +2038,7 @@ function init()
wantMinDB.checked = g_rosterSettings.wantMinDB;
wantMinFreq.checked = g_rosterSettings.wantMinFreq;
wantMaxFreq.checked = g_rosterSettings.wantMaxFreq;
wantRRCQ.checked = g_rosterSettings.wantRRCQ;
maxDTView.innerHTML = maxDT.value = g_rosterSettings.maxDT;
minDbView.innerHTML = minDb.value = g_rosterSettings.minDb;
@ -2173,8 +2185,11 @@ function handleContextMenu(ev)
}
}
let name
if (ev.target.tagName == "TD") name = ev.target.getAttribute("name");
let name = "";
if (ev.target.tagName == "TD")
{
name = ev.target.getAttribute("name");
}
if (name == "Callsign")
{
@ -2204,7 +2219,7 @@ function handleContextMenu(ev)
g_targetITUz = ev.target.parentNode.id;
g_ITUzMenu.popup(mouseX, mouseY);
}
else if (name && name.startsWith("DXCC"))
else if (name.startsWith("DXCC"))
{
let dxcca = name.split("(");
let dxcc = parseInt(dxcca[1]);
@ -2332,29 +2347,9 @@ function processAward(awardName)
if (Index > -1) mode.splice(Index, 1);
test.mode = mode.length > 0;
test.confirmed =
"qsl_req" in
g_awards[g_awardTracker[awardName].sponsor].awards[
g_awardTracker[awardName].name
].rule
? g_awards[g_awardTracker[awardName].sponsor].awards[
g_awardTracker[awardName].name
].rule.qsl_req == "confirmed"
: g_awards[g_awardTracker[awardName].sponsor].qsl_req == "confirmed";
test.confirmed = "qsl_req" in g_awards[g_awardTracker[awardName].sponsor].awards[g_awardTracker[awardName].name].rule ? g_awards[g_awardTracker[awardName].sponsor].awards[g_awardTracker[awardName].name].rule.qsl_req == "confirmed" : g_awards[g_awardTracker[awardName].sponsor].qsl_req == "confirmed";
test.look = "confirmed";
test.qsl_req =
"qsl_req" in
g_awards[g_awardTracker[awardName].sponsor].awards[
g_awardTracker[awardName].name
].rule
? g_awards[g_awardTracker[awardName].sponsor].awards[
g_awardTracker[awardName].name
].rule.qsl_req
: g_awards[g_awardTracker[awardName].sponsor].qsl_req;
test.qsl_req = "qsl_req" in g_awards[g_awardTracker[awardName].sponsor].awards[g_awardTracker[awardName].name].rule ? g_awards[g_awardTracker[awardName].sponsor].awards[g_awardTracker[awardName].name].rule.qsl_req : g_awards[g_awardTracker[awardName].sponsor].qsl_req;
test.DEcall = "call" in award.rule;
test.band = "band" in award.rule && award.rule.band.indexOf("Mixed") == -1;
test.dxcc = "dxcc" in award.rule;
@ -2741,7 +2736,7 @@ function testAcont52band(award, obj, baseHash)
function scoreAgrids(award, obj)
{
if (obj.grid)
if (obj.grid && obj.grid.length > 0)
{
let grid = obj.grid.substr(0, 4);
@ -2763,6 +2758,10 @@ function testAgrids(award, obj, baseHash)
{
return false;
}
if (!obj.grid || obj.grid.length == 0)
{
return false;
}
return true;
}

Wyświetl plik

@ -74,6 +74,7 @@ function prepareRosterSettings()
}
else
{
console.log("Invalid/Unknown huntNeed");
rosterSettings.huntIndex = false;
rosterSettings.workedIndex = false;
rosterSettings.layeredMode = false;

Wyświetl plik

@ -45,18 +45,6 @@ function processRosterFiltering(callRoster, rosterSettings)
entry.tx = false;
continue;
}
if (entry.DXcall == "CQ POTA" && huntPOTA.checked == true)
{
entry.tx = true;
if (callObj.pota == null)
{
callObj.pota = {
reference: "?-????",
name: "Unknown Park"
}
}
continue;
}
if (callObj.ituza in g_blockedITUz)
{
entry.tx = false;
@ -72,10 +60,21 @@ function processRosterFiltering(callRoster, rosterSettings)
entry.tx = false;
continue;
}
if (g_rosterSettings.cqOnly == true && callObj.CQ == false)
if (g_rosterSettings.cqOnly == true)
{
entry.tx = false;
continue;
if (g_rosterSettings.wantRRCQ)
{
if (callObj.RR73 == false && callObj.CQ == false)
{
entry.tx = false;
continue;
}
}
else if (callObj.CQ == false)
{
entry.tx = false;
continue;
}
}
if (g_rosterSettings.useRegex && g_rosterSettings.callsignRegex.length > 0)
{
@ -148,13 +147,10 @@ function processRosterFiltering(callRoster, rosterSettings)
continue;
}
}
else
else if (g_rosterSettings.onlyMyDxcc == true)
{
if (g_rosterSettings.onlyMyDxcc == true)
{
entry.tx = false;
continue;
}
entry.tx = false;
continue;
}
if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.usesLoTW == true)
@ -380,12 +376,8 @@ function processRosterFiltering(callRoster, rosterSettings)
let x = g_awardTracker[award];
// TODO: Move award reason out of exclusions code?
callObj.awardReason =
g_awards[x.sponsor].awards[x.name].tooltip +
" (" +
g_awards[x.sponsor].sponsor +
")";
callObj.awardReason = g_awards[x.sponsor].awards[x.name].tooltip + " (" + g_awards[x.sponsor].sponsor + ")";
callObj.reason.push(x.name + " - " + x.sponsor);
break;
}
}

Wyświetl plik

@ -15,12 +15,12 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
const currentYear = new Date().getFullYear();
const currentYearSuffix = `&rsquo;${currentYear - 2000}`;
const potaEnabled = (window.opener.g_potaEnabled === 1);
// TODO: Hunting results might be used to filter, based on the "Callsigns: Only Wanted" option,
// so maybe we can move this loop first, and add a check to the filtering loop?
// Second loop, hunting and highlighting
for (let callHash in callRoster)
for (const callHash in callRoster)
{
let entry = callRoster[callHash];
let callObj = entry.callObj;
@ -95,6 +95,8 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
callConf = gridConf = callingConf = dxccConf = stateConf = cntyConf = contConf = potaConf = cqzConf = ituzConf = wpxConf =
"";
let cntyPointer = (callObj.cnty && callObj.qual == false) ? "cursor: pointer;" : "";
let hash = callsign + workHashSuffix;
let layeredHash = layeredHashSuffix && (callsign + layeredHashSuffix)
@ -131,9 +133,9 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// We only do hunt highlighting when showing all entries
// This means "Callsigns: All Traffic", "Callsigns: All Traffic/Only Wanted" and "Logbook: Award Tracker"
// This means "Callsigns: All Traffic", "Callsigns: All Traffic/Only Wanted"
// There is no highlighting in other modes
if (rosterSettings.callMode == "all")
if (rosterSettings.callMode == "all" && rosterSettings.isAwardTracker == false)
{
// Skip when "only new calls"
// Questions: Move to the first loop? Why only skip new calls in "all traffic" and not other modes?
@ -163,25 +165,9 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
callObj.style.call = "class='dxCaller'";
}
}
// award tracker overrides
let awardTrackerOverrides = {
call: false,
grids: false,
dxcc: false,
states: false,
cnty: false,
cqz: false,
px: false,
cont: false
};
if (g_rosterSettings.reference == LOGBOOK_AWARD_TRACKER) {
for (let key in awardTracker) {
awardTrackerOverrides[awardTracker[key].rule.type] = true;
}
}
// Hunting for callsigns
if (huntCallsign.checked || awardTrackerOverrides.call)
if (huntCallsign.checked)
{
let hash = callsign + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (callsign + layeredHashSuffix)
@ -258,7 +244,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for grids
if ((huntGrid.checked || awardTrackerOverrides.grids) && callObj.grid.length > 1)
if (huntGrid.checked && callObj.grid.length > 1)
{
let hash = callObj.grid.substr(0, 4) + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (callObj.grid.substr(0, 4) + layeredHashSuffix)
@ -308,7 +294,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for DXCC
if (huntDXCC.checked || awardTrackerOverrides.dxcc)
if (huntDXCC.checked)
{
let hash = String(callObj.dxcc) + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (String(callObj.dxcc) + layeredHashSuffix)
@ -379,7 +365,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for US States
if ((huntState.checked || awardTrackerOverrides.states) && window.opener.g_callsignLookups.ulsUseEnable == true)
if (huntState.checked && window.opener.g_callsignLookups.ulsUseEnable == true)
{
let stateSearch = callObj.state;
let finalDxcc = callObj.dxcc;
@ -437,7 +423,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for US Counties
if ((huntCounty.checked || awardTrackerOverrides.cnty) && window.opener.g_callsignLookups.ulsUseEnable == true)
if (huntCounty.checked && window.opener.g_callsignLookups.ulsUseEnable == true)
{
let finalDxcc = callObj.dxcc;
if (
@ -454,7 +440,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
{
let counties = window.opener.g_zipToCounty[callObj.zipcode];
let foundHit = false;
for (let cnt in counties)
for (const cnt in counties)
{
let hh = counties[cnt] + workHash;
callObj.cnty = counties[cnt];
@ -492,71 +478,35 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for POTAs
if (huntPOTA.checked == true && window.opener.g_mapSettings.offlineMode == false && callObj.pota != null)
if (potaEnabled && huntPOTA.checked == true && callObj.pota.length > 0)
{
let huntTotal = callObj.pota.length;
let huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
let workedFound = 0;
for (index in callObj.pota)
for (const index in callObj.pota)
{
let hash = callObj.pota[index] + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (callObj.pota[index] + layeredHashSuffix)
let hash = g_dayAsString + callsign + callObj.pota[index] + (rosterSettings.layeredMode ? layeredHashSuffix : workHashSuffix);
// if (rosterSettings.huntIndex && hash in rosterSettings.huntIndex.pota) layeredFound++;
// if (rosterSettings.layeredMode && layeredHash in rosterSettings.huntIndex.pota) layeredFound++;
// if (rosterSettings.workedIndex && hash in rosterSettings.workedIndex.pota) workedFound++;
// if (rosterSettings.layeredMode && layeredHash in rosterSettings.workedIndex.pota) layeredWorkedFound++;
if (rosterSettings.workedIndex && hash in rosterSettings.workedIndex.pota) workedFound++;
}
if (huntFound != huntTotal)
if (workedFound != huntTotal)
{
shouldAlert = true;
callObj.reason.push("pota");
if (rosterSettings.workedIndex && workedFound == huntTotal)
{
if (rosterSettings.layeredMode && layeredFound == huntTotal)
{
callObj.hunting.pota = "worked-and-mixed";
potaConf = `${layeredUnconf}${pota}${layeredUnconfAlpha};`;
potaBg = `${potaBg}${layeredInversionAlpha}`;
pota = bold;
}
else
{
callObj.hunting.pota = "worked";
potaConf = `${unconf}${pota}${inversionAlpha};`;
}
}
else
{
if (rosterSettings.layeredMode && layeredFound == huntTotal)
{
callObj.hunting.pota = "mixed";
potaBg = `${pota}${layeredAlpha};`;
pota = bold;
}
else if (rosterSettings.layeredMode && layeredWorkedFound == huntTotal)
{
callObj.hunting.pota = "mixed-worked";
potaConf = `${unconf}${pota}${layeredAlpha};`;
}
else
{
callObj.hunting.pota = "hunted";
potaBg = `${pota}${inversionAlpha};`;
pota = bold;
}
}
callObj.hunting.pota = "hunted";
potaBg = `${pota}${inversionAlpha};`;
pota = bold;
}
}
// Hunting for CQ Zones
if (huntCQz.checked || awardTrackerOverrides.cqz)
if (huntCQz.checked)
{
let huntTotal = callObj.cqza.length;
let huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0, marathonFound = 0;
for (index in callObj.cqza)
for (const index in callObj.cqza)
{
let hash = callObj.cqza[index] + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (callObj.cqza[index] + layeredHashSuffix);
@ -614,7 +564,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
}
callObj.cqzSuffix = null
callObj.cqzSuffix = null;
if (huntMarathon.checked && callObj.hunting.cqz != "hunted" && callObj.hunting.cqz != "worked")
{
if (marathonFound != huntTotal)
@ -638,7 +588,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
let huntTotal = callObj.ituza.length;
let huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
for (index in callObj.ituza)
for (const index in callObj.ituza)
{
let hash = callObj.ituza[index] + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (callObj.ituza[index] + layeredHashSuffix)
@ -692,7 +642,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for WPX (Prefixes)
if ((huntPX.checked || awardTrackerOverrides.px) && callObj.px)
if (huntPX.checked && callObj.px)
{
let hash = String(callObj.px) + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (String(callObj.px) + layeredHashSuffix)
@ -742,7 +692,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
}
// Hunting for Continents
if ((huntCont.checked || awardTrackerOverrides.cont) && callObj.cont)
if (huntCont.checked && callObj.cont)
{
let hash = String(callObj.cont) + workHashSuffix;
let layeredHash = rosterSettings.layeredMode && (String(callObj.cont) + layeredHashSuffix)
@ -798,12 +748,12 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
callingBg = "#0000FF" + inversionAlpha;
calling = "#FFFF00;text-shadow: 0px 0px 2px #FFFF00";
}
else if (callObj.CQ == true && !g_rosterSettings.cqOnly)
else if ((callObj.CQ == true || (g_rosterSettings.wantRRCQ && callObj.RR73 == true)) && !g_rosterSettings.cqOnly)
{
callingBg = calling + inversionAlpha;
calling = bold;
// If treating RR73/73 as CQ, soften highlighting to help differentiate foreshadow from an actual CQ
if (callObj.DXcall == "RR73" || callObj.DXcall == "73")
if (g_rosterSettings.wantRRCQ && callObj.RR73 == true)
{
callingConf = `${unconf}#90EE90${inversionAlpha};`;
calling = `#90EE90${inversionAlpha};`
@ -818,7 +768,7 @@ function processRosterHunting(callRoster, rosterSettings, awardTracker)
colorObject.calling = "style='" + callingConf + "background-color:" + callingBg + ";color:" + calling + "'";
colorObject.dxcc = "style='" + dxccConf + "background-color:" + dxccBg + ";color:" + dxcc + "'";
colorObject.state = "style='" + stateConf + "background-color:" + stateBg + ";color:" + state + "'";
colorObject.cnty = "style='" + cntyConf + "background-color:" + cntyBg + ";color:" + cnty + "'";
colorObject.cnty = "style='" + cntyConf + "background-color:" + cntyBg + ";color:" + cnty + ";" + cntyPointer + "'";
colorObject.pota = "style='" + potaConf + "background-color:" + potaBg + ";color:" + pota + "'";
colorObject.cont = "style='" + contConf + "background-color:" + contBg + ";color:" + cont + "'";
colorObject.cqz = "style='" + cqzConf + "background-color:" + cqzBg + ";color:" + cqz + "'";

Wyświetl plik

@ -1,21 +1,16 @@
function renderNormalRosterHeaders(columns)
{
let html = "<table id='callTable' class='rosterTable' align=left><thead>"
html = html + columns.map(column => renderHeaderForColumn(column)).join("\n")
html = html + "</thead><tbody>"
let html = "<table id='callTable' class='rosterTable' align=left><thead>";
html = html + columns.map(column => renderHeaderForColumn(column)).join("");
html = html + "</thead><tbody>";
return html
return html;
}
function renderNormalRosterRow(columns, callObj)
{
callObj.grid4 = callObj.grid4 || (callObj.grid && callObj.grid.length > 1) ? callObj.grid.substr(0, 4) : "-";
callObj.hash = callObj.hash || `${callObj.DEcall}${callObj.band}${callObj.mode}`;
let html = `<tr id='${callObj.hash}'>`;
html = html + columns.map(column => renderEntryForColumn(column, callObj)).join("\n")
html = html + columns.map(column => renderEntryForColumn(column, callObj)).join("");
html += "</tr>";
return html;

Wyświetl plik

@ -1,8 +1,7 @@
function renderRoster(callRoster, rosterSettings)
{
let columnOverrides = {
Callsign: true,
Grid: true
Callsign: true
}
if (window.opener.g_callsignLookups.eqslUseEnable == true)
@ -34,6 +33,20 @@ function renderRoster(callRoster, rosterSettings)
columnOverrides.LoTW = false;
}
if (window.opener.g_potaEnabled === 1)
{
huntingMatrixPotaDiv.style.display = "";
}
else
{
huntingMatrixPotaDiv.style.display = "none";
columnOverrides.POTA = false;
}
if (rosterSettings.isAwardTracker)
{
columnOverrides.Wanted = true;
}
// dealing with spots
if (g_rosterSettings.columns.Spot == true) onlySpotDiv.style.display = "";
else onlySpotDiv.style.display = "none";

Wyświetl plik

@ -94,8 +94,8 @@ const ROSTER_COLUMNS = {
compare: callObjSimpleComparer("grid"),
tableData: (callObj) => ({
rawAttrs: callObj.style.grid,
onClick: `centerOn("${callObj.grid4}")`,
html: callObj.grid4
onClick: `centerOn("${callObj.grid}")`,
html: callObj.grid
})
},
@ -104,7 +104,7 @@ const ROSTER_COLUMNS = {
tableData: (callObj) => ({
rawAttrs: callObj.style.calling,
name: callObj.CQ ? "CQ" : "Calling",
html: callObj.DXcall.formatCallsign()
html: (g_rosterSettings.wantRRCQ && callObj.RR73) ? "RR73" : callObj.DXcall.formatCallsign()
})
},
@ -117,7 +117,7 @@ const ROSTER_COLUMNS = {
compare: (a, b) => window.opener.myDxccCompare(a.callObj, b.callObj),
tableData: (callObj) => ({
title: window.opener.g_worldGeoData[window.opener.g_dxccToGeoData[callObj.dxcc]].pp,
name: `${callObj.dxcc}`,
name: `DXCC (${callObj.dxcc})`,
rawAttrs: callObj.style.dxcc,
html: [window.opener.g_dxccToAltName[callObj.dxcc], callObj.dxccSuffix].join("&nbsp;")
})
@ -143,6 +143,7 @@ const ROSTER_COLUMNS = {
County: {
// Not sure why this comparison uses substring, but this is what the original code did
// Because we're sorting on the county name, the data contains "CO,Adams", we don't want to sort by state.
compare: getterSimpleComparer((elem) => elem.callObj.cnty && elem.callObj.cnty.substr(3)),
tableData: (callObj) =>
{
@ -151,11 +152,12 @@ const ROSTER_COLUMNS = {
rawAttrs: callObj.style.cnty,
html: callObj.cnty ? window.opener.g_cntyToCounty[callObj.cnty] : ""
}
if (callObj.cnty && callObj.qual)
if (callObj.cnty && callObj.qual == false)
{
attrs.title = "ZIP Code matches multiple counties, click to do a full lookup"
attrs.onClick = `lookupZip("${callObj.DEcall}", "${callObj.grid4}")`
attrs.html = `¿ ${attrs.html} ?`
attrs.title = "Matches multiple counties, click to do a full lookup"
attrs.onClick = `window.opener.lookupCallsign("${callObj.DEcall}", "${callObj.grid}")`
attrs.html = attrs.html + " +" + String(window.opener.g_zipToCounty[callObj.zipcode].length - 1)
attrs.style = "cursor: pointer; color: cyan;"
}
return attrs
}
@ -306,7 +308,7 @@ const ROSTER_COLUMNS = {
style: "color: #EEE;",
class: "lifeCol",
id: `lm${callObj.hash}`,
html: (timeNowSec() - callObj.life).toDHMS15()
html: (timeNowSec() - callObj.life).toDHMS()
})
},
@ -340,13 +342,12 @@ const ROSTER_COLUMNS = {
},
Age: {
compare: callObjSimpleComparer("time"),
compare: callObjSimpleComparer("age"),
tableData: (callObj) => ({
style: "color: #EEE;",
class: "timeCol",
id: `tm${callObj.hash}`,
title: (timeNowSec() - callObj.age).toDHMS(),
html: (timeNowSec() - callObj.age).toDHMS15()
html: (timeNowSec() - callObj.age).toDHMS()
})
},
@ -382,8 +383,8 @@ const ROSTER_COLUMNS = {
tableData: (callObj) => ({
name: "POTA",
rawAttrs: callObj.style.pota,
title: callObj.pota ? callObj.pota.name : "",
html: callObj.pota ? callObj.pota.reference : ""
title: potaColumnHover(callObj),
html: potaColumnRef(callObj)
})
},
@ -397,6 +398,36 @@ const ROSTER_COLUMNS = {
}
}
function potaColumnRef(callObj)
{
if (callObj.pota.length > 0)
{
let value = callObj.pota[0];
if (callObj.pota.length > 1)
{
value += " +" + String(callObj.pota.length - 1);
}
return value;
}
else
{
return "";
}
}
function potaColumnHover(callObj)
{
let value = ""
for (let i in callObj.pota)
{
if (callObj.pota[i] in window.opener.g_pota.parks)
{
value += callObj.pota[i] + " - " + window.opener.g_pota.parks[callObj.pota[i]].name + "\n";
}
}
return value;
}
WANTED_ORDER = ["call", "qrz", "cont", "dxcc", "cqz", "ituz", "dxccMarathon", "cqzMarathon", "state", "pota", "grid", "cnty", "wpx", "oams"];
WANTED_LABELS = {
cont: "Continent",
@ -418,7 +449,19 @@ function wantedColumnParts(callObj, options)
{
options = options || {};
if (!callObj.hunting) return [];
if (Object.keys(callObj.hunting).length == 0)
{
// is this an award reason?
// Hack until I talk with seb
if (callObj.awardReason != "Callsign")
{
return callObj.reason;
}
else
{
return [];
}
}
let parts = [];

Wyświetl plik

@ -339,7 +339,7 @@ body.roster {
.secondaryControlGroup .columns {
/* column-count: 2; */
max-height: 110px;
max-height: 135px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
@ -373,27 +373,9 @@ body.roster {
padding: 2px;
}
#instancesWrapper {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
min-width: 80px;
max-width: 160px;
margin-top: 22px;
text-align: right;
}
#instancesWrapper h3 {
text-align: left;
font-size: 12px;
margin: 0px;
margin-bottom: 2px;
padding: 0;
text-align: right;
}
#instancesWrapper .button {
display: block;
display: inline-block;
margin: 1px;
padding: 2px;
border-width: 1px;

Wyświetl plik

@ -1,7 +1,7 @@
{
"name": "GridTracker",
"product_string_do_not_use": "gridtracker",
"version": "1.22.0903",
"version": "1.22.0930",
"betaVersion": "",
"description": "GridTracker, an amateur radio companion",
"author": "GridTracker.org",