Filled, with a hairline white edge — the same trick the home marker uses. A ring is an outline drawn over whatever is beneath it, and eight pixels of one over the satellite imagery was barely visible, which is the whole reason the layer exists to be looked at. Its own colour, in its own key, beside the switch that turns the layer on. Separate from the decode colour deliberately: that one exists because the band palette disappears over some basemaps, and cyan over a pale sea has exactly the same problem for exactly the same reason. One control for both would have forced the two layers into a single colour, which is the distinction it took a shape to draw in the first place. The picker only appears while the layer is on, since it configures that layer and nothing else, and it is a portable UI pref like the other map colours, so it travels with data/.
155 lines
9.2 KiB
TypeScript
155 lines
9.2 KiB
TypeScript
// Portable UI preferences.
|
|
//
|
|
// A handful of small UI prefs historically lived only in the WebView's
|
|
// localStorage, so they did NOT travel when the operator copied the OpsLog
|
|
// folder (data/) to another machine. These helpers mirror them into the DB
|
|
// settings table (ui.* keys, like the grid columns) so the whole setup is
|
|
// identical after a copy.
|
|
import { GetUIPref, SetUIPref, LogUIError } from '../../wailsjs/go/main/App';
|
|
|
|
// Keys that must travel with data/ (DB is the portable source of truth; the
|
|
// localStorage copy is just a fast, synchronous cache).
|
|
const PORTABLE_KEYS = [
|
|
'opslog.theme', // UI colour theme (light-warm / dark-warm / graphite / high-contrast / auto)
|
|
'hamlog.qsoLimit', // QSO list page size
|
|
'bandmap.side', // band map docked left / right
|
|
'bandmap.show', // band map open / closed
|
|
'opslog.autofocusWB', // auto-focus Worked-before
|
|
'hamlog.filterPresets', // Filter Builder saved presets
|
|
'opslog.showRotor', // rotor compass shown next to the keyers
|
|
'opslog.rotorCompact', // rotor widget: dial + SP/LP only, no quick-turn buttons or Stop
|
|
'opslog.decodesGridMap',// FT decodes tab: grid-square map shown beside the table
|
|
'opslog.showChaseNew', // Chase New panel shown next to the keyers
|
|
'opslog.showAmpWidget', // amplifier widget shown next to the keyers
|
|
'opslog.ampSel.widget', // which amplifier that widget shows ("all" or an amp id)
|
|
'opslog.showBeamOnMap', // antenna beam lobe drawn on the Main map
|
|
'opslog.startEqualsEnd',// log TIME_ON = TIME_OFF (QSO time = completion time)
|
|
'opslog.showQsoRate', // QSO-rate meter (10/60 min) shown in the header
|
|
'opslog.catModeBeforeFreq', // send CAT mode before frequency (older rigs)
|
|
'opslog.bandMapBands', // bands shown side-by-side in the Band Map tab
|
|
'opslog.mapAutoZoomDX', // Main map: auto-zoom to the DX (vs free pan/zoom)
|
|
'opslog.mapView', // Main map: remembered free-pan view (lat/lon/zoom)
|
|
// The same, for the FT decodes map and the grid-square map: a view an
|
|
// operator set up is theirs, and it should follow the folder like the rest.
|
|
'opslog.ftMapView', 'opslog.gridMapView', 'opslog.satMapView',
|
|
'opslog.lookupOnBlur', // run the callsign lookup on blur instead of while typing
|
|
'opslog.groupDigitalSlots', // matrix + cluster: all digital modes count as ONE (DXCC-style) instead of per-mode slots
|
|
'opslog.matrixDigiMode', // band matrix: which digital row it opens on ('' = DIGI, the group)
|
|
'opslog.clusterShowFilters', // cluster filter sidebar shown (tab + Main pane)
|
|
// One imagery choice per map — world, grid squares, FT map, satellites.
|
|
'opslog.mapBasemap', 'opslog.gridMapBase', 'opslog.ftmapBase', 'opslog.satMapBase',
|
|
'opslog.dateFormat', // how dates are DISPLAYED (iso / fr / us); storage stays ISO
|
|
'opslog.mapGreyline', // world map: grey line (day/night terminator) shown
|
|
'opslog.awardRefSort', 'opslog.awardRefSortDir', // award reference table: sort column and direction
|
|
// Cluster filter selections — restored on reopen.
|
|
'opslog.clusterFilterSource', 'opslog.clusterGroup', 'opslog.clusterBands',
|
|
'opslog.clusterLockBand', 'opslog.clusterLockMode', 'opslog.clusterStatusFilter',
|
|
'opslog.clusterModeFilter', 'opslog.clusterSearch', 'opslog.clusterHideWorked',
|
|
'opslog.distanceMiles', // distances shown in statute miles rather than km
|
|
'opslog.iaruRegion', // IARU region (1/2/3) — band edges and segments on the band maps
|
|
'opslog.chasePota', // show POTA references and the NEW POTA marker on spots
|
|
'opslog.chaseSota', // show SOTA references on spots
|
|
// The other chase switches. In the DB as well as locally because AUTO-CALL
|
|
// reads them: what the operator does not hunt is not something to transmit
|
|
// for, and the backend cannot see localStorage.
|
|
'opslog.chasePfx', 'opslog.chaseCounty', 'opslog.chaseState', 'opslog.chaseGrids',
|
|
'opslog.activeTab', // last selected tab
|
|
'opslog.mainSplit', // Main tab: width share of the left pane (percent) — legacy, read once to seed mainShares
|
|
'opslog.mainShares', // Main tab: column shares per column count, as {2:[..],3:[..],4:[..]}
|
|
'opslog.mainQuad', // Main tab: the 2x2 cross, as {col,row} percentages
|
|
'opslog.clusterMuteWorked', // cluster/band map: no colour or badge on worked spots
|
|
'opslog.clusterSlotHighlight', // cluster/band map: colour calls not worked on this band+mode
|
|
'opslog.bandMapWidth', // docked band map: column width (px)
|
|
'opslog.satSideWidth', 'opslog.satSideShown', // Satellites tab: readout column width, and whether it is shown
|
|
'opslog.satSkyShown', // Satellites tab: the polar sky plot
|
|
'opslog.bandMapTabWidth', // Band map tab: shared card width (px)
|
|
'opslog.bandMapZoom', // band map zoom (px/kHz step) remembered per band, as one {band: index} map
|
|
'opslog.decodeColWidths', // FT decodes table: per-column widths (px), as one {col: px} map
|
|
'opslog.clusterMacros', // cluster console: the twelve named command buttons
|
|
'opslog.clusterHideSpots', // cluster console: hide the DX spot flood so replies are readable
|
|
'opslog.clusterConsoleFollow', // cluster console: keep the view pinned to the newest line
|
|
'opslog.gridMapColorConfirmed', 'opslog.gridMapColorWorked', // grid map: chosen fills (empty = follow the theme)
|
|
'opslog.ftMapColour', // FT map: one colour for every arc (empty = the band palette)
|
|
'opslog.ftMapHeardColour', // FT map: the who-hears-me diamonds (empty = the default cyan)
|
|
// NOTE: 'hamlog.awardColsShown' and the grid column layouts are NOT listed here.
|
|
// They are handled by lib/gridPrefs, which scopes the localStorage cache PER
|
|
// PROFILE and mirrors to the DB (already per-profile) itself — mirroring them
|
|
// through this global path would fight that per-profile scoping.
|
|
];
|
|
|
|
// syncPortablePrefs reconciles the DB with the local cache at startup:
|
|
// • DB has a value → copy it into localStorage (a copied folder restores it);
|
|
// • DB empty, local set → seed the DB from local (migrates the current value).
|
|
// Call it BEFORE the first render so the app's synchronous localStorage reads
|
|
// already see the portable values — no per-component hydration needed.
|
|
export async function syncPortablePrefs(): Promise<void> {
|
|
await Promise.all(PORTABLE_KEYS.map(async (key) => {
|
|
try {
|
|
const db = await GetUIPref(key);
|
|
const local = localStorage.getItem(key);
|
|
if (db != null && db !== '') {
|
|
if (db !== local) { try { localStorage.setItem(key, db); } catch { /* quota */ } }
|
|
} else if (local != null && local !== '') {
|
|
await SetUIPref(key, local);
|
|
}
|
|
} catch { /* backend not ready / no DB — keep whatever is in localStorage */ }
|
|
}));
|
|
}
|
|
|
|
// writeUiPrefDebounced is writeUiPref for a value that changes AS SOMEBODY
|
|
// TYPES.
|
|
//
|
|
// The local cache is written at once, because that is what the interface reads
|
|
// back and it costs nothing. The DATABASE write is held until the typing stops:
|
|
// writeUiPref crosses into Go and writes a row, and doing that per character in
|
|
// a text box is a round trip per keystroke — twenty-four boxes of cluster
|
|
// macros was exactly that, and it showed as characters appearing after the
|
|
// finger had left the key.
|
|
//
|
|
// Pending writes are flushed when the page goes away, so a value typed and
|
|
// immediately followed by a close is not lost.
|
|
const pendingPrefs = new Map<string, { value: string; timer: number }>();
|
|
|
|
export function writeUiPrefDebounced(key: string, value: string, ms = 400): void {
|
|
try { localStorage.setItem(key, value); } catch { /* quota / private mode */ }
|
|
const prev = pendingPrefs.get(key);
|
|
if (prev) window.clearTimeout(prev.timer);
|
|
const timer = window.setTimeout(() => {
|
|
pendingPrefs.delete(key);
|
|
SetUIPref(key, value).catch((e: any) => {
|
|
try { LogUIError('ui pref', 'could not store ' + key + ': ' + String(e?.message ?? e), ''); } catch { /* nothing left to try */ }
|
|
});
|
|
}, ms);
|
|
pendingPrefs.set(key, { value, timer });
|
|
}
|
|
|
|
// flushUiPrefs writes every pending value immediately.
|
|
export function flushUiPrefs(): void {
|
|
for (const [key, p] of pendingPrefs) {
|
|
window.clearTimeout(p.timer);
|
|
SetUIPref(key, p.value).catch(() => { /* the local cache still holds it */ });
|
|
}
|
|
pendingPrefs.clear();
|
|
}
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('beforeunload', flushUiPrefs);
|
|
// Closing the app does not always fire beforeunload in a WebView; a hidden
|
|
// page is the earlier and more reliable signal.
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.visibilityState === 'hidden') flushUiPrefs();
|
|
});
|
|
}
|
|
|
|
// writeUiPref write-throughs a value to the local cache AND the portable DB.
|
|
// Use it everywhere these keys are written instead of localStorage.setItem.
|
|
export function writeUiPref(key: string, value: string): void {
|
|
try { localStorage.setItem(key, value); } catch { /* quota / private mode */ }
|
|
SetUIPref(key, value).catch((e: any) => {
|
|
// The cache still holds it, so the interface behaves — but the BACKEND
|
|
// reads some of these keys too (digital-mode grouping decides how the
|
|
// cluster judges a slot). A write that reaches localStorage and not the
|
|
// database makes the two disagree, and used to do so in silence.
|
|
try { LogUIError("ui pref", "could not store " + key + ": " + String(e?.message ?? e), ""); } catch {}
|
|
});
|
|
}
|