Makes the webpack build use relative paths to allow for deploying on web subdirectories
Some checks are pending
build-docker / Build Image (push) Waiting to run

This commit is contained in:
mrkmntal 2026-04-08 21:31:56 -04:00
commit ea8c3bf602
10 changed files with 74 additions and 36 deletions

View file

@ -1,6 +1,7 @@
import largeIcon from './icons/icons-large.mjs';
import smallIcon from './icons/icons-small.mjs';
import hourlyIcon from './icons/icons-hourly.mjs';
import { withBasePath } from './utils/base-path.mjs';
const getWeatherGovTokenFromWmoCode = (code) => {
switch (Number(code)) {
@ -44,7 +45,7 @@ const getWeatherGovTokenFromWmoCode = (code) => {
}
};
const buildSyntheticIconUrl = (code, isDaytime = true) => `/icons/land/${isDaytime ? 'day' : 'night'}/${getWeatherGovTokenFromWmoCode(code)}`;
const buildSyntheticIconUrl = (code, isDaytime = true) => withBasePath(`icons/land/${isDaytime ? 'day' : 'night'}/${getWeatherGovTokenFromWmoCode(code)}`);
const getLargeIconFromWmoCode = (code, isDaytime = true) => largeIcon(buildSyntheticIconUrl(code, isDaytime), !isDaytime);
const getSmallIconFromWmoCode = (code, isDaytime = true) => smallIcon(buildSyntheticIconUrl(code, isDaytime), !isDaytime);

View file

@ -3,6 +3,7 @@ import STATUS from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import { debugFlag } from './utils/debug.mjs';
import { withBasePath } from './utils/base-path.mjs';
const STORIES_PER_PAGE = 2;
const PAGE_DURATION_MS = 9000;
@ -17,7 +18,7 @@ class LinuxNews extends WeatherDisplay {
if (!super.getData(weatherParameters, refresh)) return;
try {
const response = await safeJson('/api/linux-news', {
const response = await safeJson(withBasePath('api/linux-news'), {
retryCount: 0,
});

View file

@ -4,6 +4,7 @@ import STATUS from './status.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import { debugFlag } from './utils/debug.mjs';
import { withBasePath } from './utils/base-path.mjs';
const LINES_PER_PAGE = 4;
const PAGE_DURATION_MS = 7000;
@ -19,7 +20,7 @@ class ServerObservations extends WeatherDisplay {
try {
// Fetch server info from the API
const response = await safeJson('/api/server-info', {
const response = await safeJson(withBasePath('api/server-info'), {
retryCount: 0,
});

View file

@ -0,0 +1,25 @@
const normalizeBasePath = (pathname) => {
if (!pathname) return '/';
if (pathname.endsWith('/index.html')) {
const trimmed = pathname.slice(0, -'index.html'.length);
return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
}
if (pathname.endsWith('/')) return pathname;
const lastSlash = pathname.lastIndexOf('/');
if (lastSlash === -1) return '/';
return `${pathname.slice(0, lastSlash + 1)}`;
};
const getBasePath = () => normalizeBasePath(window.location.pathname);
const withBasePath = (relativePath = '') => {
const sanitizedPath = relativePath.replace(/^\/+/, '');
const basePath = getBasePath();
if (basePath === '/') return `/${sanitizedPath}`;
return `${basePath}${sanitizedPath}`;
};
export {
getBasePath,
withBasePath,
};

View file

@ -1,4 +1,5 @@
import { rewriteUrl } from './url-rewrite.mjs';
import { withBasePath } from './base-path.mjs';
// Clear cache utility for client-side use
const clearCacheEntry = async (url, baseUrl = '') => {
@ -15,7 +16,7 @@ const clearCacheEntry = async (url, baseUrl = '') => {
}
// Call the cache clear endpoint
const fetchUrl = baseUrl ? `${baseUrl}/cache${cachePath}` : `/cache${cachePath}`;
const fetchUrl = baseUrl ? `${baseUrl}/cache${cachePath}` : withBasePath(`cache${cachePath}`);
const response = await fetch(fetchUrl, {
method: 'DELETE',
});

View file

@ -1,5 +1,7 @@
// Data loader utility for fetching JSON data with cache-busting
import { withBasePath } from './base-path.mjs';
let dataCache = {};
// Load data with version-based cache busting
@ -9,7 +11,7 @@ const loadData = async (dataType, version = '') => {
}
try {
const url = `/data/${dataType}.json${version ? `?_=${version}` : ''}`;
const url = withBasePath(`data/${dataType}.json${version ? `?_=${version}` : ''}`);
const response = await fetch(url);
if (!response.ok) {

View file

@ -3,12 +3,13 @@ import { loadData } from './data-loader.mjs';
import { getSmallIconFromWmoCode } from '../icons.mjs';
import { getOpenMeteoObservationSnapshot } from './weather.mjs';
import { temperature } from './units.mjs';
import { withBasePath } from './base-path.mjs';
const getBaseMapUrl = () => (window.WS4KP_SERVER_AVAILABLE
? '/arcgis-server/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}'
? withBasePath('arcgis-server/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}')
: 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}');
const getBoundaryMapUrl = () => (window.WS4KP_SERVER_AVAILABLE
? '/arcgis-services/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}'
? withBasePath('arcgis-services/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}')
: 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}');
const DEFAULT_MAX_NEARBY_MARKERS = 7;
const MIN_CITY_DISTANCE_METERS = 25000;

View file

@ -11,6 +11,7 @@
import { safeJson } from './fetch.mjs';
import { debugFlag } from './debug.mjs';
import { withBasePath } from './base-path.mjs';
/**
* Parse MapClick date format to JavaScript Date
@ -178,7 +179,7 @@ const convertMapClickIcon = (weatherImage) => {
return null;
}
return `/icons/land/${timeOfDay}/${condition}?size=medium`;
return withBasePath(`icons/land/${timeOfDay}/${condition}?size=medium`);
};
/**

View file

@ -1,12 +1,15 @@
import { withBasePath } from './base-path.mjs';
const THEME_STORAGE_KEY = 'settings-theme-select';
const DEFAULT_THEME = 'default';
const BUILTIN_ASSETS = {
background1: '../images/backgrounds/1.png',
background1Chart: '../images/backgrounds/1-chart.png',
background2: '../images/backgrounds/2.png',
background3: '../images/backgrounds/3.png',
background4: '../images/backgrounds/4.png',
background5: '../images/backgrounds/5.png',
background1: 'images/backgrounds/1.png',
background1Chart: 'images/backgrounds/1-chart.png',
background2: 'images/backgrounds/2.png',
background3: 'images/backgrounds/3.png',
background4: 'images/backgrounds/4.png',
background5: 'images/backgrounds/5.png',
logoCorner: 'images/logos/logo-corner.png',
};
@ -20,33 +23,33 @@ const getStoredTheme = () => {
const getThemeAssetUrl = (themeName, assetKey) => {
if (themeName === DEFAULT_THEME) {
return BUILTIN_ASSETS[assetKey];
return withBasePath(BUILTIN_ASSETS[assetKey]);
}
const themeAssetAvailability = getThemeAssets()[themeName] ?? {};
if (!themeAssetAvailability[assetKey]) {
return BUILTIN_ASSETS[assetKey];
return withBasePath(BUILTIN_ASSETS[assetKey]);
}
switch (assetKey) {
case 'background1':
return `../themes/${themeName}/1.png`;
return withBasePath(`themes/${themeName}/1.png`);
case 'background1Chart':
return `../themes/${themeName}/1-chart.png`;
return withBasePath(`themes/${themeName}/1-chart.png`);
case 'background2':
return `../themes/${themeName}/2.png`;
return withBasePath(`themes/${themeName}/2.png`);
case 'background3':
return `../themes/${themeName}/3.png`;
return withBasePath(`themes/${themeName}/3.png`);
case 'background4':
return `../themes/${themeName}/4.png`;
return withBasePath(`themes/${themeName}/4.png`);
case 'background5':
return `../themes/${themeName}/5.png`;
return withBasePath(`themes/${themeName}/5.png`);
case 'logoCorner':
return `themes/${themeName}/logo-corner.png`;
return withBasePath(`themes/${themeName}/logo-corner.png`);
default:
return BUILTIN_ASSETS[assetKey];
}
};
return withBasePath(BUILTIN_ASSETS[assetKey]);
}
};
const applyTheme = (themeName) => {
const selectedTheme = getAvailableThemes().includes(themeName) ? themeName : DEFAULT_THEME;

View file

@ -1,4 +1,6 @@
// rewrite URLs to use local proxy server
import { withBasePath } from './base-path.mjs';
const rewriteUrl = (_url) => {
if (!_url) {
throw new Error(`rewriteUrl called with invalid argument: '${_url}' (${typeof _url})`);
@ -25,44 +27,44 @@ const rewriteUrl = (_url) => {
if (url.origin === 'https://api.weather.gov') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/api${url.pathname}`;
url.pathname = withBasePath(`api${url.pathname}`);
} else if (url.origin === 'https://forecast.weather.gov') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/forecast${url.pathname}`;
url.pathname = withBasePath(`forecast${url.pathname}`);
} else if (url.origin === 'https://www.spc.noaa.gov') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/spc${url.pathname}`;
url.pathname = withBasePath(`spc${url.pathname}`);
} else if (url.origin === 'https://radar.weather.gov') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/radar${url.pathname}`;
url.pathname = withBasePath(`radar${url.pathname}`);
} else if (url.origin === 'https://mesonet.agron.iastate.edu') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/mesonet${url.pathname}`;
url.pathname = withBasePath(`mesonet${url.pathname}`);
} else if (url.origin === 'https://api.open-meteo.com') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/open-meteo${url.pathname}`;
url.pathname = withBasePath(`open-meteo${url.pathname}`);
} else if (url.origin === 'https://api.rainviewer.com') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/rainviewer${url.pathname}`;
url.pathname = withBasePath(`rainviewer${url.pathname}`);
} else if (url.origin === 'https://server.arcgisonline.com') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/arcgis-server${url.pathname}`;
url.pathname = withBasePath(`arcgis-server${url.pathname}`);
} else if (url.origin === 'https://services.arcgisonline.com') {
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/arcgis-services${url.pathname}`;
url.pathname = withBasePath(`arcgis-services${url.pathname}`);
} else if (typeof OVERRIDES !== 'undefined' && OVERRIDES?.RADAR_HOST && url.origin === `https://${OVERRIDES.RADAR_HOST}`) {
// Handle override radar host
url.protocol = window.location.protocol;
url.host = window.location.host;
url.pathname = `/mesonet${url.pathname}`;
url.pathname = withBasePath(`mesonet${url.pathname}`);
}
return url;