Skip to content
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"prepare": "husky || true"
},
"dependencies": {
"@hamset/maidenhead-locator": "^0.2.1",
"axios": "^1.6.2",
"compression": "^1.7.4",
"cors": "^2.8.5",
Expand Down
49 changes: 35 additions & 14 deletions scripts/generate-translations.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,22 @@ const universal = {
'weather.unit.mi': 'mi',
'weather.unit.mph': 'mph',
// Wind directions are universal abbreviations
'weather.wind.N': 'N', 'weather.wind.NNE': 'NNE', 'weather.wind.NE': 'NE', 'weather.wind.ENE': 'ENE',
'weather.wind.E': 'E', 'weather.wind.ESE': 'ESE', 'weather.wind.SE': 'SE', 'weather.wind.SSE': 'SSE',
'weather.wind.S': 'S', 'weather.wind.SSW': 'SSW', 'weather.wind.SW': 'SW', 'weather.wind.WSW': 'WSW',
'weather.wind.W': 'W', 'weather.wind.WNW': 'WNW', 'weather.wind.NW': 'NW', 'weather.wind.NNW': 'NNW',
'weather.wind.N': 'N',
'weather.wind.NNE': 'NNE',
'weather.wind.NE': 'NE',
'weather.wind.ENE': 'ENE',
'weather.wind.E': 'E',
'weather.wind.ESE': 'ESE',
'weather.wind.SE': 'SE',
'weather.wind.SSE': 'SSE',
'weather.wind.S': 'S',
'weather.wind.SSW': 'SSW',
'weather.wind.SW': 'SW',
'weather.wind.WSW': 'WSW',
'weather.wind.W': 'W',
'weather.wind.WNW': 'WNW',
'weather.wind.NW': 'NW',
'weather.wind.NNW': 'NNW',
// Plugin layer names that are proper nouns / brand names
'plugins.layers.wspr.name': 'WSPR',
'plugins.layers.rbn.title': 'RBN',
Expand Down Expand Up @@ -226,7 +238,8 @@ const translations = {
'station.settings.dx.custom.port': 'Port',
'station.settings.dx.custom.port.placeholder': '7300',
'station.settings.dx.custom.title': '📡 Eigener Telnet-Server',
'station.settings.dx.custom.warning': '⚠️ Eigener Telnet erfordert Selbsthosting (Pi/lokal). Cloud-Hosting (Railway/openhamclock.app) blockiert ausgehende Telnet-Verbindungen.',
'station.settings.dx.custom.warning':
'⚠️ Eigener Telnet erfordert Selbsthosting (Pi/lokal). Cloud-Hosting (Railway/openhamclock.app) blockiert ausgehende Telnet-Verbindungen.',
'station.settings.headerSize': 'Rufzeichengröße',
'station.settings.layers.noLayers': 'Keine Kartenebenen verfügbar',
'station.settings.layers.opacity': 'Deckkraft',
Expand All @@ -249,14 +262,16 @@ const translations = {
'station.settings.tab3.title': '⛊ Satelliten',
'station.settings.timezone.auto': 'Auto (Browser-Standard)',
'station.settings.timezone.currentDefault': ' Aktuell wird der Browser-Standard verwendet.',
'station.settings.timezone.describe': 'Setzen Sie dies, wenn Ihre Ortszeit falsch angezeigt wird (z.B. gleich wie UTC). Datenschutzbrowser wie Librewolf können Ihre Zeitzone verschleiern.',
'station.settings.timezone.describe':
'Setzen Sie dies, wenn Ihre Ortszeit falsch angezeigt wird (z.B. gleich wie UTC). Datenschutzbrowser wie Librewolf können Ihre Zeitzone verschleiern.',
'station.settings.timezone.group.africa': 'Afrika',
'station.settings.timezone.group.asiaPacific': 'Asien & Pazifik',
'station.settings.timezone.group.europe': 'Europa',
'station.settings.timezone.group.northAmerica': 'Nordamerika',
'station.settings.timezone.group.other': 'Sonstige',
'station.settings.timezone.group.southAmerica': 'Südamerika',
'station.settings.tip.env': '💡 Tipp: Für permanente Konfiguration <envExample>.env.example</envExample> nach <env>.env</env> kopieren und CALLSIGN und LOCATOR setzen',
'station.settings.tip.env':
'💡 Tipp: Für permanente Konfiguration <envExample>.env.example</envExample> nach <env>.env</env> kopieren und CALLSIGN und LOCATOR setzen',
'weather.clouds': '☁️ Wolken',
'weather.condition.0': 'Klarer Himmel',
'weather.condition.1': 'Überwiegend klar',
Expand Down Expand Up @@ -307,7 +322,8 @@ const translations = {
es: {
'plugins.layers.floods.description': 'Inundaciones y tormentas severas activas en todo el mundo vía NASA EONET',
'plugins.layers.floods.name': 'Inundaciones y Tormentas',
'plugins.layers.wildfires.description': 'Incendios forestales activos en todo el mundo vía detección satelital NASA EONET',
'plugins.layers.wildfires.description':
'Incendios forestales activos en todo el mundo vía detección satelital NASA EONET',
'plugins.layers.wildfires.name': 'Incendios Forestales',
},

Expand Down Expand Up @@ -389,7 +405,7 @@ const translations = {
'plugins.layers.wspr.veryWeak': 'Très faible (< -20 dB)',
'plugins.layers.wspr.weak': 'Faible (-20 à -10 dB)',
'plugins.layers.wxradar.attribution': 'Données météo © Iowa State University Mesonet',
'plugins.layers.wxradar.description': 'Superposition radar météo NEXRAD pour l\'Amérique du Nord',
'plugins.layers.wxradar.description': "Superposition radar météo NEXRAD pour l'Amérique du Nord",
'plugins.layers.wxradar.name': 'Radar météo',
'propagation.day': 'Jour',
'propagation.estimated': 'estimé',
Expand All @@ -415,7 +431,8 @@ const translations = {
'station.settings.dx.custom.port': 'Port',
'station.settings.dx.custom.port.placeholder': '7300',
'station.settings.dx.custom.title': '📡 Serveur Telnet personnalisé',
'station.settings.dx.custom.warning': '⚠️ Le telnet personnalisé nécessite un hébergement local (Pi/local). L\'hébergement cloud (Railway/openhamclock.app) bloque les connexions telnet sortantes.',
'station.settings.dx.custom.warning':
"⚠️ Le telnet personnalisé nécessite un hébergement local (Pi/local). L'hébergement cloud (Railway/openhamclock.app) bloque les connexions telnet sortantes.",
'station.settings.layers.noLayers': 'Aucune couche disponible',
'station.settings.layers.opacity': 'Opacité',
'station.settings.layers.title': 'Couches de carte',
Expand Down Expand Up @@ -459,7 +476,7 @@ const translations = {
'weather.humidity': '💧 Humidité',
'weather.pressure': '🔵 Pression',
'weather.switchUnit': 'Passer en °{{unit}}',
'weather.today': 'Aujourd\'hui',
'weather.today': "Aujourd'hui",
'weather.uv': '☀️ UV',
'weather.visibility': '👁️ Visibilité',
'weather.wind': '💨 Vent',
Expand Down Expand Up @@ -500,7 +517,11 @@ function applyTranslations(langCode, newTranslations) {

// Sort keys alphabetically for consistency
const sorted = {};
Object.keys(merged).sort().forEach(k => { sorted[k] = merged[k]; });
Object.keys(merged)
.sort()
.forEach((k) => {
sorted[k] = merged[k];
});

fs.writeFileSync(filePath, JSON.stringify(sorted, null, 2) + '\n', 'utf8');
return added;
Expand All @@ -525,9 +546,9 @@ for (const lang of universalOnly) {

// Final report
console.log('\n--- Coverage After ---');
for (const lang of ['de','es','fr','it','ja','ko','ms','nl','pt','sl']) {
for (const lang of ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ms', 'nl', 'pt', 'sl']) {
const data = JSON.parse(fs.readFileSync(path.join(LANG_DIR, lang + '.json'), 'utf8'));
const count = Object.keys(data).length;
const pct = Math.round(count / enKeys.length * 100);
const pct = Math.round((count / enKeys.length) * 100);
console.log(`${lang.toUpperCase().padEnd(4)} ${count}/${enKeys.length} = ${pct}%`);
}
106 changes: 105 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import useLocalInstall from './hooks/app/useLocalInstall';
import useVersionCheck from './hooks/app/useVersionCheck';
import WhatsNew from './components/WhatsNew.jsx';
import { initCtyLookup } from './utils/ctyLookup.js';
import ActivateFilterManager from './components/ActivateFilterManager.jsx';

// Load DXCC entity database on app startup (non-blocking)
initCtyLookup();
Expand All @@ -60,6 +61,10 @@ const App = () => {
const [showSettings, setShowSettings] = useState(false);
const [showDXFilters, setShowDXFilters] = useState(false);
const [showPSKFilters, setShowPSKFilters] = useState(false);
const [showPotaFilters, setShowPotaFilters] = useState(false);
const [showSotaFilters, setShowSotaFilters] = useState(false);
const [showWwffFilters, setShowWwffFilters] = useState(false);
const [showWwbotaFilters, setShowWwbotaFilters] = useState(false);
const [layoutResetKey, setLayoutResetKey] = useState(0);
const [, setBandColorChangeVersion] = useState(0);
const [updateInProgress, setUpdateInProgress] = useState(false);
Expand Down Expand Up @@ -136,17 +141,36 @@ const App = () => {
toggleDXPaths,
toggleDXLabels,
togglePOTA,
togglePOTALabels,
toggleWWFF,
toggleWWFFLabels,
toggleSOTA,
toggleSOTALabels,
toggleWWBOTA,
toggleWWBOTALabels,
toggleSatellites,
togglePSKReporter,
toggleWSJTX,
toggleDXNews,
toggleAPRS,
} = useMapLayers();

const { dxFilters, setDxFilters, pskFilters, setPskFilters, mapBandFilter, setMapBandFilter } = useFilters();
const {
dxFilters,
setDxFilters,
pskFilters,
setPskFilters,
mapBandFilter,
setMapBandFilter,
potaFilters,
setPotaFilters,
sotaFilters,
setSotaFilters,
wwffFilters,
setWwffFilters,
wwbotaFilters,
setWwbotaFilters,
} = useFilters();

const { isFullscreen, handleFullscreenToggle } = useFullscreen();
const { wakeLockStatus } = useScreenWakeLock(config);
Expand Down Expand Up @@ -222,6 +246,38 @@ const App = () => {
});
}, [pskReporter.txReports, pskReporter.rxReports, pskFilters]);

function ActivateFilter(spots, filters) {
// console.log('[ActivateFilters] filters is ',filters);
if (!filters?.bands?.length && !filters?.grids?.length && !filters?.modes?.length) {
return spots.data;
}
return spots.data.filter((spot) => {
if (filters?.bands?.length && !filters.bands.includes(spot.band)) return false;
if (filters?.modes?.length && !filters.modes.includes(spot.mode)) return false;
if (filters?.grids?.length) {
const gridPrefix = spot.grid.substring(0, 2).toUpperCase();
if (!filters.grids.includes(gridPrefix)) return false;
}
return true;
});
}

const filteredPotaSpots = useMemo(() => {
return ActivateFilter(potaSpots, potaFilters);
}, [potaSpots, potaFilters]);

const filteredWwffSpots = useMemo(() => {
return ActivateFilter(wwffSpots, wwffFilters);
}, [wwffSpots, wwffFilters]);

const filteredSotaSpots = useMemo(() => {
return ActivateFilter(sotaSpots, sotaFilters);
}, [sotaSpots, sotaFilters]);

const filteredWwbotaSpots = useMemo(() => {
return ActivateFilter(wwbotaSpots, wwbotaFilters);
}, [wwbotaSpots, wwbotaFilters]);

const wsjtxMapSpots = useMemo(() => {
// Apply same age filter as panel (stored in localStorage)
let ageMinutes = 30;
Expand Down Expand Up @@ -278,6 +334,10 @@ const App = () => {
setShowSettings,
setShowDXFilters,
setShowPSKFilters,
setShowPotaFilters,
setShowSotaFilters,
setShowWwffFilters,
setShowWwbotaFilters,
handleUpdateClick,
updateInProgress,
isLocalInstall,
Expand All @@ -297,9 +357,13 @@ const App = () => {
propagation,
dxClusterData,
potaSpots,
filteredPotaSpots,
wwffSpots,
filteredWwffSpots,
sotaSpots,
filteredSotaSpots,
wwbotaSpots,
filteredWwbotaSpots,
mySpots,
dxpeditions,
contests,
Expand All @@ -315,13 +379,25 @@ const App = () => {
setMapBandFilter,
pskFilters,
setPskFilters,
potaFilters,
setPotaFilters,
sotaFilters,
setSotaFilters,
wwffFilters,
setWwffFilters,
wwbotaFilters,
setWwbotaFilters,
mapLayers,
toggleDXPaths,
toggleDXLabels,
togglePOTA,
togglePOTALabels,
toggleWWFF,
toggleWWFFLabels,
toggleSOTA,
toggleSOTALabels,
toggleWWBOTA,
toggleWWBOTALabels,
toggleSatellites,
togglePSKReporter,
toggleWSJTX,
Expand Down Expand Up @@ -384,6 +460,34 @@ const App = () => {
isOpen={showPSKFilters}
onClose={() => setShowPSKFilters(false)}
/>
<ActivateFilterManager
name="POTA"
filters={potaFilters}
onFilterChange={setPotaFilters}
isOpen={showPotaFilters}
onClose={() => setShowPotaFilters(false)}
/>
<ActivateFilterManager
name="SOTA"
filters={sotaFilters}
onFilterChange={setSotaFilters}
isOpen={showSotaFilters}
onClose={() => setShowSotaFilters(false)}
/>
<ActivateFilterManager
name="WWFF"
filters={wwffFilters}
onFilterChange={setWwffFilters}
isOpen={showWwffFilters}
onClose={() => setShowWwffFilters(false)}
/>
<ActivateFilterManager
name="WWBOTA"
filters={wwbotaFilters}
onFilterChange={setWwbotaFilters}
isOpen={showWwbotaFilters}
onClose={() => setShowWwbotaFilters(false)}
/>
<WhatsNew />
</div>
);
Expand Down
Loading