diff --git a/public/gh-reroute/index.tsx b/public/gh-reroute/index.tsx index 578a5a44..aa468d02 100644 --- a/public/gh-reroute/index.tsx +++ b/public/gh-reroute/index.tsx @@ -1,4 +1,4 @@ -import { convertUrlToQueryStringPath } from "../../website/utils/gh_route_utils"; +import { encodeGitHubPagesUrl } from "../../website/utils/gh_route_utils"; // Hide the default 404 page content and just show a blank screen. // The content should only be shown if the browser doesn't support JavaScript. @@ -10,6 +10,6 @@ window.onload = () => { // Convert the current URL to a query string path and redirect the browser. const location = window.location; const locationUrl = new URL(location.toString()); -const newUrl = convertUrlToQueryStringPath(locationUrl, 1); +const newUrl = encodeGitHubPagesUrl(locationUrl); location.replace(newUrl); console.log("Redirecting to " + newUrl.toString()); diff --git a/public/index.tsx b/public/index.tsx index a7a5c488..8a5a5fde 100644 --- a/public/index.tsx +++ b/public/index.tsx @@ -2,12 +2,14 @@ import React from "react"; import ReactDOM from "react-dom"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { decodeGitHubPagesUrl, isEncodedPathUrl, tryRemoveHashRouting } from "../website/utils/gh_route_utils"; + +import StyleProvider from "../src/aics-image-viewer/components/StyleProvider"; // Components import AppWrapper from "../website/components/AppWrapper"; -import LandingPage from "../website/components/LandingPage"; import ErrorPage from "../website/components/ErrorPage"; -import { isQueryStringPath, convertQueryStringPathToUrl } from "../website/utils/gh_route_utils"; -import StyleProvider from "../src/aics-image-viewer/components/StyleProvider"; +import LandingPage from "../website/components/LandingPage"; + import "./App.css"; // vars filled at build time using webpack DefinePlugin @@ -18,11 +20,11 @@ console.log(`volume-viewer Version ${VOLUMEVIEWER_VERSION}`); const basename = WEBSITE3DCELLVIEWER_BASENAME; -// Check for redirects in the query string, and update browser history state. +// Decode URL path if it was encoded for GitHub pages or uses hash routing. const locationUrl = new URL(window.location.toString()); -if (isQueryStringPath(locationUrl)) { - const url = convertQueryStringPathToUrl(locationUrl); - const newRelativePath = url.pathname + url.search + url.hash; +if (locationUrl.hash !== "" || isEncodedPathUrl(locationUrl)) { + const decodedUrl = tryRemoveHashRouting(decodeGitHubPagesUrl(locationUrl)); + const newRelativePath = decodedUrl.pathname + decodedUrl.search + decodedUrl.hash; console.log("Redirecting to " + newRelativePath); // Replaces the query string path with the original path now that the // single-page app has loaded. This lets routing work as normal below. diff --git a/website/utils/gh_route_utils.ts b/website/utils/gh_route_utils.ts index 407afc02..343dd16a 100644 --- a/website/utils/gh_route_utils.ts +++ b/website/utils/gh_route_utils.ts @@ -1,7 +1,7 @@ const ESCAPED_AMPERSAND = "~and~"; /** - * Converts the path component of a URL into a query string. Used to redirect the browser + * Encodes the path component of a URL into a query string. Used to redirect the browser * for single-page apps when the server is not configured to serve the app for all paths, * e.g. GitHub pages. * @@ -23,7 +23,7 @@ const ESCAPED_AMPERSAND = "~and~"; * * @returns The URL with the path converted to a query string, and the original query string escaped. */ -export function convertUrlToQueryStringPath(url: URL, basePathSegments: number = 0): URL { +export function encodeUrlPathAsQueryString(url: URL, basePathSegments: number = 0): URL { const pathSegments = url.pathname.split("/"); const basePath = pathSegments.slice(0, basePathSegments + 1).join("/"); const remainingPath = pathSegments.slice(basePathSegments + 1).join("/"); @@ -39,10 +39,6 @@ export function convertUrlToQueryStringPath(url: URL, basePathSegments: number = return new URL(newUrl); } -export function isQueryStringPath(url: URL): boolean { - return url.search !== "" && url.search.startsWith("?/"); -} - /** * Converts a query string back into a complete URL. Used in combination with `convertUrlToQueryStringPath()`. * to redirect the browser for single-page apps when the server cannot be configured, e.g. GitHub pages. @@ -51,7 +47,7 @@ export function isQueryStringPath(url: URL): boolean { * @param url - The URL with a path converted to a query string, and the original query string escaped. * @returns The original URL, with path instead of a query string. */ -export function convertQueryStringPathToUrl(url: URL): URL { +export function decodeUrlQueryStringPath(url: URL): URL { if (!url.search || !url.search.startsWith("?/")) { return url; } @@ -64,3 +60,51 @@ export function convertQueryStringPathToUrl(url: URL): URL { return new URL(`${url.origin}${url.pathname}${newPathAndQueryString}${url.hash}`); } + +export function isEncodedPathUrl(url: URL): boolean { + return url.search !== "" && url.search.startsWith("?/"); +} + +/** + * Encodes a URL for GitHub pages by converting the path to a query string. + * See `encodeUrlPathAsQueryString()` for more details. + */ +export function encodeGitHubPagesUrl(url: URL): URL { + url = new URL(url); // Clone the URL to avoid modifying the original + if (url.hostname === "allen-cell-animated.github.io") { + if (url.pathname.toString().includes("pr-preview")) { + return encodeUrlPathAsQueryString(url, 3); + } + // Redirect `/main` paths back to root `/` + if (url.pathname.toString().includes("main")) { + url.pathname = url.pathname.replace("/main", ""); + } + return encodeUrlPathAsQueryString(url, 1); + } + + return url; +} + +/** + * Decodes a URL that was encoded for GitHub pages, e.g. by `encodeGitHubPagesUrl()`. + * See `decodeUrlQueryStringPath()` for more details. + */ +export function decodeGitHubPagesUrl(url: URL): URL { + return decodeUrlQueryStringPath(url); +} + +/** + * Changes URLs with hash-based routing to path-based routing by removing the hash from + * the URL. Does nothing to URLs that do not use hash-based routing. + */ +export function tryRemoveHashRouting(url: URL): URL { + // Remove #/ from the URL path + if (url.hash.startsWith("#/")) { + const hashContents = url.hash.slice(2); + const [path, queryParams] = hashContents.split("?"); + url.pathname += path; + url.search = queryParams ? `?${queryParams}` : ""; + url.hash = ""; + } + return url; +}