Compare commits

..

25 Commits

Author SHA1 Message Date
ad275e4c34 Update changelog v0.3.8 2021-03-22 22:36:17 +00:00
060b9fe0de Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 23:34:13 +01:00
17b24d14ed Make standalone mode the default 2021-03-22 23:34:07 +01:00
2d278b0680 Update changelog v0.3.7 2021-03-22 19:57:34 +00:00
fb5975e4f1 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 20:54:35 +01:00
24fccaf513 (test) external asset mode by default 2021-03-22 20:54:28 +01:00
293953aa1b Update changelog v0.3.6 2021-03-22 19:02:53 +00:00
1049e312f9 Fix previous release 2021-03-22 20:00:58 +01:00
a2db250600 Update changelog v0.3.5 2021-03-22 18:43:18 +00:00
cf7fe8c337 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 19:41:08 +01:00
f5350097bf Bump version (changelog ignore) 2021-03-22 19:40:58 +01:00
1cb5dd461b support homepage with urlPath 2021-03-22 19:40:38 +01:00
845599a5e8 Update changelog v0.3.4 2021-03-22 06:23:40 +00:00
0cc02c292f Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 07:21:36 +01:00
1919702326 Bugfix: Import assets from CSS 2021-03-22 07:21:31 +01:00
0c0052e1cd Update changelog v0.3.3 2021-03-22 04:27:01 +00:00
78622770ec Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 05:21:22 +01:00
7b86727394 Fix submit not receving correct text 2021-03-22 05:21:05 +01:00
0965f8648e Update changelog v0.3.2 2021-03-21 21:31:01 +00:00
98974b4367 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-21 22:25:49 +01:00
597bcadd9e Fix broken previous release 2021-03-21 22:25:47 +01:00
4d9aabcb91 Update changelog v0.3.1 2021-03-21 21:13:36 +00:00
1606c2884d kcHeaderClass can be updated after initial mount 2021-03-21 22:10:33 +01:00
12f69b593f Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-20 05:07:26 +01:00
1ca45f90d0 Update readme (changelog ignore) 2021-03-20 05:07:22 +01:00
12 changed files with 252 additions and 110 deletions

@ -1,3 +1,35 @@
### **0.3.8** (2021-03-22)
- Make standalone mode the default
### **0.3.7** (2021-03-22)
- (test) external asset mode by default
### **0.3.6** (2021-03-22)
- Fix previous release
### **0.3.5** (2021-03-22)
- support homepage with urlPath
### **0.3.4** (2021-03-22)
- Bugfix: Import assets from CSS
### **0.3.3** (2021-03-22)
- Fix submit not receving correct text
### **0.3.2** (2021-03-21)
- Fix broken previous release
### **0.3.1** (2021-03-21)
- kcHeaderClass can be updated after initial mount
## **0.3.0** (2021-03-20) ## **0.3.0** (2021-03-20)
- Bump version - Bump version

@ -45,12 +45,11 @@ Tested with the following Keycloak versions:
- [Just changing the look](#just-changing-the-look) - [Just changing the look](#just-changing-the-look)
- [Changing the look **and** feel](#changing-the-look-and-feel) - [Changing the look **and** feel](#changing-the-look-and-feel)
- [Hot reload](#hot-reload) - [Hot reload](#hot-reload)
- [Implement context persistance (optional)](#implement-context-persistance-optional)
- [GitHub Actions](#github-actions) - [GitHub Actions](#github-actions)
- [REQUIREMENTS](#requirements) - [REQUIREMENTS](#requirements)
- [API Reference](#api-reference) - [API Reference](#api-reference)
- [The build tool](#the-build-tool) - [The build tool](#the-build-tool)
- [The fronted lib ( imported into your react app )](#the-fronted-lib--imported-into-your-react-app-) - [Implement context persistance (optional)](#implement-context-persistance-optional)
# How to use # How to use
## Setting up the build tool ## Setting up the build tool
@ -152,6 +151,37 @@ Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-ap
*NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded* *NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded*
[![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8) [![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8)
# GitHub Actions
![image](https://user-images.githubusercontent.com/6702424/110708305-c44b2c00-81fa-11eb-8152-eeaaac0883d6.png)
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
the building and publishing of the theme (the .jar file).
# REQUIREMENTS
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
and a `static/` directory generated by webpack.
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
- For building the theme: `mvn` (Maven) must be installed
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
and `docker` up and running.
NOTE: This build tool has only be tested on MacOS.
# API Reference
## The build tool
Part of the lib that runs with node, at build time.
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
# Implement context persistance (optional) # Implement context persistance (optional)
If, before logging in, a user has selected a specific language If, before logging in, a user has selected a specific language
@ -210,40 +240,3 @@ keycloakInstance.init({
If you really want to go the extra miles and avoid having the white If you really want to go the extra miles and avoid having the white
flash of the blank html before the js bundle have been evaluated flash of the blank html before the js bundle have been evaluated
[here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`. [here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
# GitHub Actions
![image](https://user-images.githubusercontent.com/6702424/110708305-c44b2c00-81fa-11eb-8152-eeaaac0883d6.png)
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
the building and publishing of the theme (the .jar file).
# REQUIREMENTS
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
and a `static/` directory generated by webpack.
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
- For building the theme: `mvn` (Maven) must be installed
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
and `docker` up and running.
NOTE: This build tool has only be tested on MacOS.
# API Reference
## The build tool
Part of the lib that runs with node, at build time.
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
## The fronted lib ( imported into your react app )
Part of the lib that you import in your react project and runs on the browser.
**TODO**

@ -1,6 +1,6 @@
{ {
"name": "keycloakify", "name": "keycloakify",
"version": "0.3.0", "version": "0.3.8",
"description": "Keycloak theme generator for Reacts app", "description": "Keycloak theme generator for Reacts app",
"repository": { "repository": {
"type": "git", "type": "git",

@ -11,9 +11,9 @@ import { objectKeys } from "evt/tools/typeSafety/objectKeys";
export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const; export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const;
export type PageId = typeof pageIds[number]; export type PageId = typeof pageIds[number];
function loadAdjacentFile(fileBasename: string){ function loadAdjacentFile(fileBasename: string) {
return fs.readFileSync(pathJoin(__dirname, fileBasename)) return fs.readFileSync(pathJoin(__dirname, fileBasename))
.toString("utf8"); .toString("utf8");
}; };
@ -29,15 +29,25 @@ function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
} }
} }
export type Mode = {
type: "standalone";
urlPathname: string;
} | {
type: "external assets";
urlPathname: string;
urlOrigin: string;
}
export function generateFtlFilesCodeFactory( export function generateFtlFilesCodeFactory(
params: { params: {
ftlValuesGlobalName: string; ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>; cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string; indexHtmlCode: string;
mode: Mode;
} }
) { ) {
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode } = params; const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode, mode } = params;
const $ = cheerio.load(indexHtmlCode); const $ = cheerio.load(indexHtmlCode);
@ -45,7 +55,8 @@ export function generateFtlFilesCodeFactory(
const { fixedJsCode } = replaceImportFromStaticInJsCode({ const { fixedJsCode } = replaceImportFromStaticInJsCode({
ftlValuesGlobalName, ftlValuesGlobalName,
"jsCode": $(element).html()! "jsCode": $(element).html()!,
mode
}); });
$(element).text(fixedJsCode); $(element).text(fixedJsCode);
@ -60,11 +71,28 @@ export function generateFtlFilesCodeFactory(
const href = $(element).attr(attrName); const href = $(element).attr(attrName);
if (!href?.startsWith("/")) { if (href === undefined) {
return; return;
} }
$(element).attr(attrName, "${url.resourcesPath}/build" + href); switch (mode.type) {
case "external assets":
$(element).attr(
attrName,
href.replace(/^\//, `${mode.urlOrigin}/`)
);
break;
case "standalone":
$(element).attr(
attrName,
href.replace(
new RegExp(`^${mode.urlPathname.replace(/\//g, "\\/")}`),
"${url.resourcesPath}/build/"
)
);
break;
}
}) })
); );
@ -89,9 +117,10 @@ export function generateFtlFilesCodeFactory(
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [ ...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
'', '',
'<style>', '<style>',
generateCssCodeToDefineGlobals( generateCssCodeToDefineGlobals({
{ cssGlobalsToDefine } cssGlobalsToDefine,
).cssCodeToPrependInHead, "urlPathname": mode.urlPathname
}).cssCodeToPrependInHead,
'</style>', '</style>',
'' ''
]), ]),

@ -6,23 +6,25 @@ import {
replaceImportFromStaticInCssCode, replaceImportFromStaticInCssCode,
replaceImportFromStaticInJsCode replaceImportFromStaticInJsCode
} from "./replaceImportFromStatic"; } from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl"; import { generateFtlFilesCodeFactory, pageIds, Mode } from "./generateFtl";
import { builtinThemesUrl } from "../install-builtin-keycloak-themes"; import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
import { downloadAndUnzip } from "../tools/downloadAndUnzip"; import { downloadAndUnzip } from "../tools/downloadAndUnzip";
import * as child_process from "child_process"; import * as child_process from "child_process";
import { ftlValuesGlobalName } from "./ftlValuesGlobalName"; import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath"; import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath";
import { isInside } from "../tools/isInside"; import { isInside } from "../tools/isInside";
export function generateKeycloakThemeResources( export function generateKeycloakThemeResources(
params: { params: {
themeName: string; themeName: string;
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;
mode: Mode;
} }
) { ) {
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath } = params; const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, mode } = params;
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login"); const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
@ -43,30 +45,34 @@ export function generateKeycloakThemeResources(
return undefined; return undefined;
} }
if (mode.type === "standalone") {
if (/\.css?$/i.test(filePath)) { if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode( const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
{ "cssCode": sourceCode.toString("utf8") } { "cssCode": sourceCode.toString("utf8") }
); );
allCssGlobalsToDefine = { allCssGlobalsToDefine = {
...allCssGlobalsToDefine, ...allCssGlobalsToDefine,
...cssGlobalsToDefine ...cssGlobalsToDefine
}; };
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") }; return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
} }
if (/\.js?$/i.test(filePath)) { if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportFromStaticInJsCode({ const { fixedJsCode } = replaceImportFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"), "jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName ftlValuesGlobalName,
}); mode
});
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
} }
@ -80,7 +86,8 @@ export function generateKeycloakThemeResources(
ftlValuesGlobalName, ftlValuesGlobalName,
"indexHtmlCode": fs.readFileSync( "indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html") pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8") ).toString("utf8"),
mode
}); });
pageIds.forEach(pageId => { pageIds.forEach(pageId => {

@ -6,10 +6,13 @@ import type { ParsedPackageJson } from "./generateJavaStackFiles";
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path"; import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
import * as child_process from "child_process"; import * as child_process from "child_process";
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles"; import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
import { URL } from "url";
const reactProjectDirPath = process.cwd(); const reactProjectDirPath = process.cwd();
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json")); const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak"); export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
@ -22,7 +25,51 @@ if (require.main === module) {
generateKeycloakThemeResources({ generateKeycloakThemeResources({
keycloakThemeBuildingDirPath, keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name "themeName": parsedPackageJson.name,
"mode": (() => {
const url = (() => {
const { homepage } = parsedPackageJson;
return homepage === undefined ?
undefined :
new URL(homepage);
})();
const urlPathname =
url === undefined ?
"/" :
url.pathname.replace(/([^/])$/, "$1/");
return !doUseExternalAssets ?
{
"type": "standalone",
urlPathname
} as const
:
{
"type": "external assets",
urlPathname,
"urlOrigin": (() => {
if (url === undefined) {
console.error("ERROR: You must specify 'homepage' in your package.json");
process.exit(-1);
}
return url.origin;
})()
} as const;
})()
}); });
const { jarFilePath } = generateJavaStackFiles({ const { jarFilePath } = generateJavaStackFiles({

@ -1,19 +1,39 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
type Mode = {
type: "standalone";
} | {
type: "external assets";
urlOrigin: string;
urlPathname: string;
}
export function replaceImportFromStaticInJsCode( export function replaceImportFromStaticInJsCode(
params: { params: {
ftlValuesGlobalName: string; ftlValuesGlobalName: string;
jsCode: string; jsCode: string;
mode: Mode;
} }
): { fixedJsCode: string; } { ): { fixedJsCode: string; } {
const { jsCode, ftlValuesGlobalName } = params; const { jsCode, ftlValuesGlobalName, mode } = params;
const fixedJsCode = (() => {
switch (mode.type) {
case "standalone":
return jsCode!.replace(
/[a-z]+\.[a-z]+\+"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
);
case "external assets":
return jsCode!.replace(
/[a-z]+\.[a-z]+\+"static\//g,
`"${mode.urlOrigin}${mode.urlPathname}static/`
);
}
})();
const fixedJsCode = jsCode!.replace(
/"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath.replace(/^\\//,"") + "/build/static/`
);
return { fixedJsCode }; return { fixedJsCode };
@ -32,7 +52,7 @@ export function replaceImportFromStaticInCssCode(
const cssGlobalsToDefine: Record<string, string> = {}; const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/(url\(\/[^)]+\))/g) ?? []) new Set(cssCode.match(/url\(\/[^)]+\)[^;}]*/g) ?? [])
.forEach(match => .forEach(match =>
cssGlobalsToDefine[ cssGlobalsToDefine[
"url" + crypto "url" + crypto
@ -60,12 +80,13 @@ export function replaceImportFromStaticInCssCode(
export function generateCssCodeToDefineGlobals( export function generateCssCodeToDefineGlobals(
params: { params: {
cssGlobalsToDefine: Record<string, string>; cssGlobalsToDefine: Record<string, string>;
urlPathname: string;
} }
): { ): {
cssCodeToPrependInHead: string; cssCodeToPrependInHead: string;
} { } {
const { cssGlobalsToDefine } = params; const { cssGlobalsToDefine, urlPathname } = params;
return { return {
"cssCodeToPrependInHead": [ "cssCodeToPrependInHead": [
@ -73,12 +94,8 @@ export function generateCssCodeToDefineGlobals(
...Object.keys(cssGlobalsToDefine) ...Object.keys(cssGlobalsToDefine)
.map(cssVariableName => [ .map(cssVariableName => [
`--${cssVariableName}:`, `--${cssVariableName}:`,
[ cssGlobalsToDefine[cssVariableName]
"url(", .replace(new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"), "url(${url.resourcesPath}/build/")
"${url.resourcesPath}/build" +
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)![1],
")"
].join("")
].join(" ")) ].join(" "))
.map(line => ` ${line};`), .map(line => ` ${line};`),
"}" "}"

@ -66,7 +66,7 @@ export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: Kc
props.kcButtonBlockClass, props.kcButtonLargeClass props.kcButtonBlockClass, props.kcButtonLargeClass
)} )}
type="submit" type="submit"
defaultValue={msgStr("doSubmit")} value={msgStr("doSubmit")}
/> />
</div> </div>
</div> </div>

@ -113,7 +113,7 @@ export const Register = memo(({ kcContext, ...props }: { kcContext: KcContext.Re
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}> <div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit" <input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit"
defaultValue={msgStr("doRegister")} /> value={msgStr("doRegister")} />
</div> </div>
</div> </div>
</form > </form >

@ -25,8 +25,7 @@ export type TemplateProps = {
showUsernameNode?: ReactNode; showUsernameNode?: ReactNode;
formNode: ReactNode; formNode: ReactNode;
infoNode?: ReactNode; infoNode?: ReactNode;
} & { kcContext: KcContext.Template; } & KcTemplateProps; } & { kcContext: KcContext.Template; } & KcTemplateProps;
export const Template = memo((props: TemplateProps) => { export const Template = memo((props: TemplateProps) => {
@ -60,34 +59,35 @@ export const Template = memo((props: TemplateProps) => {
); );
const { const {
realm, locale, auth, realm, locale, auth,
url, message, isAppInitiatedAction url, message, isAppInitiatedAction
}= kcContext; } = kcContext;
useEffect(()=>{ useEffect(() => {
if( !realm.internationalizationEnabled ){ if (!realm.internationalizationEnabled) {
return; return;
} }
assert( locale !== undefined ); assert(locale !== undefined);
if( kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current) ){ if (kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current)) {
return; return;
} }
window.location.href = window.location.href =
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url; locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
},[kcLanguageTag]); }, [kcLanguageTag]);
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false); const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
useEffect(() => { useEffect(() => {
let isUnmounted = false; let isUnmounted = false;
const cleanups: (() => void)[] = [];
const toArr = (x: string | readonly string[] | undefined) => const toArr = (x: string | readonly string[] | undefined) =>
typeof x === "string" ? x.split(" ") : x ?? []; typeof x === "string" ? x.split(" ") : x ?? [];
Promise.all( Promise.all(
@ -116,15 +116,27 @@ export const Template = memo((props: TemplateProps) => {
if (props.kcHtmlClass !== undefined) { if (props.kcHtmlClass !== undefined) {
document.getElementsByTagName("html")[0] const htmlClassList =
.classList document.getElementsByTagName("html")[0]
.add(...cx(props.kcHtmlClass).split(" ")); .classList;
const tokens = cx(props.kcHtmlClass).split(" ")
htmlClassList.add(...tokens);
cleanups.push(() => htmlClassList.remove(...tokens));
} }
return () => { isUnmounted = true; }; return () => {
}, []); isUnmounted = true;
cleanups.forEach(f => f());
};
}, [props.kcHtmlClass]);
if (!isExtraCssLoaded) { if (!isExtraCssLoaded) {
return null; return null;

@ -1,7 +1,7 @@
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources"; import { generateKeycloakThemeResources } from "../bin/build-keycloak-theme/generateKeycloakThemeResources";
import { import {
setupSampleReactProject, setupSampleReactProject,
sampleReactProjectDirPath sampleReactProjectDirPath
} from "./setupSampleReactProject"; } from "./setupSampleReactProject";
@ -9,8 +9,12 @@ import {
setupSampleReactProject(); setupSampleReactProject();
generateKeycloakThemeResources({ generateKeycloakThemeResources({
"themeName": "onyxia-ui", "themeName": "keycloakify-demo-app",
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"), "reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme") "keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
"mode": {
"type": "standalone",
"urlPathname": "/keycloakify-demo-app/"
}
}); });

@ -19,7 +19,8 @@ const { fixedJsCode } = replaceImportFromStaticInJsCode({
3: "0664cdc0" 3: "0664cdc0"
}[e] + ".chunk.js" }[e] + ".chunk.js"
} }
` `,
"mode": { "type": "standalone" }
}); });
console.log({ fixedJsCode }); console.log({ fixedJsCode });
@ -45,6 +46,6 @@ const { fixedCssCode, cssGlobalsToDefine } = replaceImportFromStaticInCssCode({
console.log({ fixedCssCode, cssGlobalsToDefine }); console.log({ fixedCssCode, cssGlobalsToDefine });
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine }); const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({ cssGlobalsToDefine, "urlPathname": "/" });
console.log({ cssCodeToPrependInHead }); console.log({ cssCodeToPrependInHead });