utils/resolveAssetPath.jsjavascript
/**
* @module utils/resolveAssetPath
* @description Resolves root-absolute app paths ("/assets/...", "/data/...")
* against Vite's `base` (import.meta.env.BASE_URL). Vite only rewrites
* imported/bundled URLs - runtime fetch() calls and path strings inside the
* content JSONs are served as-is, so under a subpath deployment (e.g. the
* test server's /<group>/<project>/<release>/) they would hit the domain
* root and 404. Every runtime path therefore goes through here once.
*/
// trailing slash stripped so `${BASE}${"/assets/x"}` never doubles the "/"
const BASE = (import.meta.env.BASE_URL || "/").replace(/\/$/, "");
/**
* Prefixes a root-absolute path with the app's base path. Full URLs
* (http/https/protocol-relative), already-relative paths and non-strings are
* returned unchanged, as is everything when the app is served from "/".
*
* @param {*} path - Candidate path, e.g. "/assets/images/foo.webp".
* @returns {*} The resolved path, e.g. "/group/project/assets/images/foo.webp".
*/
export const resolveAssetPath = (path) => {
if (!BASE || typeof path !== "string") return path;
if (!path.startsWith("/") || path.startsWith("//")) return path;
return `${BASE}${path}`;
};
/**
* Recursively resolves every "/assets/..." string inside a content JSON
* structure via {@link resolveAssetPath}. Applied once by ContentProvider
* right after fetching, so no consumer (element adapters, thumbnails,
* screensaver video) needs to resolve paths itself.
*
* @param {*} value - Any JSON value (object, array, string, ...).
* @returns {*} A copy with all asset paths resolved.
*/
export const resolveAssetPathsDeep = (value) => {
if (typeof value === "string") {
return value.startsWith("/assets/") ? resolveAssetPath(value) : value;
}
if (Array.isArray(value)) return value.map(resolveAssetPathsDeep);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.entries(value).map(([key, entry]) => [key, resolveAssetPathsDeep(entry)])
);
}
return value;
};