kopia lustrzana https://gitlab.com/gridtracker.org/gridtracker
Merge branch 'dev-test' into 'master'
Dev test Closes #150, #118, #95, and #9 See merge request gridtracker.org/gridtracker!179 If this is changing anything in the UI or operational behavior, please prepare to update the wiki!merge-requests/180/merge v1.22.0725
commit
c1f7f1be41
16
README.md
16
README.md
|
@ -107,19 +107,3 @@ Final build results are left in:
|
|||
# Editing GeoJSON files
|
||||
|
||||
We've had success using https://vector.rocks/ and then cleaning up the output with https://jsonformatter.org/
|
||||
|
||||
# Hacks
|
||||
|
||||
### Roster Column Ordering
|
||||
|
||||
We've added internal support for reordering roster columns, but have yet to implement a UI to change these settings.
|
||||
|
||||
In the meantime you can:
|
||||
* Open the roster window, right click on the "More Controls" link on the top right corner and select "Inspect" from the context menu.
|
||||
|
||||
* Select the "Console" tab in the Chrome DevTools window that should have appeared.
|
||||
|
||||
* Enter `g_rosterSettings.columnOrder` in the Console and press `[return]` to see the current list of columns.
|
||||
|
||||
* Enter the following in the Console, changing the values of `columnOrder` to fit your needs: `changeRosterColumnOrder(["Callsign", "Grid", "Spot"]);` and press `[return]`.
|
||||
Any columns included in this list will be shown before all other columns.
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
gridtracker (1.22.0725) unstable; urgency=low
|
||||
- Resolved #9 Call roster columns order can be changed
|
||||
- Resolved $95 Puts calling/called stations at the top of the call roster if sorting by Wanted
|
||||
- Resolved #118 Introduce POTA hunting in the call roster
|
||||
- Resolved #133 Fixes missing CloudLog Station Profile ID
|
||||
- Resolved #150 Highlights RR73/73 the same as a station calling CQ
|
||||
- Fixes pattern match for US 1x1 callsigns to match actual FCC rules around them.
|
||||
- Add WSJT-X/JTDX active instance name to roster window title when operating with multiple instances.
|
||||
|
||||
-- Matthew Chambers <nr0q@gridtracker.org> Sun, 24 Jul 2022 19:05:00 -0000
|
||||
|
||||
gridtracker (1.22.0503) unstable; urgency=low
|
||||
- Increment version for build with correct NWJS version
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Name: {{{ git_name name=gridtracker }}}
|
||||
Summary: GridTracker: An amateur radio companion to WSJT-X or JTDX
|
||||
Version: {{{ git_version lead=1.22.0503 }}}
|
||||
Version: {{{ git_version lead=1.22.0725 }}}
|
||||
Release: 1%{?dist}
|
||||
BuildArch: noarch
|
||||
Source0: {{{ git_dir_pack }}}
|
||||
|
@ -40,6 +40,14 @@ DESTDIR=${RPM_BUILD_ROOT} make clean
|
|||
%license %{_docdir}/%{name}/
|
||||
|
||||
%changelog
|
||||
* Sun Jul 24 2022 Matthew Chambers <nr0q@gridtracker.org> - 1.22.0725-1
|
||||
- Resolved #9 Call roster columns order can be changed
|
||||
- Resolved $95 Puts calling/called stations at the top of the call roster if sorting by Wanted
|
||||
- Resolved #118 Introduce POTA hunting in the call roster
|
||||
- Resolved #133 Fixes missing CloudLog Station Profile ID
|
||||
- Resolved #150 Highlights RR73/73 the same as a station calling CQ
|
||||
- Fixes pattern match for US 1x1 callsigns to match actual FCC rules around them.
|
||||
- Add WSJT-X/JTDX active instance name to roster window title when operating with multiple instances.
|
||||
* Mon May 02 2022 Matthew Chambers <nr0q@gridtracker.org> - 1.22.0503-1
|
||||
- Increment version number for build with correct vesion of NWJS
|
||||
* Mon May 02 2022 Matthew Chambers <nr0q@gridtracker.org> - 1.22.0502-1
|
||||
|
|
|
@ -48,8 +48,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
<script src="./lib/callsigns.js" type="text/javascript"></script>
|
||||
<script src="./lib/shadow.js" type="text/javascript"></script>
|
||||
<script src="./lib/gtws.js" type="text/javascript"></script>
|
||||
<script src="./lib/pota.js" type="text/javascript"></script>
|
||||
<script src="./lib/gt.js" type="text/javascript"></script>
|
||||
<script src="./lib/screens.js"></script>
|
||||
<script src="./lib/screens.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body id="mainBody" onload="initialDatabases();">
|
||||
<div id="startupDiv">
|
||||
|
|
|
@ -172,6 +172,11 @@
|
|||
<div>
|
||||
<label><input type="checkbox" id="huntPX" onchange="wantedChanged(this);" /> WPX</label>
|
||||
</div>
|
||||
<div>
|
||||
<label title="Parks On The Air">
|
||||
<input type="checkbox" id="huntPOTA" onchange="wantedChanged(this);" /> POTA
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label title="Off-Air Message Service Users">
|
||||
<input type="checkbox" id="huntOAMS" onchange="wantedChanged(this);" /> OAMS
|
||||
|
@ -184,6 +189,9 @@
|
|||
<div>
|
||||
<label><input type="checkbox" id="huntITUz" onchange="wantedChanged(this);" /> ITUz</label>
|
||||
</div>
|
||||
<div>
|
||||
<label title='CQ DX Marathon'><input type="checkbox" id="huntMarathon" onchange="wantedChanged(this);" /> Marathon</label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type="checkbox" id="huntState" onchange="wantedChanged(this);" /> State</label>
|
||||
</div>
|
||||
|
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
|
@ -1065,6 +1065,10 @@ function addDeDx(
|
|||
finalSatName = ""
|
||||
)
|
||||
{
|
||||
var currentYear = new Date().getFullYear();
|
||||
var qsoDate = new Date(1970, 0, 1); qsoDate.setSeconds(finalTime);
|
||||
var isCurrentYear = (qsoDate.getFullYear() == currentYear);
|
||||
|
||||
var callsign = null;
|
||||
var rect = null;
|
||||
var worked = false;
|
||||
|
@ -1262,6 +1266,10 @@ function addDeDx(
|
|||
g_tracker.worked.cqz[details.cqz + "dg"] = true;
|
||||
g_tracker.worked.cqz[details.cqz + band + "dg"] = true;
|
||||
}
|
||||
if (isCurrentYear)
|
||||
{
|
||||
g_tracker.worked.cqz[`${details.cqz}-${currentYear}`] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (details.dxcc > 0)
|
||||
|
@ -1276,6 +1284,10 @@ function addDeDx(
|
|||
g_tracker.worked.dxcc[sDXCC + "dg"] = true;
|
||||
g_tracker.worked.dxcc[sDXCC + band + "dg"] = true;
|
||||
}
|
||||
if (isCurrentYear)
|
||||
{
|
||||
g_tracker.worked.dxcc[`${sDXCC}-${currentYear}`] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (details.px)
|
||||
|
@ -6945,7 +6957,6 @@ function handleWsjtxDecode(newMessage)
|
|||
theTimeStamp =
|
||||
timeNowSec() - (timeNowSec() % 86400) + parseInt(newMessage.TM / 1000);
|
||||
var messageColor = "white";
|
||||
if (CQ == true) messageColor = "cyan";
|
||||
|
||||
// Break up the decoded message
|
||||
var decodeWords = newMessage.Msg.split(" ").slice(0, 5);
|
||||
|
@ -6991,6 +7002,7 @@ function handleWsjtxDecode(newMessage)
|
|||
CQ = true;
|
||||
msgDXcallsign = "CQ";
|
||||
}
|
||||
|
||||
if (decodeWords.length == 4 && CQ == true)
|
||||
{
|
||||
msgDXcallsign += " " + decodeWords[1];
|
||||
|
@ -7011,6 +7023,12 @@ function handleWsjtxDecode(newMessage)
|
|||
msgDEcallsign = decodeWords[1];
|
||||
}
|
||||
|
||||
if (decodeWords[2] == "RR73")
|
||||
{
|
||||
CQ = true;
|
||||
msgDXcallsign = "RR73";
|
||||
}
|
||||
|
||||
var callsign = null;
|
||||
|
||||
var hash = msgDEcallsign + newMessage.OB + newMessage.OM;
|
||||
|
@ -7079,6 +7097,7 @@ function handleWsjtxDecode(newMessage)
|
|||
newCallsign.qso = false;
|
||||
newCallsign.dxcc = callsignToDxcc(newCallsign.DEcall);
|
||||
newCallsign.px = null;
|
||||
newCallsign.pota = null;
|
||||
newCallsign.zone = null;
|
||||
newCallsign.vucc_grids = [];
|
||||
newCallsign.propMode = "";
|
||||
|
@ -7209,6 +7228,11 @@ function handleWsjtxDecode(newMessage)
|
|||
}
|
||||
}
|
||||
|
||||
if (g_potaSpots && g_potaSpots.some(item => item.activator === callsign.DEcall))
|
||||
{
|
||||
callsign.pota = g_potaSpots.filter(item => item.activator === callsign.DEcall)[0];
|
||||
}
|
||||
|
||||
if (newMessage.NW)
|
||||
{
|
||||
didCustomAlert = processAlertMessage(
|
||||
|
@ -12763,7 +12787,8 @@ function getBuffer(file_url, callback, flag, mode, port, cache = null)
|
|||
host: url.parse(file_url).host, // eslint-disable-line node/no-deprecated-api
|
||||
port: port,
|
||||
followAllRedirects: true,
|
||||
path: url.parse(file_url).path // eslint-disable-line node/no-deprecated-api
|
||||
path: url.parse(file_url).path, // eslint-disable-line node/no-deprecated-api
|
||||
headers: { "User-Agent": gtVersionString }
|
||||
};
|
||||
|
||||
http.get(options, function (res)
|
||||
|
@ -13741,7 +13766,9 @@ var g_startupTable = [
|
|||
[loadLookupDetails, "Callsign Lookup Details Loaded"],
|
||||
[startupEventsAndTimers, "Set Events and Timers"],
|
||||
[registerHotKeys, "Registered Hotkeys"],
|
||||
[gtChatSystemInit, "User System Initialized"],
|
||||
[gtChatSystemInit, "Chat System Initialized"],
|
||||
[getPotaPlaces, "Loading POTA Database"],
|
||||
[getPotaSpots, "Starting POTA Spots Pump"],
|
||||
[downloadAcknowledgements, "Contributor Acknowledgements Loaded"],
|
||||
[postInit, "Finalizing System"]
|
||||
];
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
// GridTracker Copyright © 2022 GridTracker.org
|
||||
// All rights reserved.
|
||||
// See LICENSE for more information.
|
||||
|
||||
var g_potaPlaces = null;
|
||||
var g_potaSpots = null;
|
||||
|
||||
function ingestPotaPlaces(buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_potaPlaces = JSON.parse(buffer);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// can't write, somethings broke
|
||||
}
|
||||
}
|
||||
|
||||
function getPotaPlaces()
|
||||
{
|
||||
if (g_mapSettings.offlineMode == false)
|
||||
{
|
||||
getBuffer(
|
||||
"https://storage.googleapis.com/gt_app/pota.json",
|
||||
ingestPotaPlaces,
|
||||
null,
|
||||
"https",
|
||||
443
|
||||
);
|
||||
|
||||
setTimeout(getPotaPlaces, 86400000)
|
||||
}
|
||||
}
|
||||
|
||||
function ingestPotaSpots(buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_potaSpots = JSON.parse(buffer);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// can't write, somethings broke
|
||||
}
|
||||
}
|
||||
|
||||
function getPotaSpots()
|
||||
{
|
||||
if (g_mapSettings.offlineMode == false && g_spotsEnabled == 1)
|
||||
{
|
||||
getBuffer(
|
||||
"https://api.pota.app/spot/activator",
|
||||
ingestPotaSpots,
|
||||
null,
|
||||
"https",
|
||||
443
|
||||
);
|
||||
|
||||
setTimeout(getPotaSpots, 300000);
|
||||
}
|
||||
}
|
||||
|
||||
function g_sendPotaSpot()
|
||||
{
|
||||
// if Pota spotting enabled, and we have enough info, send a spot to Pota
|
||||
}
|
|
@ -26,6 +26,8 @@ var g_callMenu = null;
|
|||
var g_ageMenu = null;
|
||||
var g_callingMenu = null;
|
||||
var g_compactMenu = null;
|
||||
var g_menuItemForCurrentColumn = null;
|
||||
var g_currentColumnName = null;
|
||||
var g_targetHash = "";
|
||||
var g_clearIgnores = null;
|
||||
var g_clearIgnoresCall = null;
|
||||
|
@ -96,10 +98,12 @@ var g_defaultSettings = {
|
|||
huntDXCC: true,
|
||||
huntCQz: false,
|
||||
huntITUz: false,
|
||||
huntMarathon: false,
|
||||
huntState: false,
|
||||
huntCounty: false,
|
||||
huntCont: false,
|
||||
huntPX: false,
|
||||
huntPOTA: false,
|
||||
huntQRZ: true,
|
||||
huntOAMS: false
|
||||
},
|
||||
|
@ -112,6 +116,7 @@ var g_defaultSettings = {
|
|||
Flag: true,
|
||||
State: true,
|
||||
County: true,
|
||||
POTA: false,
|
||||
Cont: true,
|
||||
dB: true,
|
||||
Freq: false,
|
||||
|
@ -1704,6 +1709,19 @@ function init()
|
|||
item = new nw.MenuItem({ type: "separator" });
|
||||
g_menu.append(item);
|
||||
|
||||
g_menuItemForCurrentColumn = new nw.MenuItem({
|
||||
type: "normal",
|
||||
label: "Move Column Left",
|
||||
click: function ()
|
||||
{
|
||||
moveColumnLeft(g_currentColumnName);
|
||||
}
|
||||
})
|
||||
g_menu.append(g_menuItemForCurrentColumn)
|
||||
|
||||
item = new nw.MenuItem({ type: "separator" });
|
||||
g_menu.append(item);
|
||||
|
||||
for (let columnIndex in g_rosterSettings.columnOrder)
|
||||
{
|
||||
let key = g_rosterSettings.columnOrder[columnIndex];
|
||||
|
@ -2162,7 +2180,9 @@ function handleContextMenu(ev)
|
|||
}
|
||||
}
|
||||
|
||||
let name = ev.target.getAttribute("name");
|
||||
let name
|
||||
if (ev.target.tagName == "TD") name = ev.target.getAttribute("name");
|
||||
|
||||
if (name == "Callsign")
|
||||
{
|
||||
g_targetHash = ev.target.parentNode.id;
|
||||
|
@ -2200,13 +2220,24 @@ function handleContextMenu(ev)
|
|||
}
|
||||
else
|
||||
{
|
||||
if (g_rosterSettings.compact == false)
|
||||
if (g_rosterSettings.compact)
|
||||
{
|
||||
g_menu.popup(mouseX, mouseY);
|
||||
g_compactMenu.popup(mouseX, mouseY);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_compactMenu.popup(mouseX, mouseY);
|
||||
if (ev.target.tagName == "TH" && ev.target.getAttribute("name"))
|
||||
{
|
||||
g_menuItemForCurrentColumn.enabled = true;
|
||||
g_currentColumnName = ev.target.getAttribute("name");
|
||||
}
|
||||
else
|
||||
{
|
||||
g_menuItemForCurrentColumn.enabled = false;
|
||||
g_currentColumnName = null;
|
||||
}
|
||||
|
||||
g_menu.popup(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,18 @@ 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;
|
||||
|
@ -378,6 +390,12 @@ function processRosterFiltering(callRoster, rosterSettings)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callObj.shouldAlert == false && rosterSettings.onlyHits == true && callObj.qrz == false)
|
||||
{
|
||||
tx = false
|
||||
}
|
||||
|
||||
entry.tx = tx;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
let layeredUnconf = "background-clip:padding-box;box-shadow: 0 0 4px 2px inset ";
|
||||
let layeredUnconfAlpha = "AA";
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentYearSuffix = `’${currentYear - 2000}`;
|
||||
|
||||
// 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?
|
||||
|
||||
|
@ -62,6 +65,7 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
|
||||
callObj.hunting = {}
|
||||
callObj.callFlags = {}
|
||||
callObj.style = callObj.style || {}
|
||||
|
||||
let colorObject = Object();
|
||||
|
||||
|
@ -76,18 +80,19 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
let state = "#90EE90";
|
||||
let cnty = "#CCDD00";
|
||||
let cont = "#00DDDD";
|
||||
let pota = "#fbb6fc";
|
||||
let cqz = "#DDDDDD";
|
||||
let ituz = "#DDDDDD";
|
||||
let wpx = "#FFFF00";
|
||||
|
||||
hasGtPin = false;
|
||||
let shouldAlert = false;
|
||||
let callBg, gridBg, callingBg, dxccBg, stateBg, cntyBg, contBg, cqzBg, ituzBg, wpxBg, gtBg;
|
||||
let callConf, gridConf, callingConf, dxccConf, stateConf, cntyConf, contConf, cqzConf, ituzConf, wpxConf;
|
||||
let callBg, gridBg, callingBg, dxccBg, stateBg, cntyBg, contBg, potaBg, cqzBg, ituzBg, wpxBg, gtBg;
|
||||
let callConf, gridConf, callingConf, dxccConf, stateConf, cntyConf, contConf, potaConf, cqzConf, ituzConf, wpxConf;
|
||||
|
||||
callBg = gridBg = callingBg = dxccBg = stateBg = cntyBg = contBg = cqzBg = ituzBg = wpxBg = gtBg = row;
|
||||
callBg = gridBg = callingBg = dxccBg = stateBg = cntyBg = contBg = potaBg = cqzBg = ituzBg = wpxBg = gtBg = row;
|
||||
|
||||
callConf = gridConf = callingConf = dxccConf = stateConf = cntyConf = contConf = cqzConf = ituzConf = wpxConf =
|
||||
callConf = gridConf = callingConf = dxccConf = stateConf = cntyConf = contConf = potaConf = cqzConf = ituzConf = wpxConf =
|
||||
"";
|
||||
|
||||
let hash = callsign + workHashSuffix;
|
||||
|
@ -138,6 +143,27 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
continue;
|
||||
}
|
||||
|
||||
// Special Calls
|
||||
if (callObj.DEcall.match("^[A-Z][0-9][A-Z](/w+)?$"))
|
||||
{
|
||||
callObj.style.call = "class='oneByOne'";
|
||||
}
|
||||
|
||||
// Entries currently calling or being called by us
|
||||
if (callObj.DEcall == window.opener.g_instances[callObj.instance].status.DXcall)
|
||||
{
|
||||
if (window.opener.g_instances[callObj.instance].status.TxEnabled == 1)
|
||||
{
|
||||
callObj.hunting.call = "calling";
|
||||
callObj.style.call = "class='dxCalling'";
|
||||
}
|
||||
else
|
||||
{
|
||||
callObj.hunting.call = "caller";
|
||||
callObj.style.call = "class='dxCaller'";
|
||||
}
|
||||
}
|
||||
|
||||
// Hunting for callsigns
|
||||
if (huntCallsign.checked == true)
|
||||
{
|
||||
|
@ -202,6 +228,7 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
if (huntQRZ.checked == true && callObj.qrz == true)
|
||||
{
|
||||
callObj.callFlags.calling = true
|
||||
callObj.hunting.qrz = "hunted";
|
||||
shouldAlert = true;
|
||||
callObj.reason.push("qrz");
|
||||
}
|
||||
|
@ -312,6 +339,27 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
callObj.dxccSuffix = null
|
||||
if (huntMarathon.checked && callObj.hunting.dxcc != "hunted" && callObj.hunting.dxcc != "checked")
|
||||
{
|
||||
callObj.reason.push("dxcc-marathon");
|
||||
|
||||
let hash = `${callObj.dxcc}-${currentYear}`;
|
||||
if (rosterSettings.huntIndex && !(hash in rosterSettings.huntIndex.dxcc))
|
||||
{
|
||||
if (!rosterSettings.workedIndex || !(hash in rosterSettings.workedIndex.dxcc))
|
||||
{
|
||||
callObj.dxccSuffix = currentYearSuffix;
|
||||
|
||||
callObj.hunting.dxccMarathon = "hunted";
|
||||
if (!callObj.hunting.dxcc)
|
||||
{
|
||||
dxccConf = `${unconf}${dxcc}${layeredAlpha};`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hunting for US States
|
||||
|
@ -427,21 +475,86 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
}
|
||||
}
|
||||
|
||||
// Hunting for POTAs
|
||||
if (huntPOTA.checked == true && window.opener.g_mapSettings.offlineMode == false && callObj.pota != null)
|
||||
{
|
||||
let huntTotal = callObj.pota.length;
|
||||
let huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
|
||||
|
||||
for (index in callObj.pota)
|
||||
{
|
||||
let hash = callObj.pota[index] + workHashSuffix;
|
||||
let layeredHash = rosterSettings.layeredMode && (callObj.pota[index] + layeredHashSuffix)
|
||||
|
||||
// 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 (huntFound != 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hunting for CQ Zones
|
||||
if (huntCQz.checked == true)
|
||||
{
|
||||
let huntTotal = callObj.cqza.length;
|
||||
let huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0;
|
||||
let huntFound = 0, layeredFound = 0, workedFound = 0, layeredWorkedFound = 0, marathonFound = 0;
|
||||
|
||||
for (index in callObj.cqza)
|
||||
{
|
||||
let hash = callObj.cqza[index] + workHashSuffix;
|
||||
let layeredHash = rosterSettings.layeredMode && (callObj.cqza[index] + layeredHashSuffix)
|
||||
let layeredHash = rosterSettings.layeredMode && (callObj.cqza[index] + layeredHashSuffix);
|
||||
let marathonHash = huntMarathon.checked && `${callObj.cqza[index]}-${currentYear}`;
|
||||
|
||||
if (rosterSettings.huntIndex && hash in rosterSettings.huntIndex.cqz) huntFound++;
|
||||
if (rosterSettings.layeredMode && layeredHash in rosterSettings.huntIndex.cqz) layeredFound++;
|
||||
if (rosterSettings.workedIndex && hash in rosterSettings.workedIndex.cqz) workedFound++;
|
||||
if (rosterSettings.layeredMode && layeredHash in rosterSettings.workedIndex.cqz) layeredWorkedFound++;
|
||||
if (marathonHash)
|
||||
{
|
||||
if (rosterSettings.huntIndex && marathonHash in rosterSettings.huntIndex.cqz) marathonFound++;
|
||||
else if (rosterSettings.workedIndex && marathonHash in rosterSettings.workedIndex.cqz) marathonFound++;
|
||||
}
|
||||
}
|
||||
if (huntFound != huntTotal)
|
||||
{
|
||||
|
@ -484,6 +597,23 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
callObj.cqzSuffix = null
|
||||
if (huntMarathon.checked && callObj.hunting.cqz != "hunted" && callObj.hunting.cqz != "worked")
|
||||
{
|
||||
if (marathonFound != huntTotal)
|
||||
{
|
||||
callObj.reason.push("cqz-marathon");
|
||||
|
||||
callObj.cqzSuffix = currentYearSuffix;
|
||||
|
||||
callObj.hunting.cqzMarathon = "hunted";
|
||||
if (!callObj.hunting.cqz)
|
||||
{
|
||||
cqzConf = `${unconf}${cqz}${layeredAlpha};`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hunting for ITU Zones
|
||||
|
@ -666,6 +796,7 @@ function processRosterHunting(callRoster, rosterSettings)
|
|||
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.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 + "'";
|
||||
colorObject.ituz = "style='" + ituzConf + "background-color:" + ituzBg + ";color:" + ituz + "'";
|
||||
|
|
|
@ -97,15 +97,6 @@ function renderRoster(callRoster, rosterSettings)
|
|||
window.document.title += " | " + listShortInstances().join(" • ");
|
||||
}
|
||||
|
||||
if (g_rosterSettings.compact)
|
||||
{
|
||||
sortCallList(visibleCallList, "Age", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sortCallList(visibleCallList, g_rosterSettings.sortColumn, g_rosterSettings.sortReverse);
|
||||
}
|
||||
|
||||
let showBands = (Object.keys(rosterSettings.bands).length > 1) || g_rosterSettings.columns.Band;
|
||||
let showModes = (Object.keys(rosterSettings.modes).length > 1) || g_rosterSettings.columns.Mode;
|
||||
|
||||
|
@ -113,6 +104,15 @@ function renderRoster(callRoster, rosterSettings)
|
|||
columnOverrides.Mode = showModes
|
||||
const rosterColumns = rosterColumnList(g_rosterSettings.columns, columnOverrides)
|
||||
|
||||
if (g_rosterSettings.compact)
|
||||
{
|
||||
sortCallList(visibleCallList, "Age", false, rosterColumns);
|
||||
}
|
||||
else
|
||||
{
|
||||
sortCallList(visibleCallList, g_rosterSettings.sortColumn, g_rosterSettings.sortReverse);
|
||||
}
|
||||
|
||||
let worker = g_rosterSettings.compact ? renderCompactRosterHeaders() : renderNormalRosterHeaders(rosterColumns)
|
||||
|
||||
// Third loop: render all rows
|
||||
|
@ -142,5 +142,6 @@ function renderRoster(callRoster, rosterSettings)
|
|||
}
|
||||
|
||||
worker += g_rosterSettings.compact ? renderCompactRosterFooter() : renderNormalRosterFooter()
|
||||
|
||||
RosterTable.innerHTML = worker;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ function renderHeaderForColumn(column)
|
|||
|
||||
let attrs = (columnInfo && columnInfo.tableHeader && columnInfo.tableHeader()) || {}
|
||||
|
||||
attrs.name = column
|
||||
|
||||
attrs.html = attrs.html || column
|
||||
|
||||
if (columnInfo.compare)
|
||||
|
@ -62,11 +64,20 @@ function setRosterSorting(column)
|
|||
window.opener.goProcessRoster();
|
||||
}
|
||||
|
||||
function sortCallList(callList, sortColumn, sortReverse)
|
||||
function sortCallList(callList, sortColumn, sortReverse, columns)
|
||||
{
|
||||
const columnInfo = ROSTER_COLUMNS[sortColumn]
|
||||
|
||||
callList.sort((columnInfo && columnInfo.compare) || ROSTER_COLUMNS.Age.compare)
|
||||
const comparerList = [
|
||||
(columnInfo && columnInfo.compare) || ROSTER_COLUMNS.Age.compare,
|
||||
columns && columns.includes("Spot") && ROSTER_COLUMNS.Spot.compare,
|
||||
columns && columns.includes("dB") && ROSTER_COLUMNS.dB.compare,
|
||||
columns && columns.includes("Age") && ROSTER_COLUMNS.Age.compare,
|
||||
columns && columns.includes("Life") && ROSTER_COLUMNS.Life.compare,
|
||||
columns && columns.includes("Callsign") && ROSTER_COLUMNS.Callsign.compare
|
||||
]
|
||||
|
||||
callList.sort(multiColumnComparer(comparerList))
|
||||
|
||||
if (sortReverse)
|
||||
{
|
||||
|
@ -74,16 +85,36 @@ function sortCallList(callList, sortColumn, sortReverse)
|
|||
}
|
||||
}
|
||||
|
||||
const multiColumnComparer = (comparers) => (a, b) =>
|
||||
{
|
||||
let result = 0;
|
||||
for (let i in comparers)
|
||||
{
|
||||
result = comparers[i] && comparers[i](a, b);
|
||||
if (result) return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function validateRosterColumnOrder(columns)
|
||||
{
|
||||
let correctedColumnOrder = (columns || DEFAULT_COLUMN_ORDER || []).slice();
|
||||
|
||||
// Aappend columns not included in the suggested list.
|
||||
DEFAULT_COLUMN_ORDER.forEach(column =>
|
||||
{
|
||||
if (!correctedColumnOrder.includes(column)) correctedColumnOrder.push(column);
|
||||
})
|
||||
|
||||
// Exclude any unexpected values
|
||||
correctedColumnOrder = correctedColumnOrder.filter(column => !!ROSTER_COLUMNS[column])
|
||||
|
||||
// Ensure the first three columns are always the same
|
||||
correctedColumnOrder = correctedColumnOrder.filter(column => column != "Callsign" && column != "Band" && column != "Mode");
|
||||
correctedColumnOrder.unshift("Mode");
|
||||
correctedColumnOrder.unshift("Band");
|
||||
correctedColumnOrder.unshift("Callsign");
|
||||
|
||||
return correctedColumnOrder;
|
||||
}
|
||||
|
||||
|
@ -93,3 +124,15 @@ function changeRosterColumnOrder(columns)
|
|||
writeRosterSettings();
|
||||
window.opener.goProcessRoster();
|
||||
}
|
||||
|
||||
function moveColumnLeft(column)
|
||||
{
|
||||
const columns = rosterColumnList(g_rosterSettings.columns, { Callsign: true, Grid: true });
|
||||
const pos = columns.indexOf(column);
|
||||
if (pos > 1)
|
||||
{
|
||||
columns[pos] = columns[pos - 1];
|
||||
columns[pos - 1] = column;
|
||||
}
|
||||
changeRosterColumnOrder(columns);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const DEFAULT_COLUMN_ORDER = [
|
||||
"Callsign", "Band", "Mode", "Grid", "Calling", "Msg",
|
||||
"DXCC", "Flag", "State", "County", "Cont",
|
||||
"Callsign", "Band", "Mode", "Calling", "Wanted", "Grid", "Msg",
|
||||
"POTA", "DXCC", "Flag", "State", "County", "Cont",
|
||||
"dB", "Freq", "DT", "Dist", "Azim",
|
||||
"CQz", "ITUz", "PX",
|
||||
"LoTW", "eQSL", "OQRS",
|
||||
|
@ -30,6 +30,7 @@ const getterSimpleComparer = (getter) => (a, b) =>
|
|||
{
|
||||
const aVal = getter(a);
|
||||
const bVal = getter(b);
|
||||
|
||||
if (aVal == null) return 1;
|
||||
if (bVal == null) return -1;
|
||||
if (aVal > bVal) return 1;
|
||||
|
@ -62,7 +63,7 @@ const ROSTER_COLUMNS = {
|
|||
html: html = callObj.DEcall.formatCallsign()
|
||||
}
|
||||
|
||||
let acks = window.opener.g_acknowledgedCalls;
|
||||
let acks = window.opener.g_acknowledgedCalls || {};
|
||||
if (acks[callObj.DEcall])
|
||||
{
|
||||
attrs.html = `${attrs.html} <span class='acknowledged'><img class='ackBadge' src='${acks[callObj.DEcall].badge}'></span>`
|
||||
|
@ -116,9 +117,9 @@ 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: `DXCC (${callObj.dxcc})`,
|
||||
name: `${callObj.dxcc}`,
|
||||
rawAttrs: callObj.style.dxcc,
|
||||
html: window.opener.g_dxccToAltName[callObj.dxcc]
|
||||
html: [window.opener.g_dxccToAltName[callObj.dxcc], callObj.dxccSuffix].join(" ")
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -215,7 +216,7 @@ const ROSTER_COLUMNS = {
|
|||
tableData: (callObj) => ({
|
||||
name: "CQz",
|
||||
rawAttrs: callObj.style.cqz,
|
||||
html: callObj.cqza.join(",")
|
||||
html: [callObj.cqza.join(","), callObj.cqzSuffix].join(" ")
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -374,5 +375,104 @@ const ROSTER_COLUMNS = {
|
|||
id: `sp${callObj.hash}`,
|
||||
html: getSpotString(callObj)
|
||||
})
|
||||
},
|
||||
|
||||
POTA: {
|
||||
compare: false,
|
||||
tableData: (callObj) => ({
|
||||
name: "POTA",
|
||||
rawAttrs: callObj.style.pota,
|
||||
title: callObj.pota ? callObj.pota.name : "",
|
||||
html: callObj.pota ? callObj.pota.reference : ""
|
||||
})
|
||||
},
|
||||
|
||||
Wanted: {
|
||||
compare: (a, b) => wantedColumnComparer(a.callObj, b.callObj),
|
||||
tableData: (callObj) => ({
|
||||
class: "wantedCol",
|
||||
title: wantedColumnParts(callObj).map(entry => `• ${entry}`).join("\n"),
|
||||
html: wantedColumnParts(callObj).join(" - ", { html: true })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
WANTED_ORDER = ["call", "qrz", "cont", "dxcc", "cqz", "ituz", "dxccMarathon", "cqzMarathon", "state", "pota", "grid", "cnty", "wpx", "oams"];
|
||||
WANTED_LABELS = {
|
||||
cont: "Continent",
|
||||
cqz: "CQ Zone",
|
||||
ituz: "ITU Zone",
|
||||
dxcc: "DXCC",
|
||||
dxccMarathon: "Marathon DXCC",
|
||||
cqzMarathon: "Marathon CQ Zone",
|
||||
state: "State",
|
||||
grid: "Grid",
|
||||
cnty: "County",
|
||||
wpx: "WPX",
|
||||
call: "Call",
|
||||
oams: "OAMS",
|
||||
pota: "POTA"
|
||||
}
|
||||
|
||||
function wantedColumnParts(callObj, options)
|
||||
{
|
||||
options = options || {};
|
||||
|
||||
if (!callObj.hunting) return [];
|
||||
|
||||
let parts = [];
|
||||
|
||||
WANTED_ORDER.forEach(field =>
|
||||
{
|
||||
let wanted = callObj.hunting[field];
|
||||
|
||||
if (wanted == "calling") { parts.push("Calling"); }
|
||||
// else if (wanted == "caller") { parts.push("Called"); }
|
||||
else if (wanted == "hunted" && field == "qrz") { parts.push("Caller"); }
|
||||
else if (wanted == "hunted" && field == "oams") { parts.push("OAMS User"); }
|
||||
else if (wanted == "hunted") { parts.push(`${options.html ? "<b>" : ""}New ${WANTED_LABELS[field]}${options.html ? "<b>" : ""}`); }
|
||||
else if (wanted == "worked") { parts.push(`Worked ${WANTED_LABELS[field]}`); }
|
||||
else if (wanted == "mixed") { parts.push(`${callObj.band} ${WANTED_LABELS[field]}`); }
|
||||
else if (wanted == "mixed-worked") { parts.push(`${callObj.band} ${WANTED_LABELS[field]}`); parts.push(`Worked ${WANTED_LABELS[field]}`); }
|
||||
else if (wanted == "worked-and-mixed") { parts.push(`Worked ${callObj.band} ${WANTED_LABELS[field]}`); }
|
||||
})
|
||||
|
||||
if (parts[0] == "Calling" && parts[1] == "Caller")
|
||||
{
|
||||
parts.shift(); parts.shift();
|
||||
parts.unshift(`${options.html ? "<b>" : ""}Working${options.html ? "<b>" : ""}`);
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
function wantedColumnWeighter(callObj, field)
|
||||
{
|
||||
let wanted = callObj.hunting[field];
|
||||
|
||||
// We use negative numbers so that sorting is "reversed" by default, placing most interesting items up top.
|
||||
if (wanted == "calling" || wanted == "caller") return -10;
|
||||
else if (wanted == "hunted") return -5;
|
||||
else if (wanted == "worked") return -4;
|
||||
else if (wanted == "mixed") return -3;
|
||||
else if (wanted == "mixed-worked") return -2;
|
||||
else if (wanted == "worked-and-mixed") return -1;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
function wantedColumnComparer(a, b)
|
||||
{
|
||||
if (!a.hunting) return 1;
|
||||
if (!b.hunting) return -1;
|
||||
|
||||
for (const index in WANTED_ORDER)
|
||||
{
|
||||
const field = WANTED_ORDER[index];
|
||||
const aWeight = wantedColumnWeighter(a, field);
|
||||
const bWeight = wantedColumnWeighter(b, field);
|
||||
|
||||
if (aWeight < bWeight) return 1;
|
||||
if (aWeight > bWeight) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -310,11 +310,11 @@ body.roster {
|
|||
}
|
||||
|
||||
#huntingMatrixDiv {
|
||||
flex: 0.5;
|
||||
flex: 0.75;
|
||||
}
|
||||
|
||||
#exceptionDiv {
|
||||
flex: 1.5;
|
||||
flex: 1.25;
|
||||
}
|
||||
|
||||
.secondaryControlGroup h3 {
|
||||
|
@ -557,4 +557,12 @@ table.rosterTable thead th:first-child {
|
|||
.ackBadge {
|
||||
padding: 0;
|
||||
width: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.wantedCol {
|
||||
max-width: 160px;
|
||||
overflow: hidden;
|
||||
text-overflow:
|
||||
ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"name": "GridTracker",
|
||||
"product_string_do_not_use": "gridtracker",
|
||||
"version": "1.22.0503",
|
||||
"version": "1.22.0725",
|
||||
"betaVersion": "",
|
||||
"description": "GridTracker, an amateur radio companion",
|
||||
"author": "Stephen Loomis (N0TTL) and GridTracker.org",
|
||||
"author": "GridTracker.org",
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "GridTracker.html",
|
||||
"window": {
|
||||
|
|
Ładowanie…
Reference in New Issue