keycloak_theme/src/lib/usePrepareTemplate.ts

121 lines
3.4 KiB
TypeScript
Raw Normal View History

2023-03-17 20:40:29 +01:00
import { useReducer, useEffect } from "react";
2023-03-19 23:12:45 +01:00
import { clsx } from "keycloakify/tools/clsx";
import { assert } from "tsafe/assert";
import { useInsertScriptTags, type ScriptTag } from "keycloakify/tools/useInsertScriptTags";
2023-03-17 20:40:29 +01:00
export function usePrepareTemplate(params: {
styleSheetHrefs: string[];
scriptTags: ScriptTag[];
htmlClassName: string | undefined;
bodyClassName: string | undefined;
htmlLangProperty: string | undefined;
documentTitle: string | undefined;
2023-03-17 20:40:29 +01:00
}) {
const { styleSheetHrefs, scriptTags, htmlClassName, bodyClassName, htmlLangProperty, documentTitle } = params;
2023-03-17 20:40:29 +01:00
useEffect(() => {
if (htmlLangProperty === undefined) {
return;
}
const html = document.querySelector("html");
assert(html !== null);
html.lang = htmlLangProperty;
}, [htmlLangProperty]);
useEffect(() => {
if (documentTitle === undefined) {
return;
}
document.title = documentTitle;
}, [documentTitle]);
const { areAllStyleSheetsLoaded } = useInsertLinkTags({ "hrefs": styleSheetHrefs });
// NOTE: We want to load the script after the page have been fully rendered.
useInsertScriptTags({ "scriptTags": !areAllStyleSheetsLoaded ? [] : scriptTags });
2023-03-17 20:40:29 +01:00
useSetClassName({
"target": "html",
"className": htmlClassName
});
useSetClassName({
"target": "body",
"className": bodyClassName
});
return { areAllStyleSheetsLoaded };
}
function useSetClassName(params: { target: "html" | "body"; className: string | undefined }) {
const { target, className } = params;
2023-03-17 20:40:29 +01:00
useEffect(() => {
if (className === undefined) {
return;
}
2023-03-21 03:01:49 +01:00
const htmlClassList = document.getElementsByTagName(target)[0].classList;
2023-03-17 20:40:29 +01:00
2023-03-21 03:01:49 +01:00
const tokens = clsx(className).split(" ");
2023-03-17 20:40:29 +01:00
htmlClassList.add(...tokens);
return () => {
htmlClassList.remove(...tokens);
};
}, [className]);
2023-03-17 20:40:29 +01:00
}
const hrefByPrLoaded = new Map<string, Promise<void>>();
/** NOTE: The hrefs can't changes. There should be only one one call on this. */
function useInsertLinkTags(params: { hrefs: string[] }) {
const { hrefs } = params;
const [areAllStyleSheetsLoaded, setAllStyleSheetLoaded] = useReducer(() => true, hrefs.length === 0);
useEffect(() => {
let isActive = true;
let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined;
for (const href of hrefs) {
if (hrefByPrLoaded.has(href)) {
continue;
}
const htmlElement = document.createElement("link");
hrefByPrLoaded.set(href, new Promise<void>(resolve => htmlElement.addEventListener("load", () => resolve())));
htmlElement.rel = "stylesheet";
htmlElement.href = href;
if (lastMountedHtmlElement !== undefined) {
lastMountedHtmlElement.insertAdjacentElement("afterend", htmlElement);
} else {
document.head.prepend(htmlElement);
}
lastMountedHtmlElement = htmlElement;
}
Promise.all(Array.from(hrefByPrLoaded.values())).then(() => {
if (!isActive) {
return;
}
setAllStyleSheetLoaded();
});
return () => {
isActive = false;
};
}, []);
return { areAllStyleSheetsLoaded };
}