added save as dialog (#38820)

fixes: #37717

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2025-04-11 19:37:30 +02:00 committed by GitHub
parent 0afe3aa14d
commit 195ea98fb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 50 deletions

View File

@ -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

View File

@ -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>
);
};

View File

@ -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>
</>
);
};

View File

@ -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);
});