mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
added save as dialog (#38820)
fixes: #37717 Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
0afe3aa14d
commit
195ea98fb2
@ -3473,3 +3473,5 @@ authTokenClientSecret=Auth Token Client Secret
|
||||
enableDebugSMTP=Enable Debug SMTP
|
||||
signatureMaxExp=Max expiration
|
||||
signatureMaxExpHelp=Maximum expiration allowed for the JWT. Tokens need to be generated right before authentication. After this period will be considered invalid because they are too old. If undefined the default value is 60 seconds.
|
||||
fileNameDialogTitle=Save as
|
||||
fileName=File name
|
||||
@ -0,0 +1,39 @@
|
||||
import { TextControl } from "@keycloak/keycloak-ui-shared";
|
||||
import { Form } from "@patternfly/react-core";
|
||||
import { ConfirmDialogModal } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
type FileNameDialogProps = {
|
||||
onSave: (fileName: string) => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
type FormValues = {
|
||||
fileName: string;
|
||||
};
|
||||
export const FileNameDialog = ({ onSave, onClose }: FileNameDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<FormValues>();
|
||||
const { handleSubmit } = form;
|
||||
|
||||
const save = ({ fileName }: FormValues) => onSave(fileName);
|
||||
return (
|
||||
<ConfirmDialogModal
|
||||
titleKey="fileNameDialogTitle"
|
||||
open
|
||||
toggleDialog={onClose}
|
||||
onConfirm={() => handleSubmit(save)()}
|
||||
>
|
||||
<Form isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<FormProvider {...form}>
|
||||
<TextControl
|
||||
name="fileName"
|
||||
label={t("fileName")}
|
||||
defaultValue="quick-theme.jar"
|
||||
/>
|
||||
</FormProvider>
|
||||
</Form>
|
||||
</ConfirmDialogModal>
|
||||
);
|
||||
};
|
||||
@ -23,6 +23,8 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FixedButtonsGroup } from "../../components/form/FixedButtonGroup";
|
||||
import { FormAccess } from "../../components/form/FormAccess";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { FileNameDialog } from "./FileNameDialog";
|
||||
import { ImageUpload } from "./ImageUpload";
|
||||
import { usePreviewLogo } from "./LogoContext";
|
||||
import { darkTheme, lightTheme } from "./PatternflyVars";
|
||||
@ -81,6 +83,7 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => {
|
||||
const { handleSubmit, watch } = form;
|
||||
const style = watch();
|
||||
const contextLogo = usePreviewLogo();
|
||||
const [open, toggle, setOpen] = useToggle();
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const mapping = useMemo(
|
||||
@ -116,6 +119,7 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => {
|
||||
favicon: values.favicon as File,
|
||||
logo: values.logo as File,
|
||||
bgimage: values.bgimage as File,
|
||||
fileName: values.name as string,
|
||||
attributes: {
|
||||
...realm.attributes,
|
||||
style: JSON.stringify({
|
||||
@ -135,54 +139,65 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => {
|
||||
}, [realm]);
|
||||
|
||||
return (
|
||||
<PageSection variant="light">
|
||||
<TextContent className="pf-v5-u-mb-lg">
|
||||
<Text>{t("themeColorInfo")}</Text>
|
||||
</TextContent>
|
||||
{mediaQuery.matches && theme === "light" && (
|
||||
<Alert variant="info" isInline title={t("themePreviewInfo")} />
|
||||
<>
|
||||
{open && (
|
||||
<FileNameDialog
|
||||
onSave={(name) => {
|
||||
handleSubmit((data) => convert({ ...data, name }))();
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={toggle}
|
||||
/>
|
||||
)}
|
||||
<Flex className="pf-v5-u-pt-lg">
|
||||
<FlexItem>
|
||||
<FormAccess isHorizontal role="manage-realm">
|
||||
<FormProvider {...form}>
|
||||
<FormGroup label={t("favicon")}>
|
||||
<ImageUpload name="favicon" />
|
||||
</FormGroup>
|
||||
<FormGroup label={t("logo")}>
|
||||
<ImageUpload
|
||||
name="logo"
|
||||
onChange={(logo) => contextLogo?.setLogo(logo)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("backgroundImage")}>
|
||||
<ImageUpload name="bgimage" />
|
||||
</FormGroup>
|
||||
{mapping.map((m) => (
|
||||
<ColorControl
|
||||
key={m.name}
|
||||
color={m.defaultValue!}
|
||||
name={`${theme}.${m.variable!}`}
|
||||
label={m.name}
|
||||
/>
|
||||
))}
|
||||
</FormProvider>
|
||||
</FormAccess>
|
||||
</FlexItem>
|
||||
<FlexItem grow={{ default: "grow" }} style={{ zIndex: 0 }}>
|
||||
<PreviewWindow cssVars={style?.[theme] || {}} />
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<FixedButtonsGroup
|
||||
name="colors"
|
||||
saveText={t("downloadThemeJar")}
|
||||
save={handleSubmit(convert)}
|
||||
reset={setupForm}
|
||||
>
|
||||
<Button type="button" variant="link" onClick={reset}>
|
||||
{t("defaults")}
|
||||
</Button>
|
||||
</FixedButtonsGroup>
|
||||
</PageSection>
|
||||
<PageSection variant="light">
|
||||
<TextContent className="pf-v5-u-mb-lg">
|
||||
<Text>{t("themeColorInfo")}</Text>
|
||||
</TextContent>
|
||||
{mediaQuery.matches && theme === "light" && (
|
||||
<Alert variant="info" isInline title={t("themePreviewInfo")} />
|
||||
)}
|
||||
<Flex className="pf-v5-u-pt-lg">
|
||||
<FlexItem>
|
||||
<FormAccess isHorizontal role="manage-realm">
|
||||
<FormProvider {...form}>
|
||||
<FormGroup label={t("favicon")}>
|
||||
<ImageUpload name="favicon" />
|
||||
</FormGroup>
|
||||
<FormGroup label={t("logo")}>
|
||||
<ImageUpload
|
||||
name="logo"
|
||||
onChange={(logo) => contextLogo?.setLogo(logo)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label={t("backgroundImage")}>
|
||||
<ImageUpload name="bgimage" />
|
||||
</FormGroup>
|
||||
{mapping.map((m) => (
|
||||
<ColorControl
|
||||
key={m.name}
|
||||
color={m.defaultValue!}
|
||||
name={`${theme}.${m.variable!}`}
|
||||
label={m.name}
|
||||
/>
|
||||
))}
|
||||
</FormProvider>
|
||||
</FormAccess>
|
||||
</FlexItem>
|
||||
<FlexItem grow={{ default: "grow" }} style={{ zIndex: 0 }}>
|
||||
<PreviewWindow cssVars={style?.[theme] || {}} />
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<FixedButtonsGroup
|
||||
name="colors"
|
||||
saveText={t("downloadThemeJar")}
|
||||
save={toggle}
|
||||
reset={setupForm}
|
||||
>
|
||||
<Button type="button" variant="link" onClick={reset}>
|
||||
{t("defaults")}
|
||||
</Button>
|
||||
</FixedButtonsGroup>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -21,6 +21,7 @@ type ThemesTabProps = {
|
||||
};
|
||||
|
||||
export type ThemeRealmRepresentation = RealmRepresentation & {
|
||||
fileName?: string;
|
||||
favicon?: File;
|
||||
logo?: File;
|
||||
bgimage?: File;
|
||||
@ -37,7 +38,7 @@ export default function ThemesTab({ realm, save }: ThemesTabProps) {
|
||||
|
||||
const styles = JSON.parse(realm.attributes?.style ?? "{}");
|
||||
|
||||
const { favicon, logo, bgimage, ...rest } = realm;
|
||||
const { favicon, logo, bgimage, fileName, ...rest } = realm;
|
||||
|
||||
const logoName =
|
||||
"img/logo" + logo?.name?.substring(logo?.name?.lastIndexOf("."));
|
||||
@ -135,7 +136,7 @@ styles=css/login.css css/theme-styles.css
|
||||
const url = URL.createObjectURL(content);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "quick-theme.jar";
|
||||
a.download = fileName || "quick-theme.jar";
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user