From c54252a8057714289ae71fd47a8233c7172b57f2 Mon Sep 17 00:00:00 2001 From: Sebastian Delmont Date: Sat, 5 Mar 2022 03:25:45 +0000 Subject: [PATCH] Refactor roster columns --- README.md | 16 + package.nw/gt_roster.html | 2 + package.nw/lib/gt.js | 13 +- package.nw/lib/protos.js | 23 + package.nw/lib/roster.js | 208 ++------- package.nw/lib/roster/processRosterHunting.js | 4 +- package.nw/lib/roster/renderNormalRoster.js | 412 +----------------- package.nw/lib/roster/renderRoster.js | 63 ++- .../lib/roster/rosterColumnFunctions.js | 95 ++++ package.nw/lib/roster/rosterColumns.js | 378 ++++++++++++++++ 10 files changed, 612 insertions(+), 602 deletions(-) create mode 100644 package.nw/lib/roster/rosterColumnFunctions.js create mode 100644 package.nw/lib/roster/rosterColumns.js diff --git a/README.md b/README.md index f0f7515..bcba315 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,19 @@ 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. diff --git a/package.nw/gt_roster.html b/package.nw/gt_roster.html index 3909386..871fc52 100644 --- a/package.nw/gt_roster.html +++ b/package.nw/gt_roster.html @@ -28,6 +28,8 @@ + + diff --git a/package.nw/lib/gt.js b/package.nw/lib/gt.js index e005978..6a3c9df 100644 --- a/package.nw/lib/gt.js +++ b/package.nw/lib/gt.js @@ -1092,7 +1092,11 @@ function addDeDx( details.grid.length < 6 && (details.grid.substr(0, 4) == finalGrid.substr(0, 4) || details.grid.length == 0) - ) { details.grid = finalGrid; } + ) + { + details.grid = finalGrid; + details.grid4 = finalGrid.substr(0, 4); + } } if (finalRSTsent.length > 0) details.RSTsent = finalRSTsent; if (finalRSTrecv.length > 0) details.RSTrecv = finalRSTrecv; @@ -1112,6 +1116,7 @@ function addDeDx( { details = {}; details.grid = finalGrid; + details.grid4 = finalGrid.length > 0 ? finalGrid.substr(0, 4) : "-"; details.RSTsent = finalRSTsent; details.RSTrecv = finalRSTrecv; details.msg = "-"; @@ -1153,10 +1158,9 @@ function addDeDx( finalGrid.length > 0 ) { - var fourGrid = finalGrid.substr(0, 4); - if (fourGrid in g_gridToState && g_gridToState[fourGrid].length == 1) + if (details.grid4 in g_gridToState && g_gridToState[details.grid4].length == 1) { - details.state = g_gridToState[fourGrid][0]; + details.state = g_gridToState[details.grid4][0]; } lookupCall = true; } @@ -14588,6 +14592,7 @@ function callookResults(buffer, gridPass) callObject.lat = results.location.latitude; callObject.lon = results.location.longitude; callObject.grid = results.location.gridsquare; + callObject.grid4 = callObject.grid.length > 1 ? callObject.grid.substr(0, 4) : "-"; callObject.efdate = results.otherInfo.grantDate; callObject.expdate = results.otherInfo.expiryDate; callObject.frn = results.otherInfo.frn; diff --git a/package.nw/lib/protos.js b/package.nw/lib/protos.js index 5e71394..4e38943 100644 --- a/package.nw/lib/protos.js +++ b/package.nw/lib/protos.js @@ -129,6 +129,27 @@ Number.prototype.toDHMS = function () return val; }; +Number.prototype.toDHMS15 = function () +{ + // round to earliest 15 seconds + + var seconds = Math.floor(this / 15) * 15; + 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.toDHM = function () { var seconds = this; @@ -143,6 +164,8 @@ Number.prototype.toDHM = function () hrs = hrs ? hrs + "h " : ""; mnts = mnts || seconds ? mnts + "m " : ""; val = days + hrs + mnts; + if (val == "") val = "0m"; + return val; }; diff --git a/package.nw/lib/roster.js b/package.nw/lib/roster.js index 4a9298b..1bb4af9 100644 --- a/package.nw/lib/roster.js +++ b/package.nw/lib/roster.js @@ -134,8 +134,9 @@ var g_defaultSettings = { controlsExtended: false, compact: false, settingProfiles: false, - lastSortIndex: 6, - lastSortReverse: 1 + + sortColumn: "Age", + sortReverse: false }; const LOGBOOK_LIVE_BAND_LIVE_MODE = "0"; @@ -219,11 +220,34 @@ function loadSettings() } g_rosterSettings = deepmerge(g_defaultSettings, readSettings); - if ("GT" in g_rosterSettings.columns) delete g_rosterSettings.columns.GT; + fixLegacySettings(); writeRosterSettings(); } +function fixLegacySettings() +{ + // Not sure why, but Paul Traina added this settings cleanup in August 2020. + if ("GT" in g_rosterSettings.columns) delete g_rosterSettings.columns.GT; + + // In January 2022, we refactored roster column sorting + if (g_rosterSettings.lastSortIndex) + { + g_rosterSettings.sortColumn = LEGACY_COLUMN_SORT_ID[g_rosterSettings.lastSortIndex] || "Age"; + delete g_rosterSettings.lastSortIndex; + } + + // In January 2022, we refactored roster column sorting + if (g_rosterSettings.lastSortReverse) + { + g_rosterSettings.sortReverse = g_rosterSettings.lastSortReverse; + delete g_rosterSettings.lastSortReverse; + } + + // In January 2022, we added a `columnOrder` setting, which we need to ensure always includes all columns + g_rosterSettings.columnOrder = validateRosterColumnOrder(g_rosterSettings.columnOrder); +} + function writeRosterSettings() { localStorage.rosterSettings = JSON.stringify(g_rosterSettings); @@ -266,178 +290,6 @@ function lockNewWindows() } } -var r_sortFunction = [ - myCallCompare, - myGridCompare, - myDbCompare, - myDTCompare, - myFreqCompare, - myDxccCompare, - myTimeCompare, - myDistanceCompare, - myHeadingCompare, - myStateCompare, - myCQCompare, - myWPXCompare, - myLifeCompare, - mySpotCompare, - myGTCompare, - myCntyCompare, - myContCompare -]; - -function myCallCompare(a, b) -{ - return a.callObj.DEcall.localeCompare(b.callObj.DEcall); -} - -function myGridCompare(a, b) -{ - let gridA = a.callObj.grid ? a.callObj.grid : "0"; - let gridB = b.callObj.grid ? b.callObj.grid : "0"; - - if (gridA > gridB) return 1; - if (gridA < gridB) return -1; - return 0; -} - -function myDxccCompare(a, b) -{ - return window.opener.myDxccCompare(a.callObj, b.callObj); -} - -function myTimeCompare(a, b) -{ - if (a.callObj.age > b.callObj.age) return 1; - if (a.callObj.age < b.callObj.age) return -1; - return 0; -} - -function myLifeCompare(a, b) -{ - if (a.callObj.life > b.callObj.life) return 1; - if (a.callObj.life < b.callObj.life) return -1; - return 0; -} - -function mySpotCompare(a, b) -{ - let cutoff = timeNowSec() - window.opener.g_receptionSettings.viewHistoryTimeSec; - - if (a.callObj.spot.when <= cutoff) return -1; - if (b.callObj.spot.when <= cutoff) return 1; - - let aSNR = Number(a.callObj.spot.snr); - let bSNR = Number(b.callObj.spot.snr); - - if (aSNR > bSNR) return 1; - if (aSNR < bSNR) return -1; - - if (a.callObj.spot.when > b.callObj.spot.when) return 1; - if (a.callObj.spot.when < b.callObj.spot.when) return -1; - - return 0; -} - -function myDbCompare(a, b) -{ - if (a.callObj.RSTsent > b.callObj.RSTsent) return 1; - if (a.callObj.RSTsent < b.callObj.RSTsent) return -1; - return 0; -} - -function myFreqCompare(a, b) -{ - if (a.callObj.delta > b.callObj.delta) return 1; - if (a.callObj.delta < b.callObj.delta) return -1; - return 0; -} - -function myDTCompare(a, b) -{ - if (a.callObj.dt > b.callObj.dt) return 1; - if (a.callObj.dt < b.callObj.dt) return -1; - return 0; -} - -function myDistanceCompare(a, b) -{ - if (a.callObj.distance > b.callObj.distance) return 1; - if (a.callObj.distance < b.callObj.distance) return -1; - return 0; -} - -function myHeadingCompare(a, b) -{ - if (a.callObj.heading > b.callObj.heading) return 1; - if (a.callObj.heading < b.callObj.heading) return -1; - return 0; -} - -function myStateCompare(a, b) -{ - if (a.callObj.state == null) return 1; - if (b.callObj.state == null) return -1; - if (a.callObj.state > b.callObj.state) return 1; - if (a.callObj.state < b.callObj.state) return -1; - return 0; -} - -function myCQCompare(a, b) -{ - return a.callObj.DXcall.localeCompare(b.callObj.DXcall); -} - -function myWPXCompare(a, b) -{ - if (a.callObj.px == null) return 1; - if (b.callObj.px == null) return -1; - if (a.callObj.px > b.callObj.px) return 1; - if (a.callObj.px < b.callObj.px) return -1; - return 0; -} - -function myCntyCompare(a, b) -{ - if (a.callObj.cnty == null) return 1; - if (b.callObj.cnty == null) return -1; - if (a.callObj.cnty.substr(3) > b.callObj.cnty.substr(3)) return 1; - if (a.callObj.cnty.substr(3) < b.callObj.cnty.substr(3)) return -1; - return 0; -} - -function myContCompare(a, b) -{ - if (a.callObj.cont == null) return 1; - if (b.callObj.cont == null) return -1; - if (a.callObj.cont > b.callObj.cont) return 1; - if (a.callObj.cont < b.callObj.cont) return -1; - return 0; -} -function myGTCompare(a, b) -{ - if (a.callObj.style.gt != 0 && b.callObj.style.gt == 0) return 1; - if (a.callObj.style.gt == 0 && b.callObj.style.gt != 0) return -1; - return 0; -} - -function showRosterBox(sortIndex) -{ - if (g_rosterSettings.lastSortIndex != sortIndex) - { - g_rosterSettings.lastSortIndex = sortIndex; - g_rosterSettings.lastSortReverse = 0; - } - else - { - g_rosterSettings.lastSortReverse ^= 1; - } - - writeRosterSettings(); - - window.opener.goProcessRoster(); -} - function hashMaker(start, callObj, reference) { if (reference == LOGBOOK_LIVE_BAND_LIVE_MODE) return `${start}${callObj.band}${callObj.mode}`; @@ -524,7 +376,7 @@ function getSpotString(callObj) { when = timeNowSec() - callObj.spot.when; if (when <= window.opener.g_receptionSettings.viewHistoryTimeSec) - { result = parseInt(when).toDHMS(); } + { result = parseInt(when).toDHM(); } } if (result) result += " / " + callObj.spot.snr; return result; @@ -1852,8 +1704,10 @@ function init() item = new nw.MenuItem({ type: "separator" }); g_menu.append(item); - for (let key in g_rosterSettings.columns) + for (let columnIndex in g_rosterSettings.columnOrder) { + let key = g_rosterSettings.columnOrder[columnIndex]; + let itemx = new nw.MenuItem({ type: "checkbox", label: key, diff --git a/package.nw/lib/roster/processRosterHunting.js b/package.nw/lib/roster/processRosterHunting.js index eb81d15..97c9661 100644 --- a/package.nw/lib/roster/processRosterHunting.js +++ b/package.nw/lib/roster/processRosterHunting.js @@ -117,12 +117,12 @@ function processRosterHunting(callRoster, rosterSettings) { callObj.callFlags.oams = true; // grab the CID - colorObject.gt = window.opener.g_gtCallsigns[callsign]; + callObj.gt = window.opener.g_gtCallsigns[callsign]; hasGtPin = true; } else { - colorObject.gt = 0; + callObj.gt = 0; } // We only do hunt highlighting when showing all entries diff --git a/package.nw/lib/roster/renderNormalRoster.js b/package.nw/lib/roster/renderNormalRoster.js index f71f8b9..b4fc5ec 100644 --- a/package.nw/lib/roster/renderNormalRoster.js +++ b/package.nw/lib/roster/renderNormalRoster.js @@ -1,413 +1,27 @@ -function renderNormalRosterHeaders(showBands, showModes) +function renderNormalRosterHeaders(columns) { - let worker = "" - worker = ""; + let html = "
" + html = html + columns.map(column => renderHeaderForColumn(column)).join("\n") + html = html + "" - worker += ""; - - if (showBands) - { worker += ""; } - - if (showModes) - { worker += ""; } - - worker += ""; - - if (g_rosterSettings.columns.Calling) - { worker += ""; } - - if (g_rosterSettings.columns.Msg) - { worker += ""; } - - if (g_rosterSettings.columns.DXCC) - { worker += ""; } - - if (g_rosterSettings.columns.Flag) - { worker += ""; } - - if (g_rosterSettings.columns.State) - { worker += ""; } - - if (g_rosterSettings.columns.County) - { worker += ""; } - - if (g_rosterSettings.columns.Cont) - { worker += ""; } - - if (g_rosterSettings.columns.dB) - { worker += ""; } - - if (g_rosterSettings.columns.Freq) - { worker += ""; } - - if (g_rosterSettings.columns.DT) - { worker += ""; } - - if (g_rosterSettings.columns.Dist) - { - worker += ""; - } - - if (g_rosterSettings.columns.Azim) - { worker += ""; } - - if (g_rosterSettings.columns.CQz) - { worker += ""; } - - if (g_rosterSettings.columns.ITUz) - { worker += ""; } - - if (g_rosterSettings.columns.PX) - { worker += ""; } - - if (window.opener.g_callsignLookups.lotwUseEnable == true && g_rosterSettings.columns.LoTW) - { worker += ""; } - - if (window.opener.g_callsignLookups.eqslUseEnable == true && g_rosterSettings.columns.eQSL) - { worker += ""; } - - if (window.opener.g_callsignLookups.oqrsUseEnable == true && g_rosterSettings.columns.OQRS) - { worker += ""; } - - if (g_rosterSettings.columns.Spot) - { worker += ""; } - - if (g_rosterSettings.columns.Life) - { worker += ""; } - - if (g_rosterSettings.columns.OAMS) - { worker += ""; } - - if (g_rosterSettings.columns.Age) - { worker += ""; } - - return worker + return html } -function renderNormalRosterRow(callObj, showBands, showModes) +function renderNormalRosterRow(columns, callObj) { - let thisCall = callObj.DEcall; - let acks = window.opener.g_acknowledgedCalls; - let grid = callObj.grid.length > 1 ? callObj.grid.substr(0, 4) : "-"; + 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 geo = window.opener.g_worldGeoData[window.opener.g_dxccToGeoData[callObj.dxcc]]; - let cqzone = grid in window.opener.g_gridToCQZone ? window.opener.g_gridToCQZone[grid].join(", ") : "-"; - let ituzone = grid in window.opener.g_gridToITUZone ? window.opener.g_gridToITUZone[grid].join(", ") : "-"; + let html = ``; - let spotString = ""; - if (g_rosterSettings.columns.Spot && callObj.qrz == false) - { - spotString = getSpotString(callObj); - } + html = html + columns.map(column => renderEntryForColumn(column, callObj)).join("\n") - let thisHash = thisCall + callObj.band + callObj.mode; - let callStr = thisCall.formatCallsign() - if (acks[thisCall]) - { - callStr = `${callStr} ` - callObj.awardReason += ` - ${acks[thisCall].message}` - } + html += ""; - let worker = ""; - - worker += - ""; - - if (showBands) - { - worker += - ""; - } - if (showModes) - { - let color = "888888"; - if (callObj.mode in g_modeColors) - { color = g_modeColors[callObj.mode]; } - worker += - ""; - } - - worker += - ""; - if (g_rosterSettings.columns.Calling) - { - let lookString = callObj.CQ ? "name='CQ'" : "name='Calling'"; - worker += - ""; - } - if (g_rosterSettings.columns.Msg) - { worker += ""; } - - if (g_rosterSettings.columns.DXCC) - { - worker += - ""; - } - if (g_rosterSettings.columns.Flag) - { - worker += - ""; - } - if (g_rosterSettings.columns.State) - { - worker += - ""; - } - if (g_rosterSettings.columns.County) - { - worker += - ""; - } - if (g_rosterSettings.columns.Cont) - { - worker += - ""; - } - - if (g_rosterSettings.columns.dB) - { - worker += - ""; - } - if (g_rosterSettings.columns.Freq) - { worker += ""; } - if (g_rosterSettings.columns.DT) - { worker += ""; } - if (g_rosterSettings.columns.Dist) - { - worker += - ""; - } - if (g_rosterSettings.columns.Azim) - { - worker += - ""; - } - - if (g_rosterSettings.columns.CQz) - { - worker += - ""; - } - if (g_rosterSettings.columns.ITUz) - { - worker += - ""; - } - - if (g_rosterSettings.columns.PX) - { - worker += - ""; - } - - if ( - window.opener.g_callsignLookups.lotwUseEnable == true && - g_rosterSettings.columns.LoTW - ) - { - if (thisCall in window.opener.g_lotwCallsigns) - { - if (g_rosterSettings.maxLoTW < 27) - { - let months = (g_day - window.opener.g_lotwCallsigns[thisCall]) / 30; - if (months > g_rosterSettings.maxLoTW) - { - worker += - ""; - } - else - { - worker += - ""; - } - } - else - { - worker += - ""; - } - } - else worker += ""; - } - if ( - window.opener.g_callsignLookups.eqslUseEnable == true && - g_rosterSettings.columns.eQSL - ) - { - worker += - ""; - } - if ( - window.opener.g_callsignLookups.oqrsUseEnable == true && - g_rosterSettings.columns.OQRS - ) - { - worker += - ""; - } - - if (g_rosterSettings.columns.Spot) - { - worker += - ""; - } - if (g_rosterSettings.columns.Life) - { - worker += - ""; - } - - if (g_rosterSettings.columns.OAMS) - { - if (callObj.style.gt != 0) - { - if (callObj.reason.includes("oams")) - { - worker += - ""; - } - else - { - worker += - ""; - } - } - else worker += ""; - } - - if (g_rosterSettings.columns.Age) - { - worker += - ""; - } - - worker += ""; - - return worker; + return html; } function renderNormalRosterFooter() { - return "
CallsignBandModeGridCallingMsgDXCCFlagStateCountyContdBFreqDTDist(" + - window.opener.distanceUnit.value.toLowerCase() + ")AzimCQzITUzPXLoTWeQSLOQRSSpotLifeOAMSAge
" + - callStr + - "" + - callObj.band + - "" + callObj.mode + "" + - grid + - "" + - callObj.DXcall.formatCallsign() + - "" + callObj.msg + "" + - window.opener.g_dxccToAltName[callObj.dxcc] + "" + - (callObj.state ? callObj.state.substr(3) : "") + - "" + - (callObj.cnty - ? (callObj.qual ? "" : "¿ ") + - window.opener.g_cntyToCounty[callObj.cnty] + - (callObj.qual ? "" : " ?") - : "") + - "" + - (callObj.cont ? callObj.cont : "") + - "" + - callObj.RSTsent + - "" + callObj.delta + "" + callObj.dt + "" + - parseInt( - callObj.distance * - MyCircle.validateRadius(window.opener.distanceUnit.value) - ) + - "" + - parseInt(callObj.heading) + - "" + - callObj.cqza.join(",") + - "" + - callObj.ituza.join(",") + - "" + - (callObj.px ? callObj.px : "") + - "?" + - (thisCall in window.opener.g_eqslCallsigns ? "✔" : "") + - "" + - (thisCall in window.opener.g_oqrsCallsigns ? "✔" : "") + - "" + - spotString + - "" + - (timeNowSec() - callObj.life).toDHMS() + - "" + - (timeNowSec() - callObj.age).toDHMS() + - "
"; + return ""; } diff --git a/package.nw/lib/roster/renderRoster.js b/package.nw/lib/roster/renderRoster.js index fa14e57..1e7ffa7 100644 --- a/package.nw/lib/roster/renderRoster.js +++ b/package.nw/lib/roster/renderRoster.js @@ -1,12 +1,38 @@ function renderRoster(callRoster, rosterSettings) { - // eQSL - function - if (window.opener.g_callsignLookups.eqslUseEnable == true) useseQSLDiv.style.display = ""; - else useseQSLDiv.style.display = "none"; + let columnOverrides = { + Callsign: true, + Grid: true + } - // OQRS - function - if (window.opener.g_callsignLookups.oqrsUseEnable == true) usesOQRSDiv.style.display = ""; - else usesOQRSDiv.style.display = "none"; + if (window.opener.g_callsignLookups.eqslUseEnable == true) + { + useseQSLDiv.style.display = ""; + } + else + { + columnOverrides.eQSL = false; + useseQSLDiv.style.display = "none"; + } + + if (window.opener.g_callsignLookups.oqrsUseEnable == true) + { + usesOQRSDiv.style.display = ""; + } + else + { + columnOverrides.OQRS = false; + usesOQRSDiv.style.display = "none"; + } + + if (window.opener.g_callsignLookups.lotwUseEnable == true) + { + // Do nothing + } + else + { + columnOverrides.LoTW = false; + } // dealing with spots if (g_rosterSettings.columns.Spot == true) onlySpotDiv.style.display = ""; @@ -67,24 +93,23 @@ function renderRoster(callRoster, rosterSettings) window.document.title = `Call Roster: ${countParts.join(" • ")}`; - if (g_rosterSettings.compact == false) + if (g_rosterSettings.compact) { - visibleCallList.sort(r_sortFunction[g_rosterSettings.lastSortIndex]); - if (g_rosterSettings.lastSortReverse == 1) - { - visibleCallList.reverse(); - } + sortCallList(visibleCallList, "Age", false); } else { - // Age sort for now... make this happen Tag - visibleCallList.sort(r_sortFunction[6]).reverse(); + 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; - let worker = g_rosterSettings.compact ? renderCompactRosterHeaders() : renderNormalRosterHeaders(showBands, showModes) + columnOverrides.Band = showBands + columnOverrides.Mode = showModes + const rosterColumns = rosterColumnList(g_rosterSettings.columns, columnOverrides) + + let worker = g_rosterSettings.compact ? renderCompactRosterHeaders() : renderNormalRosterHeaders(rosterColumns) // Third loop: render all rows for (let x in visibleCallList) @@ -95,11 +120,9 @@ function renderRoster(callRoster, rosterSettings) if (callObj.shouldAlert == false && rosterSettings.onlyHits == true && callObj.qrz == false) { continue; } - let thisCall = callObj.DEcall; - - if (thisCall.match("^[A-Z][0-9][A-Z](/w+)?$")) + if (callObj.DEcall.match("^[A-Z][0-9][A-Z](/w+)?$")) { callObj.style.call = "class='oneByOne'"; } - if (thisCall == window.opener.g_instances[callObj.instance].status.DXcall) + if (callObj.DEcall == window.opener.g_instances[callObj.instance].status.DXcall) { if (window.opener.g_instances[callObj.instance].status.TxEnabled == 1) { @@ -111,7 +134,7 @@ function renderRoster(callRoster, rosterSettings) } } - worker += g_rosterSettings.compact ? renderCompactRosterRow(callObj) : renderNormalRosterRow(callObj, showBands, showModes) + worker += g_rosterSettings.compact ? renderCompactRosterRow(callObj) : renderNormalRosterRow(rosterColumns, callObj) } worker += g_rosterSettings.compact ? renderCompactRosterFooter() : renderNormalRosterFooter() diff --git a/package.nw/lib/roster/rosterColumnFunctions.js b/package.nw/lib/roster/rosterColumnFunctions.js new file mode 100644 index 0000000..51d64e0 --- /dev/null +++ b/package.nw/lib/roster/rosterColumnFunctions.js @@ -0,0 +1,95 @@ +function rosterColumnList(settings = {}, overrides = {}) +{ + return g_rosterSettings.columnOrder.filter(column => + { + return column && (settings[column] || overrides[column]) && !(overrides[column] === false) + }) +} + +function renderHeaderForColumn(column) +{ + const columnInfo = ROSTER_COLUMNS[column] + + let attrs = (columnInfo && columnInfo.tableHeader && columnInfo.tableHeader()) || {} + + attrs.html = attrs.html || column + + if (columnInfo.compare) + { + attrs.style = "cursor: pointer" + attrs.onClick = `setRosterSorting('${column}');` + } + + return renderRosterTableHTML("th", attrs) +} + +function renderEntryForColumn(column, entry) +{ + const columnInfo = ROSTER_COLUMNS[column] + + let attrs = (columnInfo && columnInfo.tableData && columnInfo.tableData(entry)) || {} + + return renderRosterTableHTML("td", attrs) +} + +function renderRosterTableHTML(tag, attrs) +{ + let innerHtml = attrs.html || "" + delete attrs.html + + let rawAttrs = attrs.rawAttrs || "" + delete attrs.rawAttrs + + let attrEntries = Object.entries(attrs).filter(kv => !!kv[1]) + + return `<${tag} ${rawAttrs} ${attrEntries.map((kv) => `${kv[0]}="${kv[1].replace(/"/g, """)}"`).join(" ")}>${innerHtml}` +} + +function setRosterSorting(column) +{ + if (g_rosterSettings.sortColumn === column) + { + g_rosterSettings.sortReverse = !g_rosterSettings.sortReverse + } + else + { + g_rosterSettings.sortColumn = column + g_rosterSettings.sortReverse = false + } + + writeRosterSettings(); + + window.opener.goProcessRoster(); +} + +function sortCallList(callList, sortColumn, sortReverse) +{ + const columnInfo = ROSTER_COLUMNS[sortColumn] + + callList.sort((columnInfo && columnInfo.compare) || ROSTER_COLUMNS.Age.compare) + + if (sortReverse) + { + callList.reverse() + } +} + +function validateRosterColumnOrder(columns) +{ + let correctedColumnOrder = (columns || DEFAULT_COLUMN_ORDER || []).slice(); + + DEFAULT_COLUMN_ORDER.forEach(column => + { + if (!correctedColumnOrder.includes(column)) correctedColumnOrder.push(column); + }) + correctedColumnOrder = correctedColumnOrder.filter(column => !!ROSTER_COLUMNS[column]) + + return correctedColumnOrder; +} + +function changeRosterColumnOrder(columns) +{ + g_rosterSettings.columnOrder = validateRosterColumnOrder(columns); + writeRosterSettings(); + window.opener.goProcessRoster(); +} diff --git a/package.nw/lib/roster/rosterColumns.js b/package.nw/lib/roster/rosterColumns.js new file mode 100644 index 0000000..81ae91a --- /dev/null +++ b/package.nw/lib/roster/rosterColumns.js @@ -0,0 +1,378 @@ +const DEFAULT_COLUMN_ORDER = [ + "Callsign", "Band", "Mode", "Grid", "Calling", "Msg", + "DXCC", "Flag", "State", "County", "Cont", + "dB", "Freq", "DT", "Dist", "Azim", + "CQz", "ITUz", "PX", + "LoTW", "eQSL", "OQRS", + "Life", "Spot", "OAMS", "Age" +] + +const LEGACY_COLUMN_SORT_ID = { + 0: "Callsign", + 1: "Grid", + 2: "dB", + 3: "DT", + 4: "Freq", + 5: "DXCC", + 7: "Dist", + 8: "Azim", + 9: "State", + 10: "Calling", + 11: "PX", + 12: "Life", + 13: "Spot", + 14: "OAMS", + 15: "County", + 16: "Cont" +} + +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; + if (aVal < bVal) return -1; + return 0; +} + +const callObjSimpleComparer = (attr) => getterSimpleComparer((elem) => elem.callObj[attr]) + +const callObjLocaleComparer = (attr) => (a, b) => +{ + if (a.callObj[attr] == null) return 1; + if (b.callObj[attr] == null) return -1; + return a.callObj[attr].localeCompare(b.callObj[attr]); +} + +const ROSTER_COLUMNS = { + + Callsign: { + compare: callObjLocaleComparer("DEcall"), + tableHeader: () => ({ align: "left" }), + tableData: (callObj) => + { + let attrs = { + title: callObj.awardReason, + name: "Callsign", + align: "left", + onClick: `initiateQso("${callObj.hash}")`, + rawAttrs: callObj.style.call, + html: html = callObj.DEcall.formatCallsign() + } + + let acks = window.opener.g_acknowledgedCalls; + if (acks[callObj.DEcall]) + { + attrs.html = `${attrs.html} ` + attrs.title = `${attrs.title} - ${acks[callObj.DEcall].message}` + } + + return attrs + } + }, + + Band: { + compare: false, + tableData: (callObj) => ({ + style: `color: #${window.opener.g_pskColors[callObj.band]};`, + html: callObj.band + }) + }, + + Mode: { + compare: false, + tableData: (callObj) => ({ + style: `color: #${g_modeColors[callObj.mode] || "888888"};`, + html: callObj.mode + }) + }, + + Grid: { + compare: callObjSimpleComparer("grid"), + tableData: (callObj) => ({ + rawAttrs: callObj.style.grid, + onClick: `centerOn("${callObj.grid4}")`, + html: callObj.grid4 + }) + }, + + Calling: { + compare: callObjLocaleComparer("DXcall"), + tableData: (callObj) => ({ + rawAttrs: callObj.style.calling, + name: callObj.CQ ? "CQ" : "Calling", + html: callObj.DXcall.formatCallsign() + }) + }, + + Msg: { + compare: callObjLocaleComparer("DXcall"), + tableData: (callObj) => ({ html: callObj.msg }) + }, + + DXCC: { + 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})`, + rawAttrs: callObj.style.dxcc, + html: window.opener.g_dxccToAltName[callObj.dxcc] + }) + }, + + Flag: { + compare: (a, b) => window.opener.myDxccCompare(a.callObj, b.callObj), + tableData: (callObj) => ({ + align: "center", + style: "margin:0; padding:0;", + html: `` + }) + }, + + State: { + compare: callObjSimpleComparer("state"), + tableData: (callObj) => ({ + align: "center", + rawAttrs: callObj.style.state, + html: callObj.state ? callObj.state.substr(3) : "" + }) + }, + + County: { + // Not sure why this comparison uses substring, but this is what the original code did + compare: getterSimpleComparer((elem) => elem.callObj.cnty && elem.callObj.cnty.substr(3)), + tableData: (callObj) => + { + let attrs = { + align: "center", + rawAttrs: callObj.style.cnty, + html: callObj.cnty ? window.opener.g_cntyToCounty[callObj.cnty] : "" + } + if (callObj.cnty && callObj.qual) + { + attrs.title = "ZIP Code matches multiple counties, click to do a full lookup" + attrs.onClick = `lookupZip("${callObj.DEcall}", "${callObj.grid4}")` + attrs.html = `¿ ${attrs.html} ?` + } + return attrs + } + }, + + Cont: { + compare: callObjSimpleComparer("cont"), + tableData: (callObj) => ({ + align: "center", + rawAttrs: callObj.style.cont, + html: callObj.cont ? callObj.cont : "" + }) + }, + + dB: { + compare: callObjSimpleComparer("RSTsent"), + tableData: (callObj) => ({ + style: "color:#DD44DD;", + html: `${callObj.RSTsent}` + }) + }, + + Freq: { + compare: callObjSimpleComparer("delta"), + tableData: (callObj) => ({ + style: "color: #00FF00;", + html: callObj.delta + }) + }, + + DT: { + compare: callObjSimpleComparer("dt"), + tableData: (callObj) => ({ + style: "color: #1E90FF;", + html: callObj.dt + }) + }, + + Dist: { + compare: callObjSimpleComparer("distance"), + tableHeader: () => ({ html: `Dist (${window.opener.distanceUnit.value.toLowerCase()})` }), + tableData: (callObj) => ({ + style: "color: cyan;", + html: Math.round(callObj.distance * MyCircle.validateRadius(window.opener.distanceUnit.value)) + }) + }, + + Azim: { + compare: callObjSimpleComparer("heading"), + tableData: (callObj) => ({ + style: "color: yellow;", + html: Math.round(callObj.heading) + }) + }, + + CQz: { + compare: false, + tableData: (callObj) => ({ + name: "CQz", + rawAttrs: callObj.style.cqz, + html: callObj.cqza.join(",") + }) + }, + + ITUz: { + compare: false, + tableData: (callObj) => ({ + name: "ITUz", + rawAttrs: callObj.style.ituz, + html: callObj.ituza.join(",") + }) + }, + + PX: { + compare: callObjSimpleComparer("px"), + tableData: (callObj) => ({ + rawAttrs: callObj.style.px, + html: callObj.px ? callObj.px : "" + }) + }, + + LoTW: { + compare: false, + tableData: (callObj) => + { + if (callObj.DEcall in window.opener.g_lotwCallsigns) + { + if (g_rosterSettings.maxLoTW < 27) + { + let months = (g_day - window.opener.g_lotwCallsigns[callObj.DEcall]) / 30; + if (months > g_rosterSettings.maxLoTW) + { + return { + style: "color: yellow;", + align: "center", + title: `Has not updated a QSO in ${Number(months).toYM()}`, + html: "?" + } + } + else + { + return { + style: "color: #0F0;", + align: "center", + title: `Last Upload ${ + window.opener.userDayString(window.opener.g_lotwCallsigns[callObj.DEcall] * 86400000) + }`, + html: "✔" + } + } + } + else + { + return { + style: "color: #0F0;", + align: "center", + title: `Last Upload ${ + window.opener.userDayString(window.opener.g_lotwCallsigns[callObj.DEcall] * 86400000) + }`, + html: "✔" + } + } + } + } + }, + + eQSL: { + compare: false, + tableData: (callObj) => ({ + style: "color: #0F0;", + align: "center", + html: (callObj.DEcall in window.opener.g_eqslCallsigns ? "✔" : "") + }) + }, + + OQRS: { + compare: false, + tableData: (callObj) => ({ + style: "color: #0F0;", + align: "center", + html: (callObj.DEcall in window.opener.g_oqrsCallsigns ? "✔" : "") + }) + }, + + Life: { + compare: callObjSimpleComparer("life"), + tableData: (callObj) => ({ + style: "color: #EEE;", + class: "lifeCol", + id: `lm${callObj.hash}`, + html: (timeNowSec() - callObj.life).toDHMS15() + }) + }, + + OAMS: { + tableHeader: () => ({ description: "Off-Air Message User" }), + compare: getterSimpleComparer((elem) => elem.callObj.gt != 0 ? 1 : 0), + tableData: (callObj) => + { + if (callObj.gt != 0) + { + if (callObj.reason.includes("oams")) + { + return { + align: "center", + style: "margin: 0; padding: 0; cursor: pointer; background-clip: content-box; box-shadow: 0 0 4px 4px inset #2222FFFF;", + onClick: `openChatToCid("${callObj.gt}")`, + html: "" + } + } + else + { + return { + align: "center", + style: "margin: 0; padding: 0; cursor: pointer;", + onClick: `openChatToCid("${callObj.gt}")`, + html: "" + } + } + } + } + }, + + Age: { + compare: callObjSimpleComparer("time"), + tableData: (callObj) => ({ + style: "color: #EEE;", + class: "timeCol", + id: `tm${callObj.hash}`, + title: (timeNowSec() - callObj.age).toDHMS(), + html: (timeNowSec() - callObj.age).toDHMS15() + }) + }, + + Spot: { + compare: (a, b) => + { + let cutoff = timeNowSec() - window.opener.g_receptionSettings.viewHistoryTimeSec; + + if (a.callObj.spot.when <= cutoff) return -1; + if (b.callObj.spot.when <= cutoff) return 1; + + let aSNR = Number(a.callObj.spot.snr); + let bSNR = Number(b.callObj.spot.snr); + + if (aSNR > bSNR) return 1; + if (aSNR < bSNR) return -1; + + if (a.callObj.spot.when > b.callObj.spot.when) return 1; + if (a.callObj.spot.when < b.callObj.spot.when) return -1; + + return 0; + }, + tableData: (callObj) => ({ + style: "color: #EEE;", + class: "spotCol", + id: `sp${callObj.hash}`, + html: getSpotString(callObj) + }) + } +}