[feat](trx-frontend-http): add FT4 as map source
- Add ft4 to DEFAULT_MAP_SOURCE_FILTER (visible by default) - Add ft4 hue to locatorThemeHues (peakHue + 30° offset from FT8) - Propagate ft4 through locatorSourceLabel, locatorFilterColor, locatorHueForEntry, isLocatorOverlay, applyMapFilter, ensureDecodeLocatorMarker, pruneLocatorEntry, rebuildDecodeContactPaths, clearMapMarkersByType, markerSearchText, navigateToMapLocator, and the source items chip row - ft8MapAddLocator now maps type="ft4" to markerType="ft4" so FT4 locators get their own distinct marker colour and filter chip - ft4.js: pass "ft4" (not "ft8") to ft8MapAddLocator Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -3986,7 +3986,7 @@ const locatorMarkers = new Map();
|
|||||||
const decodeContactPaths = new Map();
|
const decodeContactPaths = new Map();
|
||||||
let selectedMapQsoKey = null;
|
let selectedMapQsoKey = null;
|
||||||
const mapMarkers = new Set();
|
const mapMarkers = new Set();
|
||||||
const DEFAULT_MAP_SOURCE_FILTER = { ais: true, vdes: true, aprs: true, bookmark: false, ft8: true, wspr: true };
|
const DEFAULT_MAP_SOURCE_FILTER = { ais: true, vdes: true, aprs: true, bookmark: false, ft8: true, ft4: true, wspr: true };
|
||||||
const mapFilter = { ...DEFAULT_MAP_SOURCE_FILTER };
|
const mapFilter = { ...DEFAULT_MAP_SOURCE_FILTER };
|
||||||
const mapLocatorFilter = { phase: "band", bands: new Set() };
|
const mapLocatorFilter = { phase: "band", bands: new Set() };
|
||||||
let mapSearchFilter = "";
|
let mapSearchFilter = "";
|
||||||
@@ -4163,7 +4163,7 @@ function ensureVdesMarker(key, entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ensureDecodeLocatorMarker(entry) {
|
function ensureDecodeLocatorMarker(entry) {
|
||||||
if (!aprsMap || !entry || entry.marker || !entry.grid || (entry.sourceType !== "ft8" && entry.sourceType !== "wspr")) return;
|
if (!aprsMap || !entry || entry.marker || !entry.grid || (entry.sourceType !== "ft8" && entry.sourceType !== "ft4" && entry.sourceType !== "wspr")) return;
|
||||||
const bounds = maidenheadToBounds(entry.grid);
|
const bounds = maidenheadToBounds(entry.grid);
|
||||||
if (!bounds) return;
|
if (!bounds) return;
|
||||||
const count = Math.max(entry.stationDetails?.size || 0, entry.stations?.size || 0, 1);
|
const count = Math.max(entry.stationDetails?.size || 0, entry.stations?.size || 0, 1);
|
||||||
@@ -4245,7 +4245,7 @@ function pruneAisEntry(key, entry, cutoffMs) {
|
|||||||
|
|
||||||
function pruneLocatorEntry(key, entry, cutoffMs) {
|
function pruneLocatorEntry(key, entry, cutoffMs) {
|
||||||
const canRenderMap = !!aprsMap && !decodeHistoryReplayActive;
|
const canRenderMap = !!aprsMap && !decodeHistoryReplayActive;
|
||||||
if (!entry || (entry.sourceType !== "ft8" && entry.sourceType !== "wspr")) return true;
|
if (!entry || (entry.sourceType !== "ft8" && entry.sourceType !== "ft4" && entry.sourceType !== "wspr")) return true;
|
||||||
if (!(entry.allStationDetails instanceof Map)) {
|
if (!(entry.allStationDetails instanceof Map)) {
|
||||||
entry.allStationDetails = entry.stationDetails instanceof Map
|
entry.allStationDetails = entry.stationDetails instanceof Map
|
||||||
? new Map(entry.stationDetails)
|
? new Map(entry.stationDetails)
|
||||||
@@ -4330,6 +4330,7 @@ function pruneMapHistory() {
|
|||||||
function locatorSourceLabel(type) {
|
function locatorSourceLabel(type) {
|
||||||
if (type === "bookmark") return "Bookmarks";
|
if (type === "bookmark") return "Bookmarks";
|
||||||
if (type === "wspr") return "WSPR";
|
if (type === "wspr") return "WSPR";
|
||||||
|
if (type === "ft4") return "FT4";
|
||||||
return "FT8";
|
return "FT8";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4345,7 +4346,7 @@ function locatorFilterColor(type) {
|
|||||||
const light = lightTheme ? 42 : 56;
|
const light = lightTheme ? 42 : 56;
|
||||||
const hue = type === "bookmark"
|
const hue = type === "bookmark"
|
||||||
? hues.bookmark
|
? hues.bookmark
|
||||||
: (type === "wspr" ? hues.wspr : hues.ft8);
|
: (type === "wspr" ? hues.wspr : (type === "ft4" ? hues.ft4 : hues.ft8));
|
||||||
return `hsl(${hue.toFixed(1)} ${sat}% ${light}%)`;
|
return `hsl(${hue.toFixed(1)} ${sat}% ${light}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4470,6 +4471,7 @@ function locatorThemeHues() {
|
|||||||
return {
|
return {
|
||||||
bookmark: wrapHue(baseHue),
|
bookmark: wrapHue(baseHue),
|
||||||
ft8: wrapHue(peakHue),
|
ft8: wrapHue(peakHue),
|
||||||
|
ft4: wrapHue(peakHue + 30),
|
||||||
wspr: wrapHue((waveHue + baseHue) / 2),
|
wspr: wrapHue((waveHue + baseHue) / 2),
|
||||||
bandBase: wrapHue((baseHue * 0.65) + (peakHue * 0.35)),
|
bandBase: wrapHue((baseHue * 0.65) + (peakHue * 0.35)),
|
||||||
};
|
};
|
||||||
@@ -4519,6 +4521,7 @@ function locatorHueForEntry(entry) {
|
|||||||
}
|
}
|
||||||
if (entry?.sourceType === "bookmark") return hues.bookmark;
|
if (entry?.sourceType === "bookmark") return hues.bookmark;
|
||||||
if (entry?.sourceType === "wspr") return hues.wspr;
|
if (entry?.sourceType === "wspr") return hues.wspr;
|
||||||
|
if (entry?.sourceType === "ft4") return hues.ft4;
|
||||||
return hues.ft8;
|
return hues.ft8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4785,7 +4788,7 @@ function setSelectedLocatorMarker(marker) {
|
|||||||
|
|
||||||
function isLocatorOverlay(marker) {
|
function isLocatorOverlay(marker) {
|
||||||
const type = marker?.__trxType;
|
const type = marker?.__trxType;
|
||||||
return type === "bookmark" || type === "ft8" || type === "wspr";
|
return type === "bookmark" || type === "ft8" || type === "ft4" || type === "wspr";
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendLocatorOverlayToBack(marker) {
|
function sendLocatorOverlayToBack(marker) {
|
||||||
@@ -4916,7 +4919,7 @@ function rebuildMapLocatorFilters() {
|
|||||||
for (const entry of locatorMarkers.values()) {
|
for (const entry of locatorMarkers.values()) {
|
||||||
const sourceType = entry?.sourceType;
|
const sourceType = entry?.sourceType;
|
||||||
if (!sourceType) continue;
|
if (!sourceType) continue;
|
||||||
if ((sourceType === "ft8" || sourceType === "wspr") && !entry?.visibleInHistoryWindow) continue;
|
if ((sourceType === "ft8" || sourceType === "ft4" || sourceType === "wspr") && !entry?.visibleInHistoryWindow) continue;
|
||||||
availableSources.add(sourceType);
|
availableSources.add(sourceType);
|
||||||
const meta = entry?.bandMeta instanceof Map ? entry.bandMeta : new Map();
|
const meta = entry?.bandMeta instanceof Map ? entry.bandMeta : new Map();
|
||||||
for (const [label, hz] of meta.entries()) {
|
for (const [label, hz] of meta.entries()) {
|
||||||
@@ -4944,7 +4947,7 @@ function rebuildMapLocatorFilters() {
|
|||||||
if (!bandMap.has(key)) mapLocatorFilter.bands.delete(key);
|
if (!bandMap.has(key)) mapLocatorFilter.bands.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceItems = ["ais", "vdes", "aprs", "bookmark", "ft8", "wspr"]
|
const sourceItems = ["ais", "vdes", "aprs", "bookmark", "ft8", "ft4", "wspr"]
|
||||||
.filter((key) => availableSources.has(key))
|
.filter((key) => availableSources.has(key))
|
||||||
.map((key) => ({
|
.map((key) => ({
|
||||||
key,
|
key,
|
||||||
@@ -4986,7 +4989,7 @@ function markerPassesLocatorFilters(marker) {
|
|||||||
|
|
||||||
function markerSearchText(marker) {
|
function markerSearchText(marker) {
|
||||||
const type = marker?.__trxType;
|
const type = marker?.__trxType;
|
||||||
if (type === "bookmark" || type === "ft8" || type === "wspr") {
|
if (type === "bookmark" || type === "ft8" || type === "ft4" || type === "wspr") {
|
||||||
const entry = locatorEntryForMarker(marker);
|
const entry = locatorEntryForMarker(marker);
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (entry?.grid) parts.push(entry.grid);
|
if (entry?.grid) parts.push(entry.grid);
|
||||||
@@ -5132,7 +5135,7 @@ window.clearMapMarkersByType = function(type) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "ft8" || type === "wspr") {
|
if (type === "ft8" || type === "ft4" || type === "wspr") {
|
||||||
const prefix = `${type}:`;
|
const prefix = `${type}:`;
|
||||||
for (const [key, entry] of locatorMarkers.entries()) {
|
for (const [key, entry] of locatorMarkers.entries()) {
|
||||||
if (!key.startsWith(prefix)) continue;
|
if (!key.startsWith(prefix)) continue;
|
||||||
@@ -5379,7 +5382,7 @@ function initAprsMap() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marker.__trxType === "bookmark" || marker.__trxType === "ft8" || marker.__trxType === "wspr") {
|
if (marker.__trxType === "bookmark" || marker.__trxType === "ft8" || marker.__trxType === "ft4" || marker.__trxType === "wspr") {
|
||||||
const center = locatorMarkerCenter(marker);
|
const center = locatorMarkerCenter(marker);
|
||||||
if (center) {
|
if (center) {
|
||||||
setSelectedLocatorMarker(marker);
|
setSelectedLocatorMarker(marker);
|
||||||
@@ -5618,10 +5621,10 @@ window.navigateToMapLocator = function(grid, preferredType = null) {
|
|||||||
sizeAprsMapToViewport();
|
sizeAprsMapToViewport();
|
||||||
if (!aprsMap) return false;
|
if (!aprsMap) return false;
|
||||||
|
|
||||||
const pref = preferredType === "wspr" ? "wspr" : (preferredType === "ft8" ? "ft8" : null);
|
const pref = preferredType === "wspr" ? "wspr" : (preferredType === "ft4" ? "ft4" : (preferredType === "ft8" ? "ft8" : null));
|
||||||
const keys = pref
|
const keys = pref
|
||||||
? [`${pref}:${normalizedGrid}`, `ft8:${normalizedGrid}`, `wspr:${normalizedGrid}`, `bookmark:${normalizedGrid}`]
|
? [`${pref}:${normalizedGrid}`, `ft8:${normalizedGrid}`, `ft4:${normalizedGrid}`, `wspr:${normalizedGrid}`, `bookmark:${normalizedGrid}`]
|
||||||
: [`ft8:${normalizedGrid}`, `wspr:${normalizedGrid}`, `bookmark:${normalizedGrid}`];
|
: [`ft8:${normalizedGrid}`, `ft4:${normalizedGrid}`, `wspr:${normalizedGrid}`, `bookmark:${normalizedGrid}`];
|
||||||
let entry = null;
|
let entry = null;
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
entry = locatorMarkers.get(key);
|
entry = locatorMarkers.get(key);
|
||||||
@@ -6160,6 +6163,7 @@ function applyMapFilter() {
|
|||||||
(type === "vdes" && mapFilter.vdes) ||
|
(type === "vdes" && mapFilter.vdes) ||
|
||||||
(type === "aprs" && mapFilter.aprs) ||
|
(type === "aprs" && mapFilter.aprs) ||
|
||||||
(type === "ft8" && mapFilter.ft8) ||
|
(type === "ft8" && mapFilter.ft8) ||
|
||||||
|
(type === "ft4" && mapFilter.ft4) ||
|
||||||
(type === "wspr" && mapFilter.wspr)
|
(type === "wspr" && mapFilter.wspr)
|
||||||
);
|
);
|
||||||
const onMap = aprsMap.hasLayer(marker);
|
const onMap = aprsMap.hasLayer(marker);
|
||||||
@@ -6271,7 +6275,7 @@ function rebuildDecodeContactPaths() {
|
|||||||
const stationLocators = new Map();
|
const stationLocators = new Map();
|
||||||
const directedMessages = [];
|
const directedMessages = [];
|
||||||
for (const entry of locatorMarkers.values()) {
|
for (const entry of locatorMarkers.values()) {
|
||||||
if (!entry || (entry.sourceType !== "ft8" && entry.sourceType !== "wspr")) continue;
|
if (!entry || (entry.sourceType !== "ft8" && entry.sourceType !== "ft4" && entry.sourceType !== "wspr")) continue;
|
||||||
const grid = String(entry.grid || "").trim().toUpperCase();
|
const grid = String(entry.grid || "").trim().toUpperCase();
|
||||||
if (!grid || !(entry.stationDetails instanceof Map)) continue;
|
if (!grid || !(entry.stationDetails instanceof Map)) continue;
|
||||||
for (const detail of entry.stationDetails.values()) {
|
for (const detail of entry.stationDetails.values()) {
|
||||||
@@ -6520,7 +6524,7 @@ window.syncBookmarkMapLocators = function(bookmarks) {
|
|||||||
|
|
||||||
window.ft8MapAddLocator = function(message, grids, type = "ft8", station = null, details = null) {
|
window.ft8MapAddLocator = function(message, grids, type = "ft8", station = null, details = null) {
|
||||||
if (!Array.isArray(grids) || grids.length === 0) return;
|
if (!Array.isArray(grids) || grids.length === 0) return;
|
||||||
const markerType = type === "wspr" ? "wspr" : "ft8";
|
const markerType = type === "wspr" ? "wspr" : (type === "ft4" ? "ft4" : "ft8");
|
||||||
const unique = [...new Set(grids.map((g) => String(g).toUpperCase()))];
|
const unique = [...new Set(grids.map((g) => String(g).toUpperCase()))];
|
||||||
const stationId = station && String(station).trim() ? String(station).trim().toUpperCase() : "";
|
const stationId = station && String(station).trim() ? String(station).trim().toUpperCase() : "";
|
||||||
const locatorDetails = new Map();
|
const locatorDetails = new Map();
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ window.onServerFt4Batch = function(messages) {
|
|||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const next = normalizeServerFt4Message(msg);
|
const next = normalizeServerFt4Message(msg);
|
||||||
if (next.grids.length > 0 && window.ft8MapAddLocator) {
|
if (next.grids.length > 0 && window.ft8MapAddLocator) {
|
||||||
window.ft8MapAddLocator(next.raw, next.grids, "ft8", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
window.ft8MapAddLocator(next.raw, next.grids, "ft4", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
||||||
}
|
}
|
||||||
next.history._tsMs = Number.isFinite(next.history?.ts_ms) ? Number(next.history.ts_ms) : Date.now();
|
next.history._tsMs = Number.isFinite(next.history?.ts_ms) ? Number(next.history.ts_ms) : Date.now();
|
||||||
normalized.push(next.history);
|
normalized.push(next.history);
|
||||||
@@ -183,7 +183,7 @@ window.onServerFt4 = function(msg) {
|
|||||||
if (ft4Status) ft4Status.textContent = ft4Paused ? "Paused" : "Receiving";
|
if (ft4Status) ft4Status.textContent = ft4Paused ? "Paused" : "Receiving";
|
||||||
const next = normalizeServerFt4Message(msg);
|
const next = normalizeServerFt4Message(msg);
|
||||||
if (next.grids.length > 0 && window.ft8MapAddLocator) {
|
if (next.grids.length > 0 && window.ft8MapAddLocator) {
|
||||||
window.ft8MapAddLocator(next.raw, next.grids, "ft8", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
window.ft8MapAddLocator(next.raw, next.grids, "ft4", next.station, { ...msg, freq_hz: next.rfHz, locator_details: next.locatorDetails });
|
||||||
}
|
}
|
||||||
addFt4Message(next.history);
|
addFt4Message(next.history);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user