182 lines
6.6 KiB
JavaScript
182 lines
6.6 KiB
JavaScript
const frame = document.getElementById("demo-frame");
|
|
const runtimeBase = "/demo/runtime";
|
|
const demoBase = "/demo";
|
|
const defaultMock = "tariffs";
|
|
const stateMocks = new Set([
|
|
"tariffs",
|
|
"depleted",
|
|
"no-subscription",
|
|
"trial",
|
|
"devices",
|
|
]);
|
|
const routeMocks = new Set([...stateMocks, "guides", "install"]);
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
const normalizePath = (value) => {
|
|
const raw = String(value || "").trim();
|
|
if (!raw) return "/home";
|
|
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
return withSlash.replace(/\/{2,}/g, "/").replace(/\/+$/, "") || "/home";
|
|
};
|
|
|
|
const normalizeRouteMock = (value) => {
|
|
const mock = String(value || "").trim().toLowerCase();
|
|
return routeMocks.has(mock) ? mock : defaultMock;
|
|
};
|
|
|
|
const normalizeStateMock = (value) => {
|
|
const mock = normalizeRouteMock(value);
|
|
return stateMocks.has(mock) ? mock : defaultMock;
|
|
};
|
|
|
|
const routeFromPublicPath = () => {
|
|
const pathname = window.location.pathname.replace(/\/+$/, "") || "/";
|
|
const lowerPathname = pathname.toLowerCase();
|
|
if (lowerPathname === demoBase) return "";
|
|
if (!lowerPathname.startsWith(`${demoBase}/`)) return "";
|
|
|
|
const publicRoute = pathname.slice(demoBase.length);
|
|
if (!publicRoute || publicRoute.toLowerCase().startsWith("/runtime")) return "";
|
|
return normalizePath(publicRoute);
|
|
};
|
|
|
|
const routeFromParams = () => {
|
|
const publicRoute = routeFromPublicPath();
|
|
if (publicRoute) return publicRoute;
|
|
|
|
const explicitPath = params.get("path");
|
|
if (explicitPath) return normalizePath(explicitPath);
|
|
|
|
const screen = String(params.get("screen") || "home")
|
|
.trim()
|
|
.toLowerCase();
|
|
if (screen === "admin") {
|
|
const adminSection = String(params.get("admin_section") || "stats")
|
|
.trim()
|
|
.toLowerCase();
|
|
return `/admin/${adminSection || "stats"}`;
|
|
}
|
|
if (
|
|
[
|
|
"home",
|
|
"install",
|
|
"trial",
|
|
"invite",
|
|
"devices",
|
|
"support",
|
|
"settings",
|
|
].includes(screen)
|
|
) {
|
|
return `/${screen}`;
|
|
}
|
|
return "/home";
|
|
};
|
|
|
|
let initialRoute = routeFromParams();
|
|
const mockForRoute = (route) => (normalizePath(route) === "/devices" ? "devices" : "");
|
|
const initialMock = params.has("mock")
|
|
? normalizeRouteMock(params.get("mock"))
|
|
: normalizeRouteMock(mockForRoute(initialRoute) || defaultMock);
|
|
if (initialMock === "trial" && initialRoute === "/trial") {
|
|
initialRoute = "/home";
|
|
const normalizedUrl = new URL(window.location.href);
|
|
normalizedUrl.pathname = `${demoBase}/home`;
|
|
window.history.replaceState(
|
|
null,
|
|
"",
|
|
`${normalizedUrl.pathname}${normalizedUrl.search}${normalizedUrl.hash}`
|
|
);
|
|
}
|
|
params.set("mock", initialMock);
|
|
params.delete("path");
|
|
params.delete("screen");
|
|
params.delete("admin_section");
|
|
params.set("path", initialRoute);
|
|
frame.src = `${runtimeBase}/app.html?${params.toString()}${window.location.hash || ""}`;
|
|
|
|
const routeFromRuntimeUrl = (url) => {
|
|
if (url.origin !== window.location.origin) return "";
|
|
if (!url.pathname.toLowerCase().startsWith(runtimeBase.toLowerCase())) return "";
|
|
const runtimePath = normalizePath(url.pathname.slice(runtimeBase.length) || "/home");
|
|
if (runtimePath === "/app.html") {
|
|
return normalizePath(url.searchParams.get("path") || "/home");
|
|
}
|
|
return runtimePath;
|
|
};
|
|
|
|
const materializedRouteFromRuntime = (route) => {
|
|
const normalized = normalizePath(route);
|
|
if (/^\/admin\/users\/-?\d+$/i.test(normalized)) return "/admin/users";
|
|
if (/^\/admin\/payments\/\d+$/i.test(normalized)) return "/admin/payments";
|
|
if (/^\/admin\/payments\/users\/-?\d+$/i.test(normalized)) return "/admin/payments";
|
|
if (/^\/admin\/support\/\d+$/i.test(normalized)) return "/admin/support";
|
|
if (/^\/support\/\d+$/i.test(normalized)) return "/support";
|
|
return normalized;
|
|
};
|
|
|
|
const publicPathFromRoute = (route) => `${demoBase}${materializedRouteFromRuntime(route)}`;
|
|
const routeForStateMock = (mock) => (mock === "devices" ? "/devices" : "/home");
|
|
const runtimeSrc = (route, searchParams = new URLSearchParams()) => {
|
|
const nextParams = new URLSearchParams(searchParams);
|
|
nextParams.delete("screen");
|
|
nextParams.delete("admin_section");
|
|
nextParams.set("path", normalizePath(route));
|
|
return `${runtimeBase}/app.html?${nextParams.toString()}${window.location.hash || ""}`;
|
|
};
|
|
const topbar = document.querySelector(".demo-topbar");
|
|
const toggle = document.querySelector(".demo-topbar__toggle");
|
|
const hide = document.querySelector(".demo-topbar__hide");
|
|
const stateSelect = document.querySelector(".demo-topbar__state-select");
|
|
|
|
const syncParentUrlFromFrame = () => {
|
|
try {
|
|
const frameUrl = new URL(frame.contentWindow.location.href);
|
|
const route = routeFromRuntimeUrl(frameUrl);
|
|
if (!route) return;
|
|
|
|
const nextUrl = new URL(window.location.href);
|
|
nextUrl.pathname = publicPathFromRoute(route);
|
|
nextUrl.searchParams.delete("path");
|
|
|
|
const mock = normalizeRouteMock(frameUrl.searchParams.get("mock") || params.get("mock"));
|
|
if (mock === defaultMock) nextUrl.searchParams.delete("mock");
|
|
else nextUrl.searchParams.set("mock", mock);
|
|
if (stateSelect) stateSelect.value = normalizeStateMock(mock);
|
|
|
|
nextUrl.searchParams.delete("screen");
|
|
nextUrl.searchParams.delete("admin_section");
|
|
const nextPath = `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`;
|
|
const currentPath = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
if (nextPath !== currentPath) window.history.replaceState(null, "", nextPath);
|
|
} catch (_error) {
|
|
// The iframe is same-origin in docs builds; this keeps local oddities harmless.
|
|
}
|
|
};
|
|
|
|
frame.addEventListener("load", syncParentUrlFromFrame);
|
|
window.setInterval(syncParentUrlFromFrame, 750);
|
|
|
|
const setCollapsed = (collapsed) => {
|
|
topbar?.toggleAttribute("data-collapsed", collapsed);
|
|
toggle?.setAttribute("aria-expanded", String(!collapsed));
|
|
};
|
|
|
|
toggle?.addEventListener("click", () => setCollapsed(false));
|
|
hide?.addEventListener("click", () => setCollapsed(true));
|
|
if (stateSelect) stateSelect.value = normalizeStateMock(params.get("mock"));
|
|
stateSelect?.addEventListener("change", () => {
|
|
const mock = normalizeStateMock(stateSelect.value);
|
|
const nextParams = new URLSearchParams(window.location.search);
|
|
nextParams.delete("path");
|
|
nextParams.delete("screen");
|
|
nextParams.delete("admin_section");
|
|
if (mock === defaultMock) nextParams.delete("mock");
|
|
else nextParams.set("mock", mock);
|
|
|
|
const query = nextParams.toString();
|
|
const stateRoute = routeForStateMock(mock);
|
|
const publicUrl = `${demoBase}${stateRoute}${query ? `?${query}` : ""}`;
|
|
window.history.replaceState(null, "", publicUrl);
|
|
frame.src = runtimeSrc(stateRoute, nextParams);
|
|
});
|