// GridTracker Copyright © 2023 GridTracker.org // All rights reserved. // See LICENSE for more information. const fs = require("fs"); // var CR is in screen.js CR.developerMode = process.versions["nw-flavor"] == "sdk"; CR.callRoster = {}; CR.blockedCalls = {}; CR.blockedCQ = {}; CR.ignoredCQ = {}; CR.blockedDxcc = {}; CR.blockedGrid = {}; CR.blockedCQz = {}; CR.blockedITUz = {}; CR.scriptReport = {}; CR.worked = {}; CR.confirmed = {}; CR.modes = {}; CR.modes_phone = {}; CR.currentUSCallsigns = null; CR.currentUSState = ""; CR.currentDXCCs = -1; CR.callsignManifest = null; CR.rosterSettings = {}; CR.day = 0; CR.dayAsString = "0"; CR.menu = null; CR.columnMenu = null; CR.columnMembers = {}; CR.callMenu = null; CR.ageMenu = null; CR.callingMenu = null; CR.compactMenu = null; CR.menuItemForMoveLeftColumn = null; CR.menuItemForMoveRightColumn = null; CR.currentColumnName = null; CR.targetHash = ""; CR.dxccMenu = null; CR.targetDxcc = -1; CR.CQMenu = null; CR.GridMenu = null; CR.MsgMenu = null; CR.targetCQ = ""; CR.timerInterval = null; CR.awards = {}; CR.awardTypes = {}; CR.awardTracker = {}; CR.callsignDatabaseDXCC = {}; CR.callsignDatabaseUS = {}; CR.callsignDatabaseUSplus = {}; CR.modeColors = {}; CR.modeColors.FT4 = "1111FF"; CR.modeColors.FT8 = "11FF11"; CR.modeColors.JT4 = "EE1111"; CR.modeColors.JT9 = "7CFC00"; CR.modeColors.JT65 = "E550E5"; CR.modeColors.QRA64 = "FF00FF"; CR.modeColors.MSK144 = "4949FF"; CR.rosterTimeout = null; CR.rosterFocus = false; CR.watchers = null; CR.defaultSettings = { callsign: "all", hunting: "dxcc", huntNeed: "confirmed", requireGrid: false, animateCQGT: true, wantMaxDT: false, wantMinDB: false, wantMinFreq: false, wantMaxFreq: false, wantRRCQ: false, maxDT: 0.5, minDb: -25, minFreq: 0, maxFreq: 3500, noMyDxcc: false, onlyMyDxcc: false, cqOnly: false, usesLoTW: false, maxLoTW: 27, useseQSL: false, usesOQRS: false, onlySpot: false, allOnlyNew: false, realtime: true, wanted: { huntCallsign: false, huntGrid: true, huntDXCC: true, huntCQz: false, huntITUz: false, huntMarathon: false, huntState: true, huntCounty: false, huntCont: false, huntPX: false, huntPOTA: false, huntQRZ: true, huntOAMS: false, huntRegex: false, huntWatcher: false }, columns: { Callsign: true, Band: false, Mode: false, Calling: true, Rig: false, Grid: true, Msg: false, DXCC: true, Flag: true, State: true, County: false, POTA: false, Cont: false, dB: true, Freq: false, DT: false, Dist: false, Azim: false, CQz: false, ITUz: false, PX: false, LoTW: false, eQSL: false, OQRS: false, Spot: false, Life: false, OAMS: true, Age: true, UTC: true }, displayFilters: { brightness: 100, contrast: 100, saturate: 100, invert: 0, sepia: 0, huerotate: 0 }, reference: 0, controls: true, controlsExtended: true, compact: false, settingProfiles: false, sortColumn: "Age", sortReverse: true, clearRosterOnBandChange: true, rosterAlwaysOnTop: false, rosterDelayOnFocus: false, rosterDelayTime: 1500, rosterTime: 120, compactEntity: "DXCC", watchers: {} }; CR.def_displayFilters = { brightness: 100, contrast: 100, saturate: 100, invert: 0, sepia: 0, huerotate: 0 }; const LOGBOOK_LIVE_BAND_LIVE_MODE = "0"; const LOGBOOK_LIVE_BAND_MIX_MODE = "1"; const LOGBOOK_LIVE_BAND_DIGI_MODE = "2"; const LOGBOOK_MIX_BAND_LIVE_MODE = "3"; const LOGBOOK_MIX_BAND_MIX_MODE = "4"; const LOGBOOK_MIX_BAND_DIGI_MODE = "5"; const LOGBOOK_AWARD_TRACKER = "6"; const LAYERED_MODE_FOR = {} LAYERED_MODE_FOR[LOGBOOK_MIX_BAND_LIVE_MODE] = LOGBOOK_LIVE_BAND_LIVE_MODE; LAYERED_MODE_FOR[LOGBOOK_MIX_BAND_MIX_MODE] = LOGBOOK_LIVE_BAND_MIX_MODE; LAYERED_MODE_FOR[LOGBOOK_MIX_BAND_DIGI_MODE] = LOGBOOK_LIVE_BAND_DIGI_MODE; document.addEventListener("dragover", function (event) { event.preventDefault(); }); document.addEventListener("drop", function (event) { event.preventDefault(); }); window.addEventListener("message", receiveMessage, false); if (typeof localStorage.awardTracker == "undefined") { localStorage.awardTracker = "{}"; CR.rosterSettings = {}; writeRosterSettings(); } CR.awardTracker = JSON.parse(localStorage.awardTracker); // These two need to stay here because of an old bug. if (typeof localStorage.blockedCQz == "undefined") { localStorage.blockedCQz = "{}"; } if (typeof localStorage.blockedITUz == "undefined") { localStorage.blockedITUz = "{}"; } if (typeof localStorage.blockedGrid == "undefined") { localStorage.blockedGrid = "{}"; } if (typeof localStorage.blockedCalls != "undefined") { CR.blockedCalls = JSON.parse(localStorage.blockedCalls); CR.blockedCQ = JSON.parse(localStorage.blockedCQ); CR.blockedDxcc = JSON.parse(localStorage.blockedDxcc); CR.blockedGrid = JSON.parse(localStorage.blockedGrid); CR.blockedCQz = JSON.parse(localStorage.blockedCQz); CR.blockedITUz = JSON.parse(localStorage.blockedITUz); } else { localStorage.blockedCalls = "{}"; localStorage.blockedDxcc = "{}"; localStorage.blockedGrid = "{}"; localStorage.blockedCQz = "{}"; localStorage.blockedITUz = "{}"; localStorage.ignoredCQ = "{}"; } if (typeof localStorage.ignoredCQ == "undefined") { // CR.blockedCQ is deprecated, we don't clear it, we copy it // This is protection against them rolling back to a previous version // where keys are "CQ POTA from CountryString" let shouldStore = false; for (let key in CR.blockedCQ) { let test = key.split(" from "); if (test.length == 2) { if (test[1] == "All") { CR.ignoredCQ[test[0]] = true; } else { CR.ignoredCQ[test[0] + ":" + window.opener.GT.altNameToDXCC[test[1]]] = true; } shouldStore = true; } } if (shouldStore) storeBlocks(false); } else { CR.ignoredCQ = JSON.parse(localStorage.ignoredCQ); } function storeBlocks(render = true) { localStorage.blockedCalls = JSON.stringify(CR.blockedCalls); localStorage.ignoredCQ = JSON.stringify(CR.ignoredCQ); localStorage.blockedDxcc = JSON.stringify(CR.blockedDxcc); localStorage.blockedGrid = JSON.stringify(CR.blockedGrid); localStorage.blockedCQz = JSON.stringify(CR.blockedCQz); localStorage.blockedITUz = JSON.stringify(CR.blockedITUz); if (render) { renderIgnoresTab(); } } function storeAwardTracker() { localStorage.awardTracker = JSON.stringify(CR.awardTracker); } function loadSettings() { let readSettings = {}; if (typeof localStorage.rosterSettings != "undefined") { readSettings = JSON.parse(localStorage.rosterSettings); } CR.rosterSettings = deepmerge(CR.defaultSettings, readSettings); fixLegacySettings(); writeRosterSettings(); // Code reducder CR.watchers = CR.rosterSettings.watchers; } function fixLegacySettings() { // Not sure why, but Paul Traina added this settings cleanup in August 2020. if ("GT" in CR.rosterSettings.columns) delete CR.rosterSettings.columns.GT; // In January 2022, we refactored roster column sorting if (CR.rosterSettings.lastSortIndex) { CR.rosterSettings.sortColumn = LEGACY_COLUMN_SORT_ID[CR.rosterSettings.lastSortIndex] || "Age"; delete CR.rosterSettings.lastSortIndex; } // In January 2022, we refactored roster column sorting if (CR.rosterSettings.lastSortReverse) { CR.rosterSettings.sortReverse = CR.rosterSettings.lastSortReverse; delete CR.rosterSettings.lastSortReverse; } // In January 2022, we added a `columnOrder` setting, which we need to ensure always includes all columns CR.rosterSettings.columnOrder = validateRosterColumnOrder(CR.rosterSettings.columnOrder); } function writeRosterSettings() { for (let key in CR.rosterSettings.watchers) { CR.rosterSettings.watchers[key].test = null; } localStorage.rosterSettings = JSON.stringify(CR.rosterSettings); } function isKnownCallsignDXCC(dxcc) { if (dxcc in CR.callsignDatabaseDXCC) return true; return false; } function isKnownCallsignUS(dxcc) { if (dxcc in CR.callsignDatabaseUS) return true; return false; } function isKnownCallsignUSplus(dxcc) { if (dxcc in CR.callsignDatabaseUSplus) return true; return false; } function timeNowSec() { return parseInt(Date.now() / 1000); } function lockNewWindows() { if (typeof nw != "undefined") { let gui = require("nw.gui"); let win = gui.Window.get(); win.on("new-win-policy", function (frame, url, policy) { gui.Shell.openExternal(url); policy.ignore(); }); } } function hashMaker(start, callObj, reference) { if (reference == LOGBOOK_LIVE_BAND_LIVE_MODE) return `${start}${callObj.band}${callObj.mode}`; if (reference == LOGBOOK_AWARD_TRACKER) return `${start}${callObj.band}${callObj.mode}`; if (reference == LOGBOOK_LIVE_BAND_MIX_MODE) return `${start}${callObj.band}`; if (reference == LOGBOOK_LIVE_BAND_DIGI_MODE) return `${start}${callObj.band}dg`; if (reference == LOGBOOK_MIX_BAND_LIVE_MODE) return `${start}${callObj.mode}`; if (reference == LOGBOOK_MIX_BAND_MIX_MODE) return `${start}`; if (reference == LOGBOOK_MIX_BAND_DIGI_MODE) return `${start}dg`; return ""; } function rosterInFocus() { if (CR.rosterSettings.rosterDelayOnFocus) { CR.rosterFocus = true; } } function rosterNoFocus() { CR.rosterFocus = false; if (CR.rosterTimeout != null) { nodeTimers.clearTimeout(CR.rosterTimeout); CR.rosterTimeout = null; viewRoster(); } } function processRoster(roster) { CR.callRoster = roster; if (CR.rosterTimeout != null) { nodeTimers.clearTimeout(CR.rosterTimeout); CR.rosterTimeout = null; } if (CR.rosterFocus) { CR.rosterTimeout = nodeTimers.setTimeout(viewRoster, CR.rosterSettings.rosterDelayTime); rosterDelayDiv.style.display = "inline-block"; } else { viewRoster(); } } function viewRoster() { CR.rosterTimeout = null; rosterDelayDiv.style.display = "none"; let rosterSettings = prepareRosterSettings(); processRosterFiltering(CR.callRoster, rosterSettings); processRosterHunting(CR.callRoster, rosterSettings, CR.awardTracker); renderRoster(CR.callRoster, rosterSettings); sendAlerts(CR.callRoster, rosterSettings); } function realtimeRoster() { let now = timeNowSec(); CR.day = parseInt(now / 86400); CR.dayAsString = String(CR.day); if (CR.rosterSettings.realtime == false) return; let timeCols = document.getElementsByClassName("timeCol"); for (const x in timeCols) { if ((typeof timeCols[x].id != "undefined") && (typeof CR.callRoster[timeCols[x].id.substr(2)] != "undefined")) { let when = now - CR.callRoster[timeCols[x].id.substr(2)].callObj.age; timeCols[x].innerHTML = toDHMS(when); } } let lifeCols = document.getElementsByClassName("lifeCol"); for (const x in lifeCols) { if ((typeof lifeCols[x].id != "undefined") && (typeof CR.callRoster[lifeCols[x].id.substr(2)] != "undefined")) { let when = now - CR.callRoster[lifeCols[x].id.substr(2)].callObj.life; lifeCols[x].innerHTML = toDHMS(when); } } if (CR.rosterSettings.columns.Spot) { let spotCols = document.getElementsByClassName("spotCol"); for (const x in spotCols) { if ((typeof spotCols[x].id != "undefined") && (typeof CR.callRoster[spotCols[x].id.substr(2)] != "undefined")) { spotCols[x].innerHTML = getSpotString(CR.callRoster[spotCols[x].id.substr(2)].callObj); } } } } function getSpotString(callObj) { let result = " "; if (callObj.spot && callObj.spot.when > 0) { when = timeNowSec() - callObj.spot.when; if (when <= window.opener.GT.receptionSettings.viewHistoryTimeSec) { result = toDHM(parseInt(when)); } } if (result != " ") result += " / " + callObj.spot.snr; return result; } function openChatToCid(cid) { window.opener.showMessaging(true, cid); } function initiateQso(thisHash) { window.opener.initiateQso(thisHash); } function callLookup(thisHash, grid) { window.opener.startLookup( CR.callRoster[thisHash].DEcall, CR.callRoster[thisHash].grid ); } function callingLookup(thisHash, grid) { let thisCall = CR.callRoster[thisHash].DXcall; window.opener.startLookup(thisCall, grid); } function callGenMessage(thisHash, grid) { let thisCall = CR.callRoster[thisHash].DEcall; let instance = CR.callRoster[thisHash].callObj.instance; window.opener.startGenMessages(thisCall, grid, instance); } function callingGenMessage(thisHash, grid) { let thisCall = CR.callRoster[thisHash].DXcall; let instance = CR.callRoster[thisHash].callObj.instance; window.opener.startGenMessages(thisCall, grid, instance); } function centerOn(grid) { window.opener.centerOn(grid); } function instanceChange(what) { window.opener.GT.instances[what.id].crEnable = what.checked; viewRoster(); } function updateInstances() { if (window.opener.GT.instancesIndex.length > 1) { let instances = window.opener.GT.instances; let worker = ""; let keys = Object.keys(instances).sort(); for (const key in keys) { let inst = keys[key]; let sp = inst.split(" - "); let shortInst = sp[sp.length - 1].substring(0, 18); let color = "blue"; if (instances[inst].open == false) { color = "purple"; } worker += `
` + ` ${shortInst}
` } instancesDiv.innerHTML = worker; instancesWrapper.style.display = ""; } else { instancesDiv.innerHTML = ""; instancesWrapper.style.display = "none"; } } function processStatus(newMessage) { if (newMessage.Transmitting == 0) { // Not Transmitting if (newMessage.Decoding == 1) { // Decoding txrxdec.style.backgroundColor = "Blue"; txrxdec.style.borderColor = "Cyan"; txrxdec.innerHTML = "DECODE"; } else { txrxdec.style.backgroundColor = "Green"; txrxdec.style.borderColor = "GreenYellow"; txrxdec.innerHTML = "RECEIVE"; } } else { txrxdec.style.backgroundColor = "Red"; txrxdec.style.borderColor = "Orange"; txrxdec.innerHTML = "TRANSMIT"; } } function toTitleCase(str) { return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } function newOption(value, text) { if (typeof text == "undefined") text = value; let option = document.createElement("option"); option.value = value; option.text = text; return option; } function createSelectOptions( selectElementString, selectNameDefault, forObject, altName = null, defaultValue = null, checkSponsor = null ) { let selector = document.getElementById(selectElementString); selector.innerHTML = ""; let option = document.createElement("option"); option.value = defaultValue; option.text = selectNameDefault; option.selected = true; option.disabled = true; option.style.display = "none"; selector.appendChild(option); let obj = null; if (forObject) { obj = Object.keys(forObject).sort(); } for (const k in obj) { let opt = obj[k]; let option = document.createElement("option"); option.value = opt; option.text = altName ? forObject[opt][altName] : opt; if (checkSponsor && opt + "-" + checkSponsor in CR.awardTracker) { option.disabled = true; } selector.appendChild(option); } } function awardSponsorChanged() { awardName.style.display = ""; createSelectOptions( "awardName", "Select Award", CR.awards[awardSponsor.value].awards, "name", null, awardSponsor.value ); } function awardNameChanged() { let awardToAdd = newAwardTrackerObject( awardSponsor.value, awardName.value, true ); let hash = awardToAdd.name + "-" + awardToAdd.sponsor; if (!(hash in CR.awardTracker)) { CR.awardTracker[hash] = awardToAdd; storeAwardTracker(); processAward(hash); updateAwardList(hash); viewRoster(); } createSelectOptions( "awardName", "Select Award", CR.awards[awardToAdd.sponsor].awards, "name", null, awardToAdd.sponsor ); } function updateAwardList(target = null) { let worker = ""; worker += ""; worker += ""; worker += ""; worker += "
NameAwardTrack
"; AwardWantedList.innerHTML = worker; let keys = Object.keys(CR.awardTracker).sort(); for (const key in keys) { let award = CR.awardTracker[keys[key]]; let rule = CR.awards[award.sponsor].awards[award.name].rule; let row = awardTable.insertRow(); row.id = keys[key]; let baseAward = false; let baseCount = 0; let endorseCount = 0; let endorseTotal = 0; let allEndorse = false; let tooltip = CR.awards[award.sponsor].awards[award.name].tooltip + " (" + CR.awards[award.sponsor].sponsor + ")\n"; tooltip += toTitleCase(award.test.qsl_req) + " QSO\n"; for (const mode in award.comp.counts) { tooltip += mode + "\n"; for (const count in award.comp.counts[mode]) { endorseTotal++; if (award.comp.counts[mode][count].per == 100) { baseAward = true; endorseCount++; } if (award.comp.counts[mode][count].num > baseCount) { baseCount = award.comp.counts[mode][count].num; } tooltip += "\t" + award.comp.counts[mode][count].num + "/" + count + " (" + award.comp.counts[mode][count].per + "%)\n"; let wrk = ""; if (Object.keys(award.comp.endorse).length > 0) { for (const band in award.comp.endorse[mode]) { endorseTotal++; if (award.comp.endorse[mode][band][count] == true) { endorseCount++; wrk += band + " "; } } } if (wrk.length > 0) { tooltip += "\t\t" + wrk + "\n"; } } } if (baseCount > 0 && endorseCount == endorseTotal) allEndorse = true; let cell = createCellHtml(row, award.name + " - " + award.sponsor); cell.style.textAlign = "left"; cell.style.color = "lightblue"; createCellHtml( row, (allEndorse ? "" : baseAward ? "" : baseCount > 0 ? "" : ""), tooltip ); createCell(row, "enable", award.enable, award.enable, "Toggle Tracking", true); createCellHtml(row, ""); } } function deleteAwardTracker(sender) { let id = sender.parentNode.parentNode.id; delete CR.awardTracker[id]; storeAwardTracker(); resetAwardAdd(); updateAwardList(); viewRoster(); } function awardCheckboxChanged(sender) { let awardId = sender.target.parentNode.parentNode.id; CR.awardTracker[sender.target.parentNode.parentNode.id][sender.target.name] = sender.target.checked; storeAwardTracker(); viewRoster(); } function awardValueChanged(sender) { let awardId = sender.target.parentNode.parentNode.id; CR.awardTracker[sender.target.parentNode.parentNode.id][sender.target.name] = sender.target.value; storeAwardTracker(); viewRoster(); } function createCell( row, target, value, data = null, title = null, checkbox = false ) { let cell = row.insertCell(); if (data == null) cell.innerHTML = value; if (title) cell.title = title; if (checkbox) { let x = document.createElement("INPUT"); x.setAttribute("type", "checkbox"); x.checked = value; x.name = target; x.addEventListener("change", awardCheckboxChanged); cell.appendChild(x); } else if (data) { cell.appendChild(createAwardSelector(cell, target, value, data)); } return cell; } function createCellHtml(row, html, title = null) { let cell = row.insertCell(); cell.innerHTML = html; if (title) cell.title = title; return cell; } function createAwardSelector(cell, target, value, forObject) { let selector = document.createElement("select"); selector.name = target; selector.value = value; selector.disabled = forObject.length == 1; selector.style.margin = "0px"; selector.style.padding = "1px"; if (selector.disabled) selector.style.cursor = "auto"; selector.addEventListener("change", awardValueChanged); for (const opt in forObject) { let option = document.createElement("option"); option.value = forObject[opt]; if (option.value == "Phone" || option.value == "CW") option.disabled = true; option.text = forObject[opt]; selector.appendChild(option); } return selector; } function resetAwardAdd() { awardName.style.display = "none"; createSelectOptions("awardName", "Select Award", null); createSelectOptions("awardSponsor", "Select Sponsor", CR.awards, "sponsor"); } function openAwardPopup() { awardHunterDiv.style.display = ""; resetAwardAdd(); } function closeAwardPopup() { awardHunterDiv.style.display = "none"; resetAwardAdd(); } function toggleMoreControls() { CR.rosterSettings.controlsExtended = !CR.rosterSettings.controlsExtended; writeRosterSettings(); setVisual(); } function setVisual() { huntNeed.style.display = "none"; stateSelect.style.display = "none"; DxccSingleSelect.style.display = "none"; if (CR.rosterSettings.controls) { if (CR.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 if (referenceNeed.value == LOGBOOK_AWARD_TRACKER) { HuntModeControls.style.display = "none"; CallsignsControls.style.display = "none"; AwardTrackerControls.style.display = ""; updateAwardList(); } else { for (const key in CR.rosterSettings.wanted) { if (document.getElementById(key)) { document.getElementById(key).checked = CR.rosterSettings.wanted[key]; } } AwardTrackerControls.style.display = "none"; HuntModeControls.style.display = ""; huntMode.style.display = ""; CallsignsControls.style.display = ""; closeAwardPopup(); if (callsignNeed.value == "all" || callsignNeed.value == "hits") { huntingMatrixDiv.style.display = ""; huntNeed.style.display = ""; huntMode.style.display = "none"; } else { huntingMatrixDiv.style.display = "none"; huntMode.style.display = ""; if (huntMode.value != "callsign" && huntMode.value != "usstate" && huntMode.value != "dxccs") { huntNeed.style.display = ""; } if (huntMode.value == "usstate") { stateSelect.style.display = ""; } if (huntMode.value == "usstates") { huntNeed.style.display = ""; } if (huntMode.value == "dxccs") { DxccSingleSelect.style.display = ""; } } } if (window.opener.GT.callsignLookups.lotwUseEnable == true) { usesLoTWDiv.style.display = ""; } else { usesLoTWDiv.style.display = "none"; } if (window.opener.GT.callsignLookups.eqslUseEnable == true) { useseQSLDiv.style.display = ""; } else { useseQSLDiv.style.display = "none"; } if (window.opener.GT.callsignLookups.oqrsUseEnable == true) { usesOQRSDiv.style.display = ""; } else { usesOQRSDiv.style.display = "none"; } if (CR.rosterSettings.columns.Spot == true) { onlySpotDiv.style.display = ""; } else { onlySpotDiv.style.display = "none"; } if (CR.rosterSettings.callsign == "all" || CR.rosterSettings.callsign == "hits") { allOnlyNewDiv.style.display = ""; } else { allOnlyNewDiv.style.display = "none"; } rosterBody.style.display = "block"; resize(); } function wantedChanged(element) { CR.rosterSettings.wanted[element.id] = element.checked; if (element.checked == true) { let t = element.id.replace("hunt", ""); if (t in CR.rosterSettings.columns) { CR.rosterSettings.columns[t] = true; if (t in CR.columnMembers) { CR.columnMembers[t].checked = true; } } } setVisual(); writeRosterSettings(); CR.scriptReport = Object(); for (const callHash in window.opener.GT.callRoster) { window.opener.GT.callRoster[callHash].callObj.alerted = false; } viewRoster(); } function valuesChanged() { CR.rosterSettings.callsign = callsignNeed.value; CR.rosterSettings.hunting = huntMode.value; CR.rosterSettings.huntNeed = huntNeed.value; CR.rosterSettings.requireGrid = wantGrid.checked; CR.rosterSettings.wantMaxDT = wantMaxDT.checked; CR.rosterSettings.wantMinDB = wantMinDB.checked; CR.rosterSettings.wantMinFreq = wantMinFreq.checked; CR.rosterSettings.wantMaxFreq = wantMaxFreq.checked; CR.rosterSettings.wantRRCQ = wantRRCQ.checked; maxDTView.innerHTML = CR.rosterSettings.maxDT = maxDT.value; minDbView.innerHTML = CR.rosterSettings.minDb = minDb.value; minFreqView.innerHTML = CR.rosterSettings.minFreq = minFreq.value; maxFreqView.innerHTML = CR.rosterSettings.maxFreq = maxFreq.value; CR.rosterSettings.maxLoTW = maxLoTW.value; maxLoTWView.innerHTML = CR.rosterSettings.maxLoTW < 27 ? toYM(Number(CR.rosterSettings.maxLoTW)) : ""; CR.rosterSettings.maxLoTW = maxLoTW.value; CR.rosterSettings.cqOnly = cqOnly.checked; CR.rosterSettings.noMyDxcc = noMyDxcc.checked; CR.rosterSettings.onlyMyDxcc = onlyMyDxcc.checked; CR.rosterSettings.usesLoTW = usesLoTW.checked; CR.rosterSettings.useseQSL = useseQSL.checked; CR.rosterSettings.usesOQRS = usesOQRS.checked; CR.rosterSettings.onlySpot = onlySpot.checked; CR.rosterSettings.reference = referenceNeed.value; CR.rosterSettings.allOnlyNew = allOnlyNew.checked; writeRosterSettings(); CR.scriptReport = Object(); for (const callHash in window.opener.GT.callRoster) { window.opener.GT.callRoster[callHash].callObj.alerted = false; } setVisual(); } function loadFilterSettings() { let filters = ""; for (const filter in CR.rosterSettings.displayFilters) { let slider = document.getElementById("filter" + filter + "Slider"); if (slider) { slider.value = CR.rosterSettings.displayFilters[filter]; let td = document.getElementById("filter" + filter + "Td"); if (filter != "huerotate") { td.innerHTML = slider.value + "%"; filters += filter + "(" + slider.value + "%) "; } else { td.innerHTML = slider.value + " deg"; filters += "hue-rotate(" + slider.value + "deg) "; } } } document.documentElement.style.filter = filters; } function filtersChanged() { for (const filter in CR.rosterSettings.displayFilters) { let slider = document.getElementById("filter" + filter + "Slider"); if (slider) { CR.rosterSettings.displayFilters[filter] = slider.value; } else { // no longer a filter, get rid of it delete CR.rosterSettings.displayFilters[filter]; } } loadFilterSettings(); } function resetFilters() { for (const filter in CR.rosterSettings.displayFilters) { CR.rosterSettings.displayFilters[filter] = CR.def_displayFilters[filter]; } loadFilterSettings(); } function getBuffer(file_url, callback, flag, mode, port, cookie) { let url = require("url"); let http = require(mode); let fileBuffer = null; let options = null; if (cookie != null) { options = { host: url.parse(file_url).host, // eslint-disable-line node/no-deprecated-api port: port, path: url.parse(file_url).path, // eslint-disable-line node/no-deprecated-api headers: { Cookie: cookie } }; } else { options = { host: url.parse(file_url).host, // eslint-disable-line node/no-deprecated-api port: port, path: url.parse(file_url).path // eslint-disable-line node/no-deprecated-api }; } http.get(options, function (res) { // let fsize = res.headers["content-length"]; let cookies = null; if (typeof res.headers["set-cookie"] != "undefined") { cookies = res.headers["set-cookie"]; } res .on("data", function (data) { if (fileBuffer == null) fileBuffer = data; else fileBuffer += data; }) .on("end", function () { if (typeof callback == "function") { // Call it, since we have confirmed it is callable callback(fileBuffer, flag, cookies); } }) .on("error", function () {}); }); } function callsignResult(buffer, flag) { let rawData = JSON.parse(buffer); CR.currentUSState = flag; CR.currentUSCallsigns = Object(); for (const key in rawData.c) CR.currentUSCallsigns[rawData.c[key]] = true; viewRoster(); } function stateChangedValue(what) { if (CR.currentUSState != stateSelect.value && stateSelect.value != "") { CR.currentUSState = stateSelect.value; if (window.opener.GT.mapSettings.offlineMode == false) { let callState = CR.currentUSState.replace("CN-", ""); getBuffer( "https://storage.googleapis.com/gt_app/callsigns/" + callState + ".callsigns.json", callsignResult, CR.currentUSState, "http", 80 ); } else { viewRoster(); CR.currentUSState = ""; CR.currentUSCallsigns = null; stateSelect.value = ""; return; } } if (stateSelect.value == "") { CR.currentUSState = ""; CR.currentUSCallsigns = null; viewRoster(); } } function DXCCsChangedValue(what) { CR.currentDXCCs = DxccSingleSelect.value; viewRoster(); } function initSelectors() { for (const column in ROSTER_COLUMNS) { if (column != "Callsign") { let option = newOption(column, column); if (column == CR.rosterSettings.compactEntity) { option.selected = true; } compactEntitySelect.appendChild(option); } } let items = Object.keys(window.opener.GT.dxccToAltName).sort(function (a, b) { return window.opener.GT.dxccToAltName[a].localeCompare( window.opener.GT.dxccToAltName[b] ); }); for (const i in items) { let key = items[i]; if (window.opener.GT.dxccInfo[key].geo != "deleted") { let option = document.createElement("option"); option.value = key; option.text = window.opener.GT.dxccToAltName[key] + " (" + window.opener.GT.dxccInfo[key].pp + ")"; DxccSingleSelect.appendChild(option); // Note: do not use cloneNode on elements/nodes that have ids ignoreCqDxccSelect.appendChild(option.cloneNode(true)); ignoreDxccSelect.appendChild(option.cloneNode(true)); } } DxccSingleSelect.oninput = DXCCsChangedValue; items = Object.keys(window.opener.GT.cqZones).sort(); for (const i in items) { let key = items[i]; let option = document.createElement("option"); option.value = key; option.text = key + " - " + window.opener.GT.cqZones[key].name; ignoreCqzSelect.appendChild(option); } items = Object.keys(window.opener.GT.ituZones).sort(); for (const i in items) { let key = items[i]; let option = document.createElement("option"); option.value = key; option.text = key; ignoreItuzSelect.appendChild(option); } CR.ignoreTypeInputs = {}; CR.ignoreTypeInputs.Callsign = ignoreCallsignValue; CR.ignoreTypeInputs.Grid = ignoreGridValue; CR.ignoreTypeInputs.CQ = ignoreCqDiv; CR.ignoreTypeInputs.DXCC = ignoreDxccSelect; CR.ignoreTypeInputs.CQz = ignoreCqzSelect; CR.ignoreTypeInputs.ITUz = ignoreItuzSelect; ignoreTypeChanged("Callsign"); watcherTypeChanged("Callsign"); } function hideIgnoreElements() { ignoreCallsignValue.style.display = "none"; ignoreGridValue.style.display = "none"; ignoreCqDiv.style.display = "none"; ignoreDxccSelect.style.display = "none"; ignoreCqzSelect.style.display = "none"; ignoreItuzSelect.style.display = "none"; } CR.ignoreType = "Callsign"; function ignoreTypeChanged(ignoreTypeValue) { hideIgnoreElements(); CR.ignoreTypeInputs[ignoreTypeValue].style.display = ""; if (CR.ignoreType != ignoreTypeValue) { ingnoreAddResultLabel.innerHTML = ""; CR.ignoreType = ignoreTypeValue; } ValidateTextInput(ignoreCallsignValue); gridInputValidate(ignoreGridValue); ValidateTextInput(ignoreCqCallsignValue); } function gridInputValidate(element) { element.value = element.value.toUpperCase().replace(/[^A-Z0-9/]+/g, "").substr(0, 4); if (!element.value.match(GRID_REGEXP)) { element.style.color = "#000"; element.style.backgroundColor = "yellow"; } else { element.style.color = ""; element.style.backgroundColor = ""; } } function addNewIgnore() { if (CR.ignoreType == "Callsign") { if (ValidateTextInput(ignoreCallsignValue, ingnoreAddResultLabel)) { ignoreCallsign(ignoreCallsignValue.value); } } else if (CR.ignoreType == "CQ") { if (ValidateTextInput(ignoreCqCallsignValue, ingnoreAddResultLabel)) { ignoreCQ("CQ " + ignoreCqCallsignValue.value, ignoreCqDxccSelect.value); } } else if (CR.ignoreType == "DXCC") { ignoreDxcc(ignoreDxccSelect.value); } else if (CR.ignoreType == "Grid") { ignoreGrid(ignoreGridValue.value); } else if (CR.ignoreType == "CQz") { ignoreCQz(ignoreCqzSelect.value) } else if (CR.ignoreType == "ITUz") { ignoreITUz(ignoreItuzSelect.value); } } function manifestResult(buffer, flag) { CR.callsignManifest = JSON.parse(buffer); let newSelect = document.getElementById("stateSelect"); for (const key in CR.callsignManifest.cnt) { let option = document.createElement("option"); if (window.opener.GT.enums[key]) { option.value = key; option.text = window.opener.GT.enums[key]; } else { option.value = "CN-" + key; option.text = window.opener.GT.enums["CN-" + key]; } newSelect.appendChild(option); } newSelect.oninput = stateChangedValue; } function receiveMessage(event) {} CR.tracker = {}; function updateWorked() { CR.worked = window.opener.GT.tracker.worked; CR.confirmed = window.opener.GT.tracker.confirmed; CR.modes = window.opener.GT.modes; CR.modes_phone = window.opener.GT.modes_phone; CR.tracker = window.opener.GT.tracker; processAllAwardTrackers(); } function deleteCallsignIgnore(key) { delete CR.blockedCalls[key]; storeBlocks(); viewRoster(); } function ignoreCallsign(callsign) { CR.blockedCalls[callsign] = true; storeBlocks(); viewRoster(); } function ignoreDxcc(dxcc) { CR.blockedDxcc[dxcc] = true; storeBlocks(); viewRoster(); } function ignoreGrid(grid) { CR.blockedGrid[grid] = true; storeBlocks(); viewRoster(); } function ignoreCQ(cq, dxcc) { if (dxcc > 0) { CR.ignoredCQ[cq + ":" + dxcc] = true; } else { CR.ignoredCQ[cq] = true; } storeBlocks(); viewRoster(); } function ignoreCQz(cqz) { CR.blockedCQz[cqz] = true; storeBlocks(); viewRoster(); } function ignoreITUz(ituz) { CR.blockedITUz[ituz] = true; storeBlocks(); viewRoster(); } function deleteDxccIgnore(key) { delete CR.blockedDxcc[key]; storeBlocks(); viewRoster(); } function deleteGridIgnore(key) { delete CR.blockedGrid[key]; storeBlocks(); viewRoster(); } function deleteCQIgnore(key) { delete CR.ignoredCQ[key]; storeBlocks(); viewRoster(); } function deleteCQzIgnore(key) { delete CR.blockedCQz[key]; storeBlocks(); viewRoster(); } function deleteITUzIgnore(key) { delete CR.blockedITUz[key]; storeBlocks(); viewRoster(); } function clearAllCallsignIgnores() { CR.blockedCalls = Object(); storeBlocks(); viewRoster(); } function clearAllDxccIgnores() { CR.blockedDxcc = Object(); storeBlocks(); viewRoster(); } function clearAllGridIgnores() { CR.blockedGrid = Object(); storeBlocks(); viewRoster(); } function clearAllCQIgnores() { CR.ignoredCQ = Object(); storeBlocks(); viewRoster(); } function clearAllCQzIgnores() { CR.blockedCQz = Object(); storeBlocks(); viewRoster(); } function clearAllITUzIgnores() { CR.blockedITUz = Object(); storeBlocks(); viewRoster(); } function openSettings() { openInfoTab("generalbox", "generalSettingsDiv"); settingsDiv.style.display = "inline-block"; } function openWatcher() { openInfoTab("watcherbox", "watcherBoxDiv", openWathcherTab); settingsDiv.style.display = "inline-block"; } function openExceptions() { openInfoTab("exceptionsbox", "exceptionsBoxDiv"); settingsDiv.style.display = "inline-block"; } function openIgnores() { openInfoTab("ingoresbox", "ignoresBoxDiv", renderIgnoresTab); settingsDiv.style.display = "inline-block"; } function openColumns() { openInfoTab("columnsbox", "columnsBoxDiv", renderColumnsTab); settingsDiv.style.display = "inline-block"; } function closeSettings() { settingsDiv.style.display = "none"; } function renderIgnoresTab() { let worker = ""; let clearString = "none"; if (Object.keys(CR.blockedCalls).length > 0) { clearString = "Clear All"; worker += "
" + clearString + ""; Object.keys(CR.blockedCalls) .sort() .forEach(function (key, i) { worker += ""; }); worker += "
Callsigns
" + key + "
"; } if (Object.keys(CR.ignoredCQ).length > 0) { clearString = "Clear All"; worker += "
" + clearString + ""; Object.keys(CR.ignoredCQ) .sort() .forEach(function (rawKey, i) { let split = rawKey.split(":"); let key = split[0]; let dxcc = -1; if (split.length == 2) dxcc = parseInt(split[1]); worker += ""; }); worker += "
CQ
" + key + " from " + (dxcc == -1 ? "All" : window.opener.GT.dxccToAltName[dxcc]) + "
"; } if (Object.keys(CR.blockedDxcc).length > 0) { clearString = "Clear All"; worker += "
" + clearString + ""; Object.keys(CR.blockedDxcc) .sort() .forEach(function (key, i) { worker += ""; }); worker += "
DXCC
" + window.opener.GT.dxccToAltName[key] + " (" + window.opener.GT.dxccInfo[key].pp + ")
"; } if (Object.keys(CR.blockedGrid).length > 0) { clearString = "Clear All"; worker += "
" + clearString + ""; Object.keys(CR.blockedGrid) .sort() .forEach(function (key, i) { worker += ""; }); worker += "
Grid
" + key + "
"; } if (Object.keys(CR.blockedCQz).length > 0) { clearString = "Clear All"; worker += "
" + clearString + ""; Object.keys(CR.blockedCQz) .sort() .forEach(function (key, i) { worker += ""; }); worker += "
CQ Zones
" + key + "
"; } if (Object.keys(CR.blockedITUz).length > 0) { clearString = "Clear All"; worker += "
" + clearString + ""; Object.keys(CR.blockedITUz) .sort() .forEach(function (key, i) { worker += ""; }); worker += "
ITU Zones
" + key + "
"; } ignoresEditView.innerHTML = worker; ignoresBoxDiv.style.height = (window.innerHeight - 50) + "px"; let elems = document.getElementsByClassName("ignoresTables"); for (let x = 0; x < elems.length; x++) { let height = 110; if (elems[x].offsetHeight > window.innerHeight - height) { elems[x].style.height = (window.innerHeight - height) + "px"; } } } function renderColumnsTab() { let worker = ""; let renderHeight = 230; worker += "
"; worker += "
Enabled
"; const columns = rosterColumnList(CR.rosterSettings.columns, { Callsign: false }); let enabled = {}; for (let x = 0; x != columns.length; x++) { let column = columns[x]; enabled[column] = true; worker += "
"; if (x > 0) { worker += `
`; } else { worker += "
 
" } worker += `
` worker += "
" + column + "
"; if (x + 1 != columns.length) { worker += `
`; } else { worker += "
 
"; } worker += "
"; } worker += "
"; worker += "
"; let available = [...CR.rosterSettings.columnOrder]; available.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1); for (let x = 0; x != available.length; x++) { let column = available[x]; if (column == "Callsign") continue; if (column == "eQSL" && window.opener.GT.callsignLookups.eqslUseEnable == false) continue; if (column == "LoTW" && window.opener.GT.callsignLookups.lotwUseEnable == false) continue; if (column == "OQRS" && window.opener.GT.callsignLookups.oqrsUseEnable == false) continue; if (!(column in enabled)) { worker += "
"; worker += `
`; worker += "
" + column + "
"; worker += "
"; } } worker += "
"; columnsEditView.innerHTML = worker; renderHeight = Math.max(window.innerHeight - 200, 150); columnEnabledView.style.height = renderHeight + "px"; columnAvailableView.style.height = renderHeight + "px"; } function onMyKeyDown(event) { if (event.keyCode == 27) { closeSettings(); } if (event.ctrlKey == true) { if (event.code == "KeyS") { openSettings(); } else if (event.code == "KeyW" || event.code == "KeyO") { openWatcher(); } else if (event.code == "KeyE") { openExceptions(); } else if (event.code == "KeyI") { openIgnores(); } else if (event.code == "KeyC") { openColumns(); } else if (event.code == "KeyR") { resetFilters(); } } } function blurOnEnter(ele) { if (event.key == "Enter") { ele.blur(); } } function resize() { if (ignoresBoxDiv.style.display != "none") { renderIgnoresTab(); } if (columnsBoxDiv.style.display != "none") { renderColumnsTab(); } wantRenderWatchersTab(); window.opener.goProcessRoster(); } function init() { window.opener.GT.rosterInitialized = true; CR.callsignDatabaseDXCC = window.opener.GT.callsignDatabaseDXCC; CR.callsignDatabaseUS = window.opener.GT.callsignDatabaseUS; CR.callsignDatabaseUSplus = window.opener.GT.callsignDatabaseUSplus; loadAwardJson(); updateWorked(); // addAllAwards(); window.addEventListener("message", receiveMessage, false); lockNewWindows(); if (window.opener.GT.mapSettings.offlineMode == false) { getBuffer( "https://storage.googleapis.com/gt_app/callsigns/manifest.json", manifestResult, null, "http", 80 ); } loadSettings(); loadFilterSettings(); updateInstances(); // callback to addControls(); loadRosterI18n(); setRosterTop(); } function toggleShowControls() { CR.rosterSettings.controls = !CR.rosterSettings.controls; let newLabel = CR.rosterSettings.controls ? $.i18n("roster.menu.HideControls") : $.i18n("roster.menu.ShowControls"); CR.compactMenu.items[0].label = newLabel; CR.menu.items[0].label = newLabel; writeRosterSettings(); setVisual(); } // From i18n.js function addControls() { WANTED_LABELS = { cont: $.i18n("rosterColumns.Wanted.cont"), cqz: $.i18n("rosterColumns.Wanted.cqz"), ituz: $.i18n("rosterColumns.Wanted.ituz"), dxcc: $.i18n("rosterColumns.Wanted.dxcc"), dxccMarathon: $.i18n("rosterColumns.Wanted.dxccMarathon"), cqzMarathon: $.i18n("rosterColumns.Wanted.cqzMarathon"), state: $.i18n("rosterColumns.Wanted.state"), grid: $.i18n("rosterColumns.Wanted.grid"), cnty: $.i18n("rosterColumns.Wanted.cnty"), wpx: $.i18n("rosterColumns.Wanted.wpx"), call: $.i18n("rosterColumns.Wanted.call"), watcher: $.i18n("roster.watcher.label"), oams: $.i18n("rosterColumns.Wanted.oams"), pota: $.i18n("rosterColumns.Wanted.pota") } window.opener.setRosterSpot(CR.rosterSettings.columns.Spot); for (const key in CR.rosterSettings.wanted) { if (document.getElementById(key)) { document.getElementById(key).checked = CR.rosterSettings.wanted[key]; } } CR.menu = new nw.Menu(); CR.compactMenu = new nw.Menu(); let showControlsText = $.i18n("roster.menu.ShowControls"); let hideControlsText = $.i18n("roster.menu.HideControls"); let item = new nw.MenuItem({ type: "normal", label: CR.rosterSettings.controls ? hideControlsText : showControlsText, click: function () { toggleShowControls(); } }); CR.menu.append(item); item = new nw.MenuItem({ type: "normal", label: CR.rosterSettings.controls ? hideControlsText : showControlsText, click: function () { toggleShowControls(); } }); CR.compactMenu.append(item); CR.callMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.Lookup"), click: function () { callLookup(CR.targetHash, ""); } }); CR.callMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.GenMesg"), click: function () { callGenMessage(CR.targetHash, ""); } }); CR.callMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.add.watcher.label"), click: function () { addWatcher(CR.callRoster[CR.targetHash].DEcall, "Callsign"); } }); CR.callMenu.append(item); item = new nw.MenuItem({ type: "separator" }); CR.callMenu.append(item); if (window.opener.GT.pstrotatorSettings.enable) { item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.AimRotator"), click: function () { let target = CR.callRoster[CR.targetHash] window.opener.aimRotator(target, ""); } }); CR.callMenu.append(item); item = new nw.MenuItem({ type: "separator" }); CR.callMenu.append(item); } item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.IgnoreCall"), click: function () { ignoreCallsign(CR.callRoster[CR.targetHash].DEcall); } }); CR.callMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.EditIgnores"), enabled: true, click: function () { openIgnores(); } }); CR.callMenu.append(item); CR.callingMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.Lookup"), click: function () { callingLookup(CR.targetHash, ""); } }); CR.callingMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.GenMesg"), click: function () { callingGenMessage(CR.targetHash, ""); } }); CR.callingMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.add.watcher.label"), click: function () { addWatcher(CR.callRoster[CR.targetHash].DXcall, "Callsign"); } }); CR.callingMenu.append(item); item = new nw.MenuItem({ type: "separator" }); CR.menu.append(item); item = new nw.MenuItem({ type: "separator" }); CR.callingMenu.append(item); if (window.opener.GT.pstrotatorSettings.enable) { item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.AimRotator"), click: function () { let target = CR.callRoster[CR.targetHash] window.opener.aimRotator(target, ""); } }); CR.callingMenu.append(item); } item = new nw.MenuItem({ type: "checkbox", label: $.i18n("roster.menu.Realtime"), checked: CR.rosterSettings.realtime, click: function () { CR.rosterSettings.realtime = this.checked; writeRosterSettings(); viewRoster(); } }); CR.menu.append(item); CR.columnMenu = new nw.Menu(); CR.menuItemForMoveLeftColumn = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.MoveLeft"), click: function () { moveColumnLeft(CR.currentColumnName); } }) CR.columnMenu.append(CR.menuItemForMoveLeftColumn) CR.menuItemForMoveRightColumn = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.MoveRight"), click: function () { moveColumnRight(CR.currentColumnName); } }) CR.columnMenu.append(CR.menuItemForMoveRightColumn) item = new nw.MenuItem({ type: "separator" }); CR.columnMenu.append(item); for (const columnIndex in CR.rosterSettings.columnOrder) { let key = CR.rosterSettings.columnOrder[columnIndex]; if (key != "Callsign") { let itemx = new nw.MenuItem({ type: "checkbox", label: key, checked: CR.rosterSettings.columns[key], click: function () { toggleColumn(this); } }); CR.columnMenu.append(itemx); CR.columnMembers[key] = itemx; } } CR.MsgMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.add.watcher.label"), click: function () { addWatcher(CR.callRoster[CR.targetHash].callObj.msg, "Message"); } }); CR.MsgMenu.append(item); CR.CQMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.add.watcher.label"), click: function () { addWatcher(CR.callRoster[CR.targetCQ].DXcall, "Calling"); } }); CR.CQMenu.append(item); item = new nw.MenuItem({ type: "normal", label: "Ignore CQ from DXCC", click: function () { ignoreCQ(CR.callRoster[CR.targetCQ].DXcall, CR.callRoster[CR.targetCQ].callObj.dxcc); } }); CR.CQMenu.append(item); item = new nw.MenuItem({ type: "normal", label: "Ignore CQ from All", click: function () { ignoreCQ(CR.callRoster[CR.targetCQ].DXcall, -1); } }); CR.CQMenu.append(item); CR.CQzMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.IgnoreCQZone"), click: function () { ignoreCQz(CR.callRoster[CR.targetCQz].callObj.cqz); } }); CR.CQzMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.EditIgnores"), enabled: true, click: function () { openIgnores(); } }); CR.CQzMenu.append(item); CR.ITUzMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.IgnoreITUZone"), click: function () { ignoreITUz(CR.callRoster[CR.targetITUz].callObj.itu); } }); CR.ITUzMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.EditIgnores"), enabled: true, click: function () { openIgnores(); } }); CR.ITUzMenu.append(item); CR.dxccMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.IgnoreDXCC"), click: function () { ignoreDxcc(CR.targetDxcc); } }); CR.dxccMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.EditIgnores"), enabled: true, click: function () { openIgnores(); } }); CR.dxccMenu.append(item); CR.GridMenu = new nw.Menu(); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.add.watcher.label"), click: function () { addWatcher(CR.callRoster[CR.targetHash].callObj.grid, "Grid"); } }); CR.GridMenu.append(item); item = new nw.MenuItem({ type: "normal", label: "Ignore Grid", click: function () { ignoreGrid(CR.callRoster[CR.targetHash].callObj.grid); } }); CR.GridMenu.append(item); item = new nw.MenuItem({ type: "normal", label: $.i18n("roster.menu.EditIgnores"), enabled: true, click: function () { openIgnores(); } }); CR.GridMenu.append(item); callsignNeed.value = CR.rosterSettings.callsign; huntMode.value = CR.rosterSettings.hunting; huntNeed.value = CR.rosterSettings.huntNeed; wantGrid.checked = CR.rosterSettings.requireGrid; wantMaxDT.checked = CR.rosterSettings.wantMaxDT; wantMinDB.checked = CR.rosterSettings.wantMinDB; wantMinFreq.checked = CR.rosterSettings.wantMinFreq; wantMaxFreq.checked = CR.rosterSettings.wantMaxFreq; wantRRCQ.checked = CR.rosterSettings.wantRRCQ; maxDTView.innerHTML = maxDT.value = CR.rosterSettings.maxDT; minDbView.innerHTML = minDb.value = CR.rosterSettings.minDb; minFreqView.innerHTML = minFreq.value = CR.rosterSettings.minFreq; maxFreqView.innerHTML = maxFreq.value = CR.rosterSettings.maxFreq; maxLoTW.value = CR.rosterSettings.maxLoTW; maxLoTWView.innerHTML = maxLoTW.value < 27 ? toYM(Number(maxLoTW.value)) : ""; cqOnly.checked = CR.rosterSettings.cqOnly; noMyDxcc.checked = CR.rosterSettings.noMyDxcc; onlyMyDxcc.checked = CR.rosterSettings.onlyMyDxcc; usesLoTW.checked = CR.rosterSettings.usesLoTW; useseQSL.checked = CR.rosterSettings.useseQSL; onlySpot.checked = CR.rosterSettings.onlySpot; usesOQRS.checked = CR.rosterSettings.usesOQRS; referenceNeed.value = CR.rosterSettings.reference; allOnlyNew.checked = CR.rosterSettings.allOnlyNew; clearRosterOnBandChange.checked = CR.rosterSettings.clearRosterOnBandChange; rosterAlwaysOnTop.checked = CR.rosterSettings.rosterAlwaysOnTop; animateCQGT.checked = CR.rosterSettings.animateCQGT; rosterDelayOnFocus.checked = CR.rosterSettings.rosterDelayOnFocus; displayDelayOnFocus(); rosterDelayTime.value = CR.rosterSettings.rosterDelayTime; rosterDelayTimeTd.innerHTML = rosterDelayTime.value + "ms"; setRosterTimeView(); setCompactView(); initSelectors(); setVisual(); document.addEventListener("keydown", onMyKeyDown, false); CR.timerInterval = nodeTimers.setInterval(realtimeRoster, 1000); updateInstances(); } function setCompactView() { compactModeDiv.innerHTML = CR.rosterSettings.compact ? $.i18n("roster.menu.RosterMode") : $.i18n("roster.menu.CompactMode"); compactEnityDiv.style.display = CR.rosterSettings.compact ? "" : "none"; columnsBoxDiv.style.display = columnsbox.style.display = CR.rosterSettings.compact ? "none" : ""; } function compactModeChanged() { CR.rosterSettings.compact = !CR.rosterSettings.compact; writeRosterSettings(); setCompactView(); viewRoster(); } function compactEntityChanged() { CR.rosterSettings.compactEntity = compactEntitySelect.value; writeRosterSettings(); viewRoster(); } function clearRosterOnBandChangeValueChanged(what) { CR.rosterSettings.clearRosterOnBandChange = clearRosterOnBandChange.checked; writeRosterSettings(); } function rosterDelayOnFocusValueChanged(what) { CR.rosterSettings.rosterDelayOnFocus = rosterDelayOnFocus.checked; displayDelayOnFocus(); writeRosterSettings(); } function displayDelayOnFocus() { if (CR.rosterSettings.rosterDelayOnFocus) { rosterDelayTimeTd.style.display = "block"; rosterDelayTime.style.display = "block"; } else { rosterDelayTimeTd.style.display = "none"; rosterDelayTime.style.display = "none"; } } function changeRosterDelayTime() { CR.rosterSettings.rosterDelayTime = rosterDelayTime.value; rosterDelayTimeTd.innerHTML = rosterDelayTime.value + "ms"; writeRosterSettings(); } function changeRosterTime() { CR.rosterSettings.rosterTime = rosterTime.value; setRosterTimeView(); writeRosterSettings(); viewRoster(); } function changeAnimateCQGT(butt) { CR.rosterSettings.animateCQGT = butt.checked; viewRoster(); } function changeRosterTop(butt) { CR.rosterSettings.rosterAlwaysOnTop = butt.checked; setRosterTop(); } function setRosterTop() { nw.Window.get().setAlwaysOnTop(CR.rosterSettings.rosterAlwaysOnTop); } function setRosterTimeView() { rosterTime.value = CR.rosterSettings.rosterTime; rosterTimeTd.innerHTML = toDHMS(Number(rosterTime.value)); } function handleContextMenu(ev) { let mouseX = Math.round(ev.x); let mouseY = Math.round(ev.y); if (typeof ev.target != "undefined") { if (ev.target.className == "inputTextValue") return true; if (CR.developerMode) { if ((ev.target.id == "ShowMoreControlsLink") || (ev.target.id == "ShowFewerControlsLink") || (ev.target.id == "txrxdec")) { // Allow event to bubble up so that NWJS will show the developer menu return true; } } let name = ""; let target = ev.target; let parent = ev.target.parentNode; if (target.tagName == "SPAN") { target = ev.target.parentNode; parent = target.parentNode; } if (target.tagName == "TD" || (CR.rosterSettings.compact && target.tagName == "DIV")) { name = target.getAttribute("name"); } if (CR.rosterSettings.compact && name != "Callsign") { parent = parent.parentNode; } if (name == "Callsign") { CR.targetHash = parent.id; CR.callMenu.popup(mouseX, mouseY); } else if (name == "Calling") { CR.targetHash = parent.id; CR.callingMenu.popup(mouseX, mouseY); } else if (name == "Msg") { CR.targetHash = parent.id; CR.MsgMenu.popup(mouseX, mouseY); } else if (name == "Grid") { if (CR.callRoster[parent.id].callObj.grid.length == 4) { CR.targetHash = parent.id; CR.GridMenu.popup(mouseX, mouseY); } } else if (name == "CQ") { if (CR.callRoster[parent.id].DXcall != "CQ") { CR.targetCQ = parent.id; CR.CQMenu.popup(mouseX, mouseY); } } else if (name == "CQz") { CR.targetCQz = parent.id; CR.CQzMenu.popup(mouseX, mouseY); } else if (name == "ITUz") { CR.targetITUz = parent.id; CR.ITUzMenu.popup(mouseX, mouseY); } else if (name && name.startsWith("DXCC")) { let dxcca = name.split("("); let dxcc = parseInt(dxcca[1]); CR.targetDxcc = dxcc; CR.dxccMenu.popup(mouseX, mouseY); } else { if (CR.rosterSettings.compact) { CR.compactMenu.popup(mouseX, mouseY); } else { if (target.tagName == "TH" && target.getAttribute("name")) { CR.menuItemForMoveLeftColumn.enabled = true; CR.menuItemForMoveRightColumn.enabled = true; CR.currentColumnName = target.getAttribute("name"); const columns = rosterColumnList(CR.rosterSettings.columns, { Callsign: true }); const pos = columns.indexOf(CR.currentColumnName); if (pos == 0) { CR.menuItemForMoveLeftColumn.enabled = false; CR.menuItemForMoveRightColumn.enabled = false; } else if (pos == 1) { CR.menuItemForMoveLeftColumn.enabled = false; } if (pos + 1 == Object.keys(columns).length) { CR.menuItemForMoveRightColumn.enabled = false; } CR.columnMenu.popup(mouseX, mouseY); } else { CR.menu.popup(mouseX, mouseY); } } } } else { if (CR.rosterSettings.compact == false) { CR.menu.popup(mouseX, mouseY); } else { CR.compactMenu.popup(mouseX, mouseY); } } ev.preventDefault(); return false; } function getTypeFromMode(mode) { if (mode in CR.modes) { if (CR.modes[mode] == true) return "Digital"; else if (CR.modes_phone[mode] == true) return "Phone"; } return ""; } function testAward(awardName, obj, baseHash) { if ( CR.awardTracker[awardName].test.dxcc && CR.awardTracker[awardName].rule.dxcc.indexOf(obj.dxcc) == -1 ) { return false; } if ( CR.awardTracker[awardName].test.mode && CR.awardTracker[awardName].rule.mode.indexOf(obj.mode) == -1 ) { return false; } if ( CR.awardTracker[awardName].test.band && CR.awardTracker[awardName].rule.band.indexOf(obj.band) == -1 ) { return false; } if ( CR.awardTracker[awardName].test.DEcall && CR.awardTracker[awardName].rule.call.indexOf(obj.DEcall) == -1 ) { return false; } if ( CR.awardTracker[awardName].test.cont && CR.awardTracker[awardName].rule.cont.indexOf(obj.cont) == -1 ) { return false; } if ( CR.awardTracker[awardName].test.prop && CR.awardTracker[awardName].rule.propMode != obj.propMode ) { return false; } if ( CR.awardTracker[awardName].test.sat && CR.awardTracker[awardName].rule.satName.indexOf(obj.satName) == -1 ) { return false; } return CR.awardTypes[CR.awardTracker[awardName].rule.type].test( CR.awardTracker[awardName], obj, baseHash ); } function processAward(awardName) { let award = CR.awards[CR.awardTracker[awardName].sponsor].awards[ CR.awardTracker[awardName].name ]; CR.awardTracker[awardName].rule = award.rule; let test = (CR.awardTracker[awardName].test = {}); let mode = award.rule.mode.slice(); let Index = mode.indexOf("Mixed"); if (Index > -1) mode.splice(Index, 1); Index = mode.indexOf("Digital"); if (Index > -1) mode.splice(Index, 1); Index = mode.indexOf("Phone"); if (Index > -1) mode.splice(Index, 1); test.mode = mode.length > 0; test.confirmed = "qsl_req" in CR.awards[CR.awardTracker[awardName].sponsor].awards[CR.awardTracker[awardName].name].rule ? CR.awards[CR.awardTracker[awardName].sponsor].awards[CR.awardTracker[awardName].name].rule.qsl_req == "confirmed" : CR.awards[CR.awardTracker[awardName].sponsor].qsl_req == "confirmed"; test.look = "confirmed"; test.qsl_req = "qsl_req" in CR.awards[CR.awardTracker[awardName].sponsor].awards[CR.awardTracker[awardName].name].rule ? CR.awards[CR.awardTracker[awardName].sponsor].awards[CR.awardTracker[awardName].name].rule.qsl_req : CR.awards[CR.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; test.cont = "cont" in award.rule; test.prop = "propMode" in award.rule; test.sat = "satName" in award.rule; CR.awardTracker[awardName].stat = {}; for (const i in window.opener.GT.QSOhash) { let obj = window.opener.GT.QSOhash[i]; if (test.confirmed && !obj.confirmed) continue; if (obj.dxcc < 1) continue; if (test.dxcc && award.rule.dxcc.indexOf(obj.dxcc) == -1) continue; if (test.mode && award.rule.mode.indexOf(obj.mode) == -1) continue; if (test.band && award.rule.band.indexOf(obj.band) == -1) continue; if (test.DEcall && award.rule.call.indexOf(obj.DEcall) == -1) continue; if (test.cont && award.rule.cont.indexOf(obj.cont) == -1) continue; if (test.prop && award.rule.propMode != obj.propMode) continue; if (test.sat && award.rule.satName.indexOf(obj.satName) == -1) continue; CR.awardTypes[award.rule.type].score(CR.awardTracker[awardName], obj); } CR.awardTracker[awardName].comp = CR.awardTypes[award.rule.type].compile( CR.awardTracker[awardName], CR.awardTracker[awardName].stat ); CR.awardTracker[awardName].stat = {}; } function newAwardCountObject() { let statCountObject = {}; statCountObject.bands = {}; statCountObject.bands.Mixed = {}; statCountObject.bands.Digital = {}; statCountObject.bands.Phone = {}; statCountObject.modes = {}; statCountObject.modes.Mixed = {}; statCountObject.modes.Digital = {}; statCountObject.modes.Phone = {}; statCountObject.unique = null; return statCountObject; } function workAwardObject(obj, band, mode, isDigital, isPhone, unique = null) { obj.bands.Mixed[band] = ~~obj.bands.Mixed[band] + 1; if (!(mode in obj.bands)) obj.bands[mode] = {}; obj.bands[mode][band] = ~~obj.bands[mode][band] + 1; obj.modes.Mixed[mode] = ~~obj.modes.Mixed[mode] + 1; if (isDigital) { obj.bands.Digital[band] = ~~obj.bands.Digital[band] + 1; obj.modes.Digital[mode] = ~~obj.modes.Digital[mode] + 1; } if (isPhone) { obj.bands.Phone[band] = ~~obj.bands.Phone[band] + 1; obj.modes.Phone[mode] = ~~obj.modes.Phone[mode] + 1; } if (unique) { if (obj.unique == null) obj.unique = {}; if (!(unique in obj.unique)) obj.unique[unique] = newAwardCountObject(); workAwardObject(obj.unique[unique], band, mode, isDigital, isPhone); } return true; } function buildAwardTypeHandlers() { CR.awardTypes = { IOTA: { name: "Islands On The Air" }, call: { name: "Callsign" }, callarea: { name: "Call Area" }, calls2dxcc: { name: "Stations per DXCC" }, cnty: { name: "County" }, cont: { name: "Continents" }, cont5: { name: "5 Continents" }, cont52band: { name: "5 Continents per Band" }, cqz: { name: "CQ Zone" }, dxcc: { name: "DXCC" }, grids: { name: "Grids" }, numsfx: { name: "Call Area + Suffix" }, px: { name: "Prefix" }, pxa: { name: "Prefixes" }, pxplus: { name: "Special Calls" }, sfx: { name: "Suffix" }, states: { name: "States" }, cont2band: { name: "Continents per Band" }, calls2band: { name: "Stations per Band" }, dxcc2band: { name: "DXCC per Band" }, states2band: { name: "States per Band" } }; CR.awardTypes.IOTA.score = scoreAIOTA; CR.awardTypes.call.score = scoreAcall; CR.awardTypes.callarea.score = scoreAcallarea; CR.awardTypes.calls2dxcc.score = scoreAcalls2dxcc; CR.awardTypes.cnty.score = scoreAcnty; CR.awardTypes.cont.score = scoreAcont; CR.awardTypes.cont5.score = scoreAcont5; CR.awardTypes.cont52band.score = scoreAcont52band; CR.awardTypes.cqz.score = scoreAcqz; CR.awardTypes.dxcc.score = scoreAdxcc; CR.awardTypes.grids.score = scoreAgrids; CR.awardTypes.numsfx.score = scoreAnumsfx; CR.awardTypes.px.score = scoreApx; CR.awardTypes.pxa.score = scoreApxa; CR.awardTypes.pxplus.score = scoreApxplus; CR.awardTypes.sfx.score = scoreAsfx; CR.awardTypes.states.score = scoreAstates; CR.awardTypes.cont2band.score = scoreAcont2band; CR.awardTypes.calls2band.score = scoreAcalls2band; CR.awardTypes.dxcc2band.score = scoreAdxcc2band; CR.awardTypes.states2band.score = scoreAstates2band; CR.awardTypes.IOTA.test = testAIOTA; CR.awardTypes.call.test = testAcall; CR.awardTypes.callarea.test = testAcallarea; CR.awardTypes.calls2dxcc.test = testAcalls2dxcc; CR.awardTypes.cnty.test = testAcnty; CR.awardTypes.cont.test = testAcont; CR.awardTypes.cont5.test = testAcont5; CR.awardTypes.cont52band.test = testAcont52band; CR.awardTypes.cqz.test = testAcqz; CR.awardTypes.dxcc.test = testAdxcc; CR.awardTypes.grids.test = testAgrids; CR.awardTypes.numsfx.test = testAnumsfx; CR.awardTypes.px.test = testApx; CR.awardTypes.pxa.test = testApxa; CR.awardTypes.pxplus.test = testApxplus; CR.awardTypes.sfx.test = testAsfx; CR.awardTypes.states.test = testAstates; CR.awardTypes.cont2band.test = testAcont2band; CR.awardTypes.calls2band.test = testAcalls2band; CR.awardTypes.dxcc2band.test = testAdxcc2band; CR.awardTypes.states2band.test = testAstates; CR.awardTypes.IOTA.compile = singleCompile; CR.awardTypes.call.compile = singleCompile; CR.awardTypes.callarea.compile = singleCompile; CR.awardTypes.calls2dxcc.compile = doubleCompile; CR.awardTypes.cnty.compile = singleCompile; CR.awardTypes.cont.compile = singleCompile; CR.awardTypes.cont5.compile = singleCompile; CR.awardTypes.cont52band.compile = doubleCompile; CR.awardTypes.cqz.compile = singleCompile; CR.awardTypes.dxcc.compile = singleCompile; CR.awardTypes.grids.compile = singleCompile; CR.awardTypes.numsfx.compile = singleCompile; CR.awardTypes.px.compile = singleCompile; CR.awardTypes.pxa.compile = singleCompile; CR.awardTypes.pxplus.compile = singleCompile; CR.awardTypes.sfx.compile = singleCompile; CR.awardTypes.states.compile = singleCompile; CR.awardTypes.cont2band.compile = doubleCompile; CR.awardTypes.calls2band.compile = doubleCompile; CR.awardTypes.dxcc2band.compile = doubleCompile; CR.awardTypes.states2band.compile = doubleCompile; } function scoreAstates(award, obj) { if (obj.state) { if (!(obj.state in award.stat)) { award.stat[obj.state] = newAwardCountObject(); } return workAwardObject( award.stat[obj.state], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAstates(award, obj, baseHash) { // calls with empty state will not match anything in the hash map. so filter those out if (!obj.state || obj.state + baseHash in CR.tracker[award.test.look].state) { return false; } return true; } function scoreAstates2band(award, obj) { if (obj.state) { if (!(obj.band in award.stat)) award.stat[obj.band] = newAwardCountObject(); return workAwardObject( award.stat[obj.band], obj.band, obj.mode, obj.digital, obj.phone, obj.state ); } return false; } function scoreAdxcc(award, obj) { if (!(obj.dxcc in award.stat)) award.stat[obj.dxcc] = newAwardCountObject(); return workAwardObject( award.stat[obj.dxcc], obj.band, obj.mode, obj.digital, obj.phone ); } function testAdxcc(award, obj, baseHash) { if (String(obj.dxcc) + "|" + baseHash in CR.tracker[award.test.look].dxcc) { return false; } return true; } function scoreAcont(award, obj) { if (obj.cont) { let cont = obj.cont; if (cont == "AN") cont = "OC"; if (!(cont in award.stat)) award.stat[cont] = newAwardCountObject(); return workAwardObject( award.stat[cont], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAcont(award, obj, baseHash) { if (obj.cont) { let cont = obj.cont; if (cont == "AN") cont = "OC"; if (cont + baseHash in CR.tracker[award.test.look].cont) { return false; } } return true; } function scoreAcont5(award, obj, baseHash) { if (obj.cont) { let cont = obj.cont; if (cont == "NA" || cont == "SA") cont = "AM"; if (cont == "AN") cont = "OC"; if (!(cont in award.stat)) award.stat[cont] = newAwardCountObject(); return workAwardObject( award.stat[cont], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAcont5(award, obj, baseHash) { if (obj.cont) { let cont = obj.cont; if (cont == "NA" || cont == "SA") cont = "AM"; if (cont == "AN") cont = "OC"; if (cont + baseHash in CR.tracker[award.test.look].cont) { return false; } } return true; } function scoreAcont2band(award, obj) { if (!(obj.band in award.stat)) award.stat[obj.band] = newAwardCountObject(); return workAwardObject( award.stat[obj.band], obj.band, obj.mode, obj.digital, obj.phone, obj.cont ); } function testAcont2band(award, obj, baseHash) { if (obj.cont) { let cont = obj.cont; if (cont == "AN") cont = "OC"; if (cont + baseHash in CR.tracker[award.test.look].cont) { return false; } } return true; } function scoreAcont52band(award, obj) { if (obj.cont) { let cont = obj.cont; if (cont == "NA" || cont == "SA") cont = "AM"; if (cont == "AN") cont = "OC"; if (!(obj.band in award.stat)) award.stat[obj.band] = newAwardCountObject(); return workAwardObject( award.stat[obj.band], obj.band, obj.mode, obj.digital, obj.phone, cont ); } return false; } function testAcont52band(award, obj, baseHash) { if (obj.cont) { let cont = obj.cont; if (cont == "NA" || cont == "SA") cont = "AM"; if (cont == "AN") cont = "OC"; if (cont + baseHash in CR.tracker[award.test.look].cont) { return false; } } return true; } function scoreAgrids(award, obj) { if (obj.grid && obj.grid.length > 0) { let grid = obj.grid.substr(0, 4); if (!(grid in award.stat)) award.stat[grid] = newAwardCountObject(); return workAwardObject( award.stat[grid], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAgrids(award, obj, baseHash) { if (obj.grid && obj.grid + baseHash in CR.tracker[award.test.look].grid) { return false; } if (!obj.grid || obj.grid.length == 0) { return false; } return true; } function scoreAcnty(award, obj) { if (obj.cnty) { if (!(obj.cnty in award.stat)) award.stat[obj.cnty] = newAwardCountObject(); return workAwardObject( award.stat[obj.cnty], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAcnty(award, obj, baseHash) { if (obj.cnty && obj.cnty + baseHash in CR.tracker[award.test.look].cnty) { return false; } return true; } function scoreAcall(award, obj) { let call = obj.DEcall; if (call.indexOf("/") > -1) { if (call.endsWith("/MM")) return false; call = call.replace("/P", "").replace("/R", "").replace("/QRP"); } if (!(call in award.stat)) award.stat[call] = newAwardCountObject(); return workAwardObject( award.stat[call], obj.band, obj.mode, obj.digital, obj.phone ); } function testAcall(award, obj, baseHash) { if (obj.DEcall.indexOf("/") > -1 && obj.DEcall.endsWith("/MM")) return false; if (obj.DEcall + baseHash in CR.tracker[award.test.look].call) { return false; } return true; } function scoreAIOTA(award, obj) { if (obj.IOTA) { let test = CR.awards[award.sponsor].awards[award.name]; if ("IOTA" in test.rule && test.rule.IOTA.indexOf(obj.IOTA) == -1) { return false; } if (!(obj.IOTA in award.stat)) award.stat[obj.IOTA] = newAwardCountObject(); return workAwardObject( award.stat[obj.IOTA], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } // NO IOTA YET function testAIOTA(award, obj, baseHash) { /* if ( obj.IOTA ) { let test = CR.awards[award.sponsor].awards[award.name]; if ( "IOTA" in test.rule && test.rule.IOTA.indexOf(obj.IOTA) == -1 ) return false; } */ return false; } function scoreAcallarea(award, obj) { if (obj.zone != null) { let test = CR.awards[award.sponsor].awards[award.name]; if ("zone" in test.rule && test.rule.zone.indexOf(obj.zone) == -1) { return false; } if (!(obj.zone in award.stat)) award.stat[obj.zone] = newAwardCountObject(); return workAwardObject( award.stat[obj.zone], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAcallarea(award, obj, baseHash) { if (obj.zone != null) { let test = CR.awards[award.sponsor].awards[award.name]; if ("zone" in test.rule && test.rule.zone.indexOf(obj.zone) == -1) { return false; } } return true; } function scoreApx(award, obj) { if (obj.px) { let test = CR.awards[award.sponsor].awards[award.name]; let px = obj.px; if ("px" in test.rule) { px = px.substr(0, test.rule.px[0].length); if (test.rule.px.indexOf(px) == -1) return false; } if (!(px in award.stat)) award.stat[px] = newAwardCountObject(); return workAwardObject( award.stat[px], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testApx(award, obj, baseHash) { if (obj.px) { let test = CR.awards[award.sponsor].awards[award.name]; let px = obj.px; if ("px" in test.rule) { px = px.substr(0, test.rule.px[0].length); if (test.rule.px.indexOf(px) == -1) return false; } if (String(obj.px) + baseHash in CR.tracker[award.test.look].px) { return false; } } return true; } function scoreApxa(award, obj) { if (obj.px) { let test = CR.awards[award.sponsor].awards[award.name]; for (const i in test.rule.pxa) { if (test.rule.pxa[i].indexOf(obj.px) > -1) { if (!(i in award.stat)) award.stat[i] = newAwardCountObject(); return workAwardObject( award.stat[i], obj.band, obj.mode, obj.digital, obj.phone ); } } } return false; } function testApxa(award, obj, baseHash) { if (obj.px) { let test = CR.awards[award.sponsor].awards[award.name]; for (const i in test.rule.pxa) { if (test.rule.pxa[i].indexOf(obj.px) > -1) { if (String(obj.px) + baseHash in CR.tracker[award.test.look].px) { return false; } else { return true; } } } } return false; } function scoreAsfx(award, obj) { let test = CR.awards[award.sponsor].awards[award.name]; let suf = obj.DEcall.replace(obj.px, ""); for (const i in test.rule.sfx) { for (const s in test.rule.sfx[i]) { if (suf.indexOf(test.rule.sfx[i][s]) == 0) { if (!(i in award.stat)) award.stat[i] = newAwardCountObject(); return workAwardObject( award.stat[i], obj.band, obj.mode, obj.digital, obj.phone ); } } } return false; } function testAsfx(award, obj, baseHash) { let test = CR.awards[award.sponsor].awards[award.name]; let suf = obj.DEcall.replace(obj.px, ""); for (const i in test.rule.sfx) { for (const s in test.rule.sfx[i]) { if (suf.indexOf(test.rule.sfx[i][s]) == 0) { return false; } } } return true; } function scoreAcalls2dxcc(award, obj) { if (!(obj.dxcc in award.stat)) award.stat[obj.dxcc] = newAwardCountObject(); return workAwardObject( award.stat[obj.dxcc], obj.band, obj.mode, obj.digital, obj.phone, obj.DEcall ); } function testAcalls2dxcc(award, obj, baseHash) { if (obj.DEcall + baseHash in CR.tracker[award.test.look].call) { return false; } return true; } function scoreAcalls2band(award, obj) { if (!(obj.band in award.stat)) award.stat[obj.band] = newAwardCountObject(); return workAwardObject( award.stat[obj.band], obj.band, obj.mode, obj.digital, obj.phone, obj.DEcall ); } function testAcalls2band(award, obj, baseHash) { if (obj.DEcall + baseHash in CR.tracker[award.test.look].call) { return false; } return true; } function scoreAdxcc2band(award, obj) { if (!(obj.band in award.stat)) award.stat[obj.band] = newAwardCountObject(); return workAwardObject( award.stat[obj.band], obj.band, obj.mode, obj.digital, obj.phone, obj.dxcc ); } function testAdxcc2band(award, obj, baseHash) { if (String(obj.dxcc) + "|" + baseHash in CR.tracker[award.test.look].dxcc) { return false; } return true; } function scoreAcqz(award, obj) { if (obj.cqz) { if (!(obj.cqz in award.stat)) award.stat[obj.cqz] = newAwardCountObject(); return workAwardObject( award.stat[obj.cqz], obj.band, obj.mode, obj.digital, obj.phone ); } return false; } function testAcqz(award, obj, baseHash) { // calls with empty cqz will not match anything in the hash map. so filter those out if (!obj.cqz || obj.cqz + "|" + baseHash in CR.tracker[award.test.look].cqz) { return false; } return true; } function scoreAnumsfx(award, obj) { if (obj.px) { let test = CR.awards[award.sponsor].awards[award.name]; let px = obj.px.substr(0, obj.px.length - 1); let suf = obj.DEcall.replace(px, ""); suf = suf.substr(0, test.rule.numsfx[0][0].length); for (const i in test.rule.numsfx) { for (const s in test.rule.numsfx[i]) { if (suf.indexOf(test.rule.numsfx[i][s]) == 0) { if (!(i in award.stat)) award.stat[i] = newAwardCountObject(); return workAwardObject( award.stat[i], obj.band, obj.mode, obj.digital, obj.phone ); } } } } return false; } function testAnumsfx(award, obj) { if (obj.px) { let test = CR.awards[award.sponsor].awards[award.name]; let px = obj.px.substr(0, obj.px.length - 1); let suf = obj.DEcall.replace(px, ""); suf = suf.substr(0, test.rule.numsfx[0][0].length); for (const i in test.rule.numsfx) { for (const s in test.rule.numsfx[i]) { if (suf.indexOf(test.rule.numsfx[i][s]) == 0) { return false; } } } } return true; } function scoreApxplus(award, obj) { let test = CR.awards[award.sponsor].awards[award.name]; if (test.rule.pxplus) { for (const i in test.rule.pxplus) { if (obj.DEcall.indexOf(test.rule.pxplus[i]) == 0) { if (!(i in award.stat)) award.stat[i] = newAwardCountObject(); return workAwardObject( award.stat[i], obj.band, obj.mode, obj.digital, obj.phone ); } } } return false; } function testApxplus(award, obj) { let test = CR.awards[award.sponsor].awards[award.name]; if (test.rule.pxplus) { for (const i in test.rule.pxplus) { if (obj.DEcall.indexOf(test.rule.pxplus[i]) == 0) { return false; } } } return true; } function loadAwardJson() { CR.awards = {}; let fs = require("fs"); if (fs.existsSync("./data/awards.json")) { fileBuf = fs.readFileSync("./data/awards.json"); try { CR.awards = JSON.parse(fileBuf); // fs.writeFileSync("./data/awards.json", JSON.stringify(CR.awards,null,2)); for (const sp in CR.awards) { for (const aw in CR.awards[sp].awards) { if (!("unique" in CR.awards[sp].awards[aw].rule)) { CR.awards[sp].awards[aw].rule.unique = 1; } if (CR.awards[sp].awards[aw].rule.band[0] == "Mixed") { CR.awards[sp].awards[aw].rule.band.shift(); } if (CR.awards[sp].awards[aw].rule.band.length == 0) { CR.awards[sp].awards[aw].rule.band = []; for (let key in CR.awards[sp].mixed) { CR.awards[sp].awards[aw].rule.band.push(CR.awards[sp].mixed[key]); } } if ( CR.awards[sp].awards[aw].rule.endorse.length == 1 && CR.awards[sp].awards[aw].rule.endorse[0] == "Mixed" ) { CR.awards[sp].awards[aw].rule.endorse = []; for (let key in CR.awards[sp].mixed) { CR.awards[sp].awards[aw].rule.endorse.push( CR.awards[sp].mixed[key] ); } } } } buildAwardTypeHandlers(); } catch (e) { alert("Core awards.json : " + e); CR.awards = {}; } } else alert("Missing core awards.json"); } function processAllAwardTrackers() { for (let tracker in CR.awardTracker) { if (!(CR.awardTracker[tracker].sponsor in CR.awards)) { delete CR.awardTracker[tracker]; continue; } if ( !( CR.awardTracker[tracker].name in CR.awards[CR.awardTracker[tracker].sponsor].awards ) ) { delete CR.awardTracker[tracker]; continue; } processAward(tracker); } updateAwardList(); } function newAwardTrackerObject(sponsor, award, enable) { let newAward = {}; newAward.sponsor = sponsor; newAward.name = award; newAward.enable = enable; newAward.mode = CR.awards[sponsor].awards[award].rule.mode[0]; newAward.band = CR.awards[sponsor].awards[award].rule.band[0]; newAward.count = CR.awards[sponsor].awards[award].rule.count[0]; newAward.stat = {}; newAward.comp = {}; newAward.test = {}; return newAward; } function addAllAwards() { for (let sponsor in CR.awards) { for (let award in CR.awards[sponsor].awards) { let awardToAdd = newAwardTrackerObject(sponsor, award, true); let hash = awardToAdd.name + "-" + awardToAdd.sponsor; if (!(hash in CR.awardTracker)) { CR.awardTracker[hash] = awardToAdd; processAward(hash); storeAwardTracker(); } } } updateAwardList(); viewRoster(); } function delAllAwards() { CR.awardTracker = {}; storeAwardTracker(); updateAwardList(); viewRoster(); } function newCompileCountObject() { let compileCountObject = {}; compileCountObject.bands = {}; compileCountObject.modes = {}; compileCountObject.endorse = {}; compileCountObject.counts = {}; return compileCountObject; } function singleCompile(award, obj) { let test = CR.awards[award.sponsor].awards[award.name]; let rule = test.rule; let comp = newCompileCountObject(); for (let mode in rule.mode) { comp.modes[rule.mode[mode]] = 0; comp.bands[rule.mode[mode]] = {}; for (let band in rule.band) { comp.bands[rule.mode[mode]][rule.band[band]] = 0; } for (let key in obj) { if ( rule.mode[mode] in obj[key].bands && Object.keys(obj[key].bands[rule.mode[mode]]).length ) { comp.modes[rule.mode[mode]] += 1; for (let band in rule.band) { if (rule.band[band] in obj[key].bands[rule.mode[mode]]) { comp.bands[rule.mode[mode]][rule.band[band]] += 1; } } } } } for (let mode in comp.modes) { comp.endorse[mode] = {}; comp.counts[mode] = {}; for (let cnts in rule.count) { comp.counts[mode][rule.count[cnts]] = { num: comp.modes[mode], per: parseInt( Math.min(100, (comp.modes[mode] / rule.count[cnts]) * 100.0) ) }; } for (let endorse in rule.endorse) { comp.endorse[mode][rule.endorse[endorse]] = {}; for (let cnts in rule.count) { comp.endorse[mode][rule.endorse[endorse]][rule.count[cnts]] = comp.bands[mode][rule.endorse[endorse]] >= rule.count[cnts]; } } } return comp; } function doubleCompile(award, firstLevel) { let test = CR.awards[award.sponsor].awards[award.name]; let rule = test.rule; for (let k in firstLevel) { firstLevel[k].bands = {}; // firstLevel[k].modes = {}; let obj = singleCompile(award, firstLevel[k].unique); for (let mode in obj.bands) { for (let cnt in test.rule.count) { if (obj.counts[mode][test.rule.count[cnt]].num >= test.rule.unique) { for (let band in obj.bands[mode]) { if (!(mode in firstLevel[k].bands)) firstLevel[k].bands[mode] = {}; if (obj.bands[mode][band] > 0) { firstLevel[k].bands[mode][band] = ~~firstLevel[k].bands[mode][band] + 1; } } } } } /* for ( let mode in obj.modes ) { if ( !(mode in firstLevel[k].modes) ) firstLevel[k].modes[mode] = 0; if ( obj.modes[mode] > 0 ) firstLevel[k].modes[mode] += 1; } */ delete firstLevel[k].unique; firstLevel[k].unique = null; } return singleCompile(award, firstLevel); } function listShortInstances() { let shortInstances = []; if (typeof window.opener.GT.instancesIndex != "undefined" && typeof window.opener.GT.instances != "undefined") { if (window.opener.GT.instancesIndex.length > 1) { let instances = window.opener.GT.instances; let keys = Object.keys(instances).sort(); for (let key in keys) { let inst = keys[key]; let sp = inst.split(" - "); let shortInst = sp[sp.length - 1].substring(0, 18); shortInstances.push(shortInst); } } } return shortInstances; } function openInfoTab(evt, tabName, callFunc, callObj) { // Declare all variables var i, infoTabcontent, infoTablinks; // Get all elements with class="infoTabcontent" and hide them infoTabcontent = document.getElementsByClassName("infoTabcontent"); for (i = 0; i < infoTabcontent.length; i++) { infoTabcontent[i].style.display = "none"; } // Get all elements with class="infoTablinks" and remove the class "active" infoTablinks = document.getElementsByClassName("infoTablinks"); for (i = 0; i < infoTablinks.length; i++) { infoTablinks[i].className = infoTablinks[i].className.replace( " active", "" ); } // Show the current tab, and add an "active" class to the button that opened the tab document.getElementById(tabName).style.display = "block"; if (typeof evt == "string") { for (i = 0; i < infoTablinks.length; i++) { if (infoTablinks[i].id == evt) { infoTablinks[i].className += " active"; } } } else if (typeof evt.currentTarget != "undefined") { evt.currentTarget.className += " active"; } else { evt.className += " active"; } if (callFunc) { if (typeof callFunc == "function") { if (callObj) callFunc(callObj); else callFunc(); } } } function ValidateTextInput(inputText, validDiv = null) { if (inputText.value.length > 0) { var passed = false; inputText.value = inputText.value.toUpperCase(); if (/\d/.test(inputText.value) || /[A-Z]/.test(inputText.value)) { passed = true; } if (passed) { inputText.style.color = "#FF0"; inputText.style.backgroundColor = "green"; if (validDiv) validDiv.innerHTML = ""; return true; } else { inputText.style.color = "#000"; inputText.style.backgroundColor = "yellow"; if (validDiv) validDiv.innerHTML = $.i18n("stats.Validate.Invalid"); return false; } } else { inputText.style.color = "#000"; inputText.style.backgroundColor = "yellow"; if (validDiv) validDiv.innerHTML = $.i18n("stats.Validate.Invalid"); return false; } } function watcherOnName() { watcherName.value = watcherName.value.replace(/[$%.'",<>]/g, ""); watcherNameValidate(); } function watcherNameValidate() { if (watcherName.value.length == 0 || (watcherName.value in CR.watchers && watcherName.value != CR.watcherEditKey)) { watcherName.style.color = "#000"; watcherName.style.backgroundColor = "red"; return false; } else { watcherName.style.color = ""; watcherName.style.backgroundColor = ""; return true; } } function watcherTypeChanged(value) { watcherType.value = value; if (value == "Callsign") { watcherTextTh.innerHTML = $.i18n("roster.controls.hunting.callsign"); } if (value == "Calling") { watcherTextTh.innerHTML = $.i18n("alerts.QRZ.speech"); } if (value == "Grid") { watcherTextTh.innerHTML = $.i18n("roster.controls.hunting.grid"); } if (value == "Message") { watcherTextTh.innerHTML = $.i18n("gt.WSJTMessage.Message"); } watcherStartDateEnable(watcherStartDateCheckbox.checked); watcherEndDateEnable(watcherEndDateCheckbox.checked); } function watcherRegexChanged(checked) { watcherOnText(); } function watcherOnText() { var testCallsign = false; if (watcherRegexCheckbox.checked == false) { if (watcherType.value == "Message") { watcherText.value = watcherText.value.toUpperCase().replace(/[^A-Z0-9/<>\s]+/g, ""); } else if (watcherType.value == "Calling") { testCallsign = true; watcherText.value = watcherText.value.toUpperCase().replace(/[^A-Z0-9/\s]+/g, ""); } else if (watcherType.value == "Grid") { gridInputValidate(watcherText); return; } else { testCallsign = true; watcherText.value = watcherText.value.toUpperCase().replace(/[^A-Z0-9/]+/g, ""); } } watcherTextValidate(testCallsign); } function watcherTextValidate(testCallsign = false) { if (watcherText.value.length == 0) { watcherText.style.color = "#000"; watcherText.style.backgroundColor = "red"; return false; } else { if (testCallsign && !watcherText.value.match(CALLSIGN_REGEXP)) { watcherText.style.color = "#000"; watcherText.style.backgroundColor = "yellow"; } else { watcherText.style.color = ""; watcherText.style.backgroundColor = ""; } return true; } } function watcherStartDateEnable(checked) { watcherStartDateTh.style.display = watcherStartDateTd.style.display = (checked ? "" : "none"); } function watcherEndDateEnable(checked) { watcherAutoDeleteTd.style.display = watcherAutoDeleteTh.style.display = watcherEndDateTh.style.display = watcherEndDateTd.style.display = (checked ? "" : "none"); } function newWatcherEntry() { let entry = Object(); entry.watch = true; entry.type = "Callsign"; entry.regex = false; entry.text = ""; entry.test = null; entry.start = false; entry.end = false; entry.startTime = Date.now(); entry.startTime -= (entry.startTime % 86400000); entry.endTime = Date.now(); entry.endTime -= (entry.endTime % 86400000); entry.autoDelete = false; return entry; } function saveWatcher() { watcherName.value = watcherName.value.trim(); watcherText.value = watcherText.value.trim(); if (watcherNameValidate() == false || watcherTextValidate() == false) return; if (CR.watcherEditKey.length > 0 && CR.watcherEditKey in CR.watchers) { delete CR.watchers[CR.watcherEditKey]; } let entry = newWatcherEntry(); entry.watch = true; entry.type = watcherType.value; entry.regex = watcherRegexCheckbox.checked; entry.text = watcherText.value; entry.test = null; entry.start = watcherStartDateCheckbox.checked; entry.end = watcherEndDateCheckbox.checked; entry.autoDelete = entry.end ? watcherAutoDeleteCheckbox.checked : false; if (entry.start) { if (watcherStartDate.value.length == 0) { entry.startTime = Date.now(); } else { entry.startTime = Date.parse(watcherStartDate.value + "Z"); } } if (entry.end) { if (watcherEndDate.value.length == 0) { entry.endTime = Date.now(); } else { entry.endTime = Date.parse(watcherEndDate.value + "Z"); } if (entry.start && entry.endTime <= entry.startTime) { // Good for a minute, least we can do :) entry.endTime = entry.startTime + 60000; } } CR.watchers[watcherName.value] = entry; writeRosterSettings(); openWathcherTab(); window.opener.goProcessRoster(); } function addWatcher(value, type) { if (!(value in CR.watchers)) { let entry = newWatcherEntry(); entry.watch = true; entry.type = type; entry.regex = false; entry.text = value; entry.test = null; entry.autoDelete = false; CR.watchers[value] = entry; CR.rosterSettings.wanted.huntWatcher = huntWatcher.checked = true; writeRosterSettings(); window.opener.goProcessRoster(); wantRenderWatchersTab(); } } function clearWatcher() { CR.watcherEditKey = ""; watcherName.style.color = ""; watcherName.style.backgroundColor = ""; watcherText.style.color = ""; watcherText.style.backgroundColor = ""; loadWatcherValues("", newWatcherEntry()); } function toggleWatcher(key) { CR.watchers[key].watch = !CR.watchers[key].watch; writeRosterSettings(); wantRenderWatchersTab(); window.opener.goProcessRoster(); } function deleteWatcher(key) { delete CR.watchers[key]; writeRosterSettings(); wantRenderWatchersTab(); window.opener.goProcessRoster(); } CR.watcherEditKey = ""; function editWatcher(key) { CR.watcherEditKey = key; loadWatcherValues(key, CR.watchers[key]); } function loadWatcherValues(key, entry) { watcherName.value = key; watcherType.vale = entry.type; watcherRegexCheckbox.checked = entry.regex; watcherText.value = entry.text; watcherStartDateCheckbox.checked = entry.start; watcherEndDateCheckbox.checked = entry.end; let date = new Date(entry.startTime); watcherStartDate.value = date.toISOString().slice(0, 16); date = new Date(entry.endTime); watcherEndDate.value = date.toISOString().slice(0, 16); watcherAutoDeleteCheckbox.checked = entry.autoDelete; watcherTypeChanged(entry.type); } function htmlEntities(str) { return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function openWathcherTab() { clearWatcher(); wantRenderWatchersTab(); } function wantRenderWatchersTab() { if (watcherBoxDiv.style.display != "none") { renderWatchersTab(); } } function renderWatchersTab() { if (Object.keys(CR.watchers).length > 0) { let worker = "
"; Object.keys(CR.watchers) .sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1) .forEach(function (key) { worker += ""; worker += ""; let text = htmlEntities(CR.watchers[key].text); worker += ""; worker += ""; worker += ""; worker += ""; worker += ""; }); worker += "
👁️NameTypeRegexTextStart DateEnd DateEditDelete
" + (CR.watchers[key].watch ? "👀" : "🙈") + "" + key + "" + CR.watchers[key].type + "" + (CR.watchers[key].regex ? "☑️" : "") + "" + (CR.watchers[key].regex ? text : formatCallsign(text)) + "" + (CR.watchers[key].start ? window.opener.userTimeString(CR.watchers[key].startTime) : "") + "" + (CR.watchers[key].end ? window.opener.userTimeString(CR.watchers[key].endTime) : "") + "📝"; worker += CR.watchers[key].autoDelete ? "🤖" : "🚮"; worker += "
"; watcherEditView.innerHTML = worker; let height = 40; if (watcherBoxDiv.offsetHeight >= window.innerHeight - height) { watcherBoxDiv.style.height = (window.innerHeight - height) + "px"; } else { watcherBoxDiv.style.height = ""; } } else { watcherBoxDiv.style.height = ""; watcherEditView.innerHTML = ""; } }