Re implement asset fetching
This commit is contained in:
parent
89fb6de2d5
commit
b1da684008
@ -2,14 +2,12 @@ import { useEffect } from "react";
|
|||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import { type TemplateProps } from "keycloakify/account/TemplateProps";
|
import { type TemplateProps } from "keycloakify/account/TemplateProps";
|
||||||
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
||||||
import { createUseInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||||
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
import { assert } from "keycloakify/tools/assert";
|
import { assert } from "keycloakify/tools/assert";
|
||||||
|
|
||||||
const { useInsertLinkTags } = createUseInsertLinkTags();
|
|
||||||
|
|
||||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
|
const { kcContext, i18n, doUseDefaultCss, active, classes, children } = props;
|
||||||
|
|
||||||
@ -46,6 +44,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
||||||
|
componentOrHookName: "Template",
|
||||||
hrefs: !doUseDefaultCss
|
hrefs: !doUseDefaultCss
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ExtendKcContext, KcContext as KcContextBase } from "./KcContext";
|
import type { ExtendKcContext, KcContext as KcContextBase } from "./KcContext";
|
||||||
import type { LoginThemePageId } from "keycloakify/bin/shared/constants";
|
import type { AccountThemePageId } from "keycloakify/bin/shared/constants";
|
||||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||||
@ -18,7 +18,7 @@ export function createGetKcContextMock<
|
|||||||
overrides?: DeepPartial<KcContextExtraProperties & KcContextBase.Common>;
|
overrides?: DeepPartial<KcContextExtraProperties & KcContextBase.Common>;
|
||||||
overridesPerPage?: {
|
overridesPerPage?: {
|
||||||
[PageId in
|
[PageId in
|
||||||
| LoginThemePageId
|
| AccountThemePageId
|
||||||
| keyof KcContextExtraPropertiesPerPage]?: DeepPartial<
|
| keyof KcContextExtraPropertiesPerPage]?: DeepPartial<
|
||||||
Extract<
|
Extract<
|
||||||
ExtendKcContext<
|
ExtendKcContext<
|
||||||
@ -43,7 +43,7 @@ export function createGetKcContextMock<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
function getKcContextMock<
|
function getKcContextMock<
|
||||||
PageId extends LoginThemePageId | keyof KcContextExtraPropertiesPerPage
|
PageId extends AccountThemePageId | keyof KcContextExtraPropertiesPerPage
|
||||||
>(params: {
|
>(params: {
|
||||||
pageId: PageId;
|
pageId: PageId;
|
||||||
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
|
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
|
||||||
|
@ -3,15 +3,12 @@ import { assert } from "keycloakify/tools/assert";
|
|||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import { type TemplateProps } from "keycloakify/login/TemplateProps";
|
import { type TemplateProps } from "keycloakify/login/TemplateProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { createUseInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
||||||
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import type { I18n } from "./i18n";
|
import type { I18n } from "./i18n";
|
||||||
|
|
||||||
const { useInsertLinkTags } = createUseInsertLinkTags();
|
|
||||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
|
||||||
|
|
||||||
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||||
const {
|
const {
|
||||||
displayInfo = false,
|
displayInfo = false,
|
||||||
@ -63,6 +60,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
||||||
|
componentOrHookName: "Template",
|
||||||
hrefs: !doUseDefaultCss
|
hrefs: !doUseDefaultCss
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
@ -75,6 +73,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
|
componentOrHookName: "Template",
|
||||||
scriptTags: [
|
scriptTags: [
|
||||||
{
|
{
|
||||||
type: "module",
|
type: "module",
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { memoize } from "keycloakify/tools/memoize";
|
|
||||||
import { fallbackLanguageTag } from "keycloakify/login/i18n/i18n";
|
import { fallbackLanguageTag } from "keycloakify/login/i18n/i18n";
|
||||||
import { useConst } from "keycloakify/tools/useConst";
|
|
||||||
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import {
|
import {
|
||||||
createStatefulObservable,
|
createStatefulObservable,
|
||||||
useRerenderOnChange
|
useRerenderOnChange
|
||||||
} from "keycloakify/tools/StatefulObservable";
|
} from "keycloakify/tools/StatefulObservable";
|
||||||
import { KcContext } from "../kcContext";
|
import { KcContext } from "../kcContext";
|
||||||
|
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
|
||||||
|
|
||||||
const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);
|
const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);
|
||||||
|
|
||||||
@ -27,29 +24,18 @@ export function useDownloadTerms(params: {
|
|||||||
kcContext: KcContextLike;
|
kcContext: KcContextLike;
|
||||||
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
|
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
|
||||||
}) {
|
}) {
|
||||||
const { kcContext } = params;
|
const { kcContext, downloadTermMarkdown } = params;
|
||||||
|
|
||||||
const { downloadTermMarkdownMemoized } = (function useClosure() {
|
useOnFistMount(async () => {
|
||||||
const { downloadTermMarkdown } = params;
|
|
||||||
|
|
||||||
const downloadTermMarkdownConst = useConstCallback(downloadTermMarkdown);
|
|
||||||
|
|
||||||
const downloadTermMarkdownMemoized = useConst(() =>
|
|
||||||
memoize((currentLanguageTag: string) =>
|
|
||||||
downloadTermMarkdownConst({ currentLanguageTag })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return { downloadTermMarkdownMemoized };
|
|
||||||
})();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
||||||
downloadTermMarkdownMemoized(
|
const termsMarkdown = await downloadTermMarkdown({
|
||||||
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
currentLanguageTag:
|
||||||
).then(thermMarkdown => (obsTermsMarkdown.current = thermMarkdown));
|
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
||||||
|
});
|
||||||
|
|
||||||
|
obsTermsMarkdown.current = termsMarkdown;
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTermsMarkdown() {
|
export function useTermsMarkdown() {
|
||||||
|
@ -3,6 +3,7 @@ import type { ClassKey } from "keycloakify/login/TemplateProps";
|
|||||||
|
|
||||||
export const { useGetClassName } = createUseClassName<ClassKey>({
|
export const { useGetClassName } = createUseClassName<ClassKey>({
|
||||||
defaultClasses: {
|
defaultClasses: {
|
||||||
|
kcHtmlClass: "login-pf",
|
||||||
kcBodyClass: undefined,
|
kcBodyClass: undefined,
|
||||||
kcHeaderWrapperClass: undefined,
|
kcHeaderWrapperClass: undefined,
|
||||||
kcLocaleWrapperClass: undefined,
|
kcLocaleWrapperClass: undefined,
|
||||||
@ -54,7 +55,6 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|||||||
kcLogoLink: "http://www.keycloak.org",
|
kcLogoLink: "http://www.keycloak.org",
|
||||||
kcContainerClass: "container-fluid",
|
kcContainerClass: "container-fluid",
|
||||||
kcSelectAuthListItemTitle: "select-auth-box-paragraph",
|
kcSelectAuthListItemTitle: "select-auth-box-paragraph",
|
||||||
kcHtmlClass: "login-pf",
|
|
||||||
kcLoginOTPListItemTitleClass: "pf-c-tile__title",
|
kcLoginOTPListItemTitleClass: "pf-c-tile__title",
|
||||||
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
|
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
|
||||||
kcWebAuthnUnknownIcon: "pficon pficon-key unknown-transport-class",
|
kcWebAuthnUnknownIcon: "pficon pficon-key unknown-transport-class",
|
||||||
|
@ -8,7 +8,7 @@ import { emailRegexp } from "keycloakify/tools/emailRegExp";
|
|||||||
import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/KcContext";
|
import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/KcContext";
|
||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import { formatNumber } from "keycloakify/tools/formatNumber";
|
import { formatNumber } from "keycloakify/tools/formatNumber";
|
||||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
@ -103,12 +103,11 @@ namespace internal {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
|
||||||
|
|
||||||
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
|
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
|
||||||
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
const { kcContext, i18n, doMakeUserConfirmPassword } = params;
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
|
componentOrHookName: "useUserProfileForm",
|
||||||
scriptTags: Object.keys(kcContext.profile?.html5DataAnnotations ?? {})
|
scriptTags: Object.keys(kcContext.profile?.html5DataAnnotations ?? {})
|
||||||
.filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it.
|
.filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it.
|
||||||
.map(key => ({
|
.map(key => ({
|
||||||
|
@ -2,12 +2,10 @@ import { useEffect } from "react";
|
|||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
|
||||||
|
|
||||||
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>, I18n>) {
|
export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<KcContext, { pageId: "login-recovery-authn-code-config.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
@ -21,6 +19,7 @@ export default function LoginRecoveryAuthnCodeConfig(props: PageProps<Extract<Kc
|
|||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
|
componentOrHookName: "LoginRecoveryAuthnCodeConfig",
|
||||||
scriptTags: [
|
scriptTags: [
|
||||||
{
|
{
|
||||||
type: "text/javascript",
|
type: "text/javascript",
|
||||||
|
@ -3,12 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
|
|||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
|
||||||
|
|
||||||
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
|
export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext, { pageId: "webauthn-authenticate.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
@ -31,6 +29,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
const { msg, msgStr, advancedMsg } = i18n;
|
const { msg, msgStr, advancedMsg } = i18n;
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
|
componentOrHookName: "WebauthnAuthenticate",
|
||||||
scriptTags: [
|
scriptTags: [
|
||||||
{
|
{
|
||||||
type: "text/javascript",
|
type: "text/javascript",
|
||||||
|
@ -3,12 +3,10 @@ import { clsx } from "keycloakify/tools/clsx";
|
|||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
const { useInsertScriptTags } = createUseInsertScriptTags();
|
|
||||||
|
|
||||||
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>, I18n>) {
|
export default function WebauthnRegister(props: PageProps<Extract<KcContext, { pageId: "webauthn-register.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
@ -35,6 +33,7 @@ export default function WebauthnRegister(props: PageProps<Extract<KcContext, { p
|
|||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
|
componentOrHookName: "WebauthnRegister",
|
||||||
scriptTags: [
|
scriptTags: [
|
||||||
{
|
{
|
||||||
type: "text/javascript",
|
type: "text/javascript",
|
||||||
|
@ -5,15 +5,15 @@ import type { StatefulObservable } from "../StatefulObservable";
|
|||||||
/**
|
/**
|
||||||
* Equivalent of https://docs.evt.land/api/react-hooks
|
* Equivalent of https://docs.evt.land/api/react-hooks
|
||||||
* */
|
* */
|
||||||
export function useRerenderOnChange($: StatefulObservable<unknown>): void {
|
export function useRerenderOnChange(obs: StatefulObservable<unknown>): void {
|
||||||
//NOTE: We use function in case the state is a function
|
//NOTE: We use function in case the state is a function
|
||||||
const [, setCurrent] = useState(() => $.current);
|
const [, setCurrent] = useState(() => obs.current);
|
||||||
|
|
||||||
useObservable(
|
useObservable(
|
||||||
({ registerSubscription }) => {
|
({ registerSubscription }) => {
|
||||||
const subscription = $.subscribe(current => setCurrent(() => current));
|
const subscription = obs.subscribe(current => setCurrent(() => current));
|
||||||
registerSubscription(subscription);
|
registerSubscription(subscription);
|
||||||
},
|
},
|
||||||
[$]
|
[obs]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
type SimpleType = number | string | boolean | null | undefined;
|
|
||||||
type FuncWithSimpleParams<T extends SimpleType[], R> = (...args: T) => R;
|
|
||||||
|
|
||||||
export function memoize<T extends SimpleType[], R>(
|
|
||||||
fn: FuncWithSimpleParams<T, R>,
|
|
||||||
options?: {
|
|
||||||
argsLength?: number;
|
|
||||||
max?: number;
|
|
||||||
}
|
|
||||||
): FuncWithSimpleParams<T, R> {
|
|
||||||
const cache = new Map<string, ReturnType<FuncWithSimpleParams<T, R>>>();
|
|
||||||
|
|
||||||
const { argsLength = fn.length, max = Infinity } = options ?? {};
|
|
||||||
|
|
||||||
return ((...args: Parameters<FuncWithSimpleParams<T, R>>) => {
|
|
||||||
const key = JSON.stringify(
|
|
||||||
args
|
|
||||||
.slice(0, argsLength)
|
|
||||||
.map(v => {
|
|
||||||
if (v === null) {
|
|
||||||
return "null";
|
|
||||||
}
|
|
||||||
if (v === undefined) {
|
|
||||||
return "undefined";
|
|
||||||
}
|
|
||||||
switch (typeof v) {
|
|
||||||
case "number":
|
|
||||||
return `number-${v}`;
|
|
||||||
case "string":
|
|
||||||
return `string-${v}`;
|
|
||||||
case "boolean":
|
|
||||||
return `boolean-${v ? "true" : "false"}`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join("-sIs9sAslOdeWlEdIos3-")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cache.has(key)) {
|
|
||||||
return cache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max === cache.size) {
|
|
||||||
for (const key of cache.keys()) {
|
|
||||||
cache.delete(key);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = fn(...args);
|
|
||||||
|
|
||||||
cache.set(key, value);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}) as any;
|
|
||||||
}
|
|
@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useReducer } from "react";
|
||||||
import {
|
import { useConst } from "keycloakify/tools/useConst";
|
||||||
createStatefulObservable,
|
import { id } from "tsafe/id";
|
||||||
useRerenderOnChange
|
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
|
||||||
} from "keycloakify/tools/StatefulObservable";
|
|
||||||
|
const alreadyMountedComponentOrHookNames = new Set<string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: The component that use this hook can only be mounded once!
|
* NOTE: The component that use this hook can only be mounded once!
|
||||||
@ -10,28 +11,37 @@ import {
|
|||||||
* If it's mounted again the page will be reloaded.
|
* If it's mounted again the page will be reloaded.
|
||||||
* This simulates the behavior of a server rendered page that imports css stylesheet in the head.
|
* This simulates the behavior of a server rendered page that imports css stylesheet in the head.
|
||||||
*/
|
*/
|
||||||
export function createUseInsertLinkTags() {
|
export function useInsertLinkTags(params: {
|
||||||
let isFistMount = true;
|
componentOrHookName: string;
|
||||||
|
hrefs: string[];
|
||||||
|
}) {
|
||||||
|
const { hrefs, componentOrHookName } = params;
|
||||||
|
|
||||||
const obsAreAllStyleSheetsLoaded = createStatefulObservable(() => false);
|
useOnFistMount(() => {
|
||||||
|
const isAlreadyMounted =
|
||||||
|
alreadyMountedComponentOrHookNames.has(componentOrHookName);
|
||||||
|
|
||||||
function useInsertLinkTags(params: { hrefs: string[] }) {
|
if (isAlreadyMounted) {
|
||||||
const { hrefs } = params;
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
useRerenderOnChange(obsAreAllStyleSheetsLoaded);
|
alreadyMountedComponentOrHookNames.add(componentOrHookName);
|
||||||
|
});
|
||||||
|
|
||||||
useState(() => {
|
const [areAllStyleSheetsLoaded, setAllStyleSheetsLoaded] = useReducer(
|
||||||
if (!isFistMount) {
|
() => true,
|
||||||
window.location.reload();
|
false
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
isFistMount = false;
|
const refPrAllStyleSheetLoaded = useConst(() => ({
|
||||||
});
|
current: id<Promise<void> | undefined>(undefined)
|
||||||
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isActive = true;
|
let isActive = true;
|
||||||
|
|
||||||
|
(refPrAllStyleSheetLoaded.current ??= (async () => {
|
||||||
let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined;
|
let lastMountedHtmlElement: HTMLLinkElement | undefined = undefined;
|
||||||
|
|
||||||
const prs: Promise<void>[] = [];
|
const prs: Promise<void>[] = [];
|
||||||
@ -58,20 +68,19 @@ export function createUseInsertLinkTags() {
|
|||||||
lastMountedHtmlElement = htmlElement;
|
lastMountedHtmlElement = htmlElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(prs).then(() => {
|
await Promise.all(prs);
|
||||||
if (!isActive) {
|
})()).then(() => {
|
||||||
return;
|
if (!isActive) {
|
||||||
}
|
return;
|
||||||
obsAreAllStyleSheetsLoaded.current = true;
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
setAllStyleSheetsLoaded();
|
||||||
isActive = false;
|
});
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { areAllStyleSheetsLoaded: obsAreAllStyleSheetsLoaded.current };
|
return () => {
|
||||||
}
|
isActive = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return { useInsertLinkTags };
|
return { areAllStyleSheetsLoaded };
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback } from "react";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
|
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
|
||||||
|
|
||||||
export type ScriptTag = ScriptTag.TextContent | ScriptTag.Src;
|
export type ScriptTag = ScriptTag.TextContent | ScriptTag.Src;
|
||||||
|
|
||||||
@ -16,80 +17,86 @@ export namespace ScriptTag {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alreadyMountedComponentOrHookNames = new Set<string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: The component that use this hook can only be mounded once!
|
* NOTE: The component that use this hook can only be mounded once!
|
||||||
* And can'r rerender with different scriptTags.
|
* And can't rerender with different scriptTags.
|
||||||
* If it's mounted again the page will be reloaded.
|
* If it's mounted again the page will be reloaded.
|
||||||
* This simulates the behavior of a server rendered page that imports javascript in the head.
|
* This simulates the behavior of a server rendered page that imports javascript in the head.
|
||||||
|
*
|
||||||
|
* The returned function is supposed to be called in a useEffect and
|
||||||
|
* will not download the scripts multiple times event if called more than once (react strict mode).
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
export function createUseInsertScriptTags() {
|
export function useInsertScriptTags(params: {
|
||||||
|
componentOrHookName: string;
|
||||||
|
scriptTags: ScriptTag[];
|
||||||
|
}) {
|
||||||
|
const { scriptTags, componentOrHookName } = params;
|
||||||
|
|
||||||
|
useOnFistMount(() => {
|
||||||
|
const isAlreadyMounted =
|
||||||
|
alreadyMountedComponentOrHookNames.has(componentOrHookName);
|
||||||
|
|
||||||
|
if (isAlreadyMounted) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadyMountedComponentOrHookNames.add(componentOrHookName);
|
||||||
|
});
|
||||||
|
|
||||||
let areScriptsInserted = false;
|
let areScriptsInserted = false;
|
||||||
|
|
||||||
let isFistMount = true;
|
const insertScriptTags = useCallback(() => {
|
||||||
|
if (areScriptsInserted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function useInsertScriptTags(params: { scriptTags: ScriptTag[] }) {
|
scriptTags.forEach(scriptTag => {
|
||||||
const { scriptTags } = params;
|
// NOTE: Avoid loading same script twice. (Like jQuery for example)
|
||||||
|
{
|
||||||
useState(() => {
|
const scripts = document.getElementsByTagName("script");
|
||||||
if (!isFistMount) {
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
window.location.reload();
|
const script = scripts[i];
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isFistMount = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
const insertScriptTags = useCallback(() => {
|
|
||||||
if (areScriptsInserted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scriptTags.forEach(scriptTag => {
|
|
||||||
// NOTE: Avoid loading same script twice. (Like jQuery for example)
|
|
||||||
{
|
|
||||||
const scripts = document.getElementsByTagName("script");
|
|
||||||
for (let i = 0; i < scripts.length; i++) {
|
|
||||||
const script = scripts[i];
|
|
||||||
if ("textContent" in scriptTag) {
|
|
||||||
if (script.textContent === scriptTag.textContent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ("src" in scriptTag) {
|
|
||||||
if (script.getAttribute("src") === scriptTag.src) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const htmlElement = document.createElement("script");
|
|
||||||
|
|
||||||
htmlElement.type = scriptTag.type;
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
if ("textContent" in scriptTag) {
|
if ("textContent" in scriptTag) {
|
||||||
htmlElement.textContent = scriptTag.textContent;
|
if (script.textContent === scriptTag.textContent) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if ("src" in scriptTag) {
|
if ("src" in scriptTag) {
|
||||||
htmlElement.src = scriptTag.src;
|
if (script.getAttribute("src") === scriptTag.src) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
assert(false);
|
assert(false);
|
||||||
})();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.head.appendChild(htmlElement);
|
const htmlElement = document.createElement("script");
|
||||||
});
|
|
||||||
|
|
||||||
areScriptsInserted = true;
|
htmlElement.type = scriptTag.type;
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { insertScriptTags };
|
(() => {
|
||||||
}
|
if ("textContent" in scriptTag) {
|
||||||
|
htmlElement.textContent = scriptTag.textContent;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ("src" in scriptTag) {
|
||||||
|
htmlElement.src = scriptTag.src;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
})();
|
||||||
|
|
||||||
return { useInsertScriptTags };
|
document.head.appendChild(htmlElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
areScriptsInserted = true;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { insertScriptTags };
|
||||||
}
|
}
|
||||||
|
18
src/tools/useOnFirstMount.ts
Normal file
18
src/tools/useOnFirstMount.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useConst } from "powerhooks/useConst";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
|
||||||
|
/** Callback is guaranteed to be call only once per component mount event in strict mode */
|
||||||
|
export function useOnFistMount(callback: () => void) {
|
||||||
|
const refHasCallbackBeenCalled = useConst(() => ({ current: id<boolean>(false) }));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (refHasCallbackBeenCalled.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
|
||||||
|
refHasCallbackBeenCalled.current = true;
|
||||||
|
}, []);
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
import React, { lazy, Suspense } from "react";
|
import React from "react";
|
||||||
import Fallback from "../../dist/account";
|
import Fallback from "../../dist/account";
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import { useI18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
|
import Template from "../../dist/account/Template";
|
||||||
const DefaultTemplate = lazy(() => import("../../dist/account/Template"));
|
|
||||||
|
|
||||||
export default function KcApp(props: { kcContext: KcContext }) {
|
export default function KcApp(props: { kcContext: KcContext }) {
|
||||||
const { kcContext } = props;
|
const { kcContext } = props;
|
||||||
@ -14,14 +13,5 @@ export default function KcApp(props: { kcContext: KcContext }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <Fallback {...{ kcContext, i18n }} Template={Template} doUseDefaultCss={true} />;
|
||||||
<Suspense>
|
|
||||||
{(() => {
|
|
||||||
switch (kcContext.pageId) {
|
|
||||||
default:
|
|
||||||
return <Fallback {...{ kcContext, i18n }} Template={DefaultTemplate} doUseDefaultCss={true} />;
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,11 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
|
|||||||
overrides
|
overrides
|
||||||
});
|
});
|
||||||
|
|
||||||
return <KcApp kcContext={kcContextMock} />;
|
return (
|
||||||
|
<React.StrictMode>
|
||||||
|
<KcApp kcContext={kcContextMock} />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { PageStory };
|
return { PageStory };
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { lazy, Suspense } from "react";
|
import React from "react";
|
||||||
import Fallback from "../../dist/login";
|
import Fallback from "../../dist/login";
|
||||||
import type { KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
import { useI18n } from "./i18n";
|
import { useI18n } from "./i18n";
|
||||||
@ -35,23 +35,14 @@ export default function KcApp(props: { kcContext: KcContext }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Fallback
|
||||||
{(() => {
|
{...{
|
||||||
switch (kcContext.pageId) {
|
kcContext,
|
||||||
default:
|
i18n,
|
||||||
return (
|
Template,
|
||||||
<Fallback
|
UserProfileFormFields
|
||||||
{...{
|
}}
|
||||||
kcContext,
|
doUseDefaultCss={true}
|
||||||
i18n,
|
/>
|
||||||
Template,
|
|
||||||
UserProfileFormFields
|
|
||||||
}}
|
|
||||||
doUseDefaultCss={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,11 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
|
|||||||
overrides
|
overrides
|
||||||
});
|
});
|
||||||
|
|
||||||
return <KcApp kcContext={kcContextMock} />;
|
return (
|
||||||
|
<React.StrictMode>
|
||||||
|
<KcApp kcContext={kcContextMock} />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { PageStory };
|
return { PageStory };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user