From 5e1e1252dbb0b82a53f4479ac0210ff2f198afe7 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Thu, 14 Sep 2023 14:32:45 +0100 Subject: [PATCH 01/15] added new format for localization tabs --- .../public/locales/en/translation.json | 7 +- .../src/realm-settings/LocalizationTab.tsx | 747 +++++------------- .../localization/EffectiveMessageBundles.tsx | 7 + .../localization/RealmOverrides.tsx | 7 + 4 files changed, 228 insertions(+), 540 deletions(-) create mode 100644 js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx create mode 100644 js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index 3f04da10f1b2..246d98036fa1 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -3282,5 +3282,10 @@ "scopeTypeHelp": "Client scopes, which will be added as default scopes to each created client", "clientDescriptionHelp": "Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example: ${my_client_description}", "clientsClientTypeHelp": "'OpenID Connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.", - "clientsClientScopesHelp": "The scopes associated with this resource." + "clientsClientScopesHelp": "The scopes associated with this resource.", + "locales": "Locales", + "realmOverrides": "Realm overrides ", + "realmOverridesHelp": "You can edit the supported locales. If you haven't selected supported locale yet, you only can edit English locale.", + "effectiveMessageBundles": "Effective message bundles ", + "effectiveMessageBundlesHelp": "Todo" } diff --git a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx b/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx index 1401afaaab41..f6de05550de8 100644 --- a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx @@ -1,55 +1,27 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import { ActionGroup, - AlertVariant, Button, - Divider, FormGroup, PageSection, Select, - SelectGroup, SelectOption, SelectVariant, Switch, - TextContent, - ToolbarItem, + Tab, + TabTitleText, + Tabs, } from "@patternfly/react-core"; -import { SearchIcon } from "@patternfly/react-icons"; -import { - EditableTextCell, - IEditableTextCell, - IRow, - IRowCell, - RowEditType, - RowErrors, - Table, - TableBody, - TableHeader, - TableVariant, - applyCellEdits, - cancelCellEdits, - validateCellEdits, -} from "@patternfly/react-table"; -import { cloneDeep, isEqual, uniqWith } from "lodash-es"; import { useEffect, useMemo, useState } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { HelpItem } from "ui-shared"; - -import { adminClient } from "../admin-client"; -import { useAlerts } from "../components/alert/Alerts"; import { FormAccess } from "../components/form/FormAccess"; -import type { KeyValueType } from "../components/key-value-form/key-value-convert"; -import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; -import { FormPanel } from "../components/scroll-form/FormPanel"; -import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar"; -import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; -import { useWhoAmI } from "../context/whoami/WhoAmI"; import { DEFAULT_LOCALE } from "../i18n/i18n"; import { convertToFormValues } from "../util"; -import { useFetch } from "../utils/useFetch"; -import { AddMessageBundleModal } from "./AddMessageBundleModal"; +import { RealmOverrides } from "./localization/RealmOverrides"; +import { EffectiveMessageBundles } from "./localization/EffectiveMessageBundles"; type LocalizationTabProps = { save: (realm: RealmRepresentation) => void; @@ -57,19 +29,6 @@ type LocalizationTabProps = { realm: RealmRepresentation; }; -export enum RowEditAction { - Save = "save", - Cancel = "cancel", - Edit = "edit", - Delete = "delete", -} - -export type BundleForm = { - key: string; - value: string; - messageBundle: KeyValueType; -}; - const localeToDisplayName = (locale: string) => { try { return new Intl.DisplayNames([locale], { type: "language" }).of(locale); @@ -80,18 +39,14 @@ const localeToDisplayName = (locale: string) => { export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { const { t } = useTranslation(); - const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] = - useState(false); - + const [activeTab, setActiveTab] = useState(0); + const { setValue, control, handleSubmit, formState } = useForm(); const [supportedLocalesOpen, setSupportedLocalesOpen] = useState(false); const [defaultLocaleOpen, setDefaultLocaleOpen] = useState(false); - const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); - const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); - const { setValue, getValues, control, handleSubmit, formState } = useForm(); - const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); - const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); - const [tableRows, setTableRows] = useState([]); + const defaultSupportedLocales = realm.supportedLocales?.length + ? realm.supportedLocales + : [DEFAULT_LOCALE]; const themeTypes = useServerInfo().themes!; const allLocales = useMemo(() => { @@ -100,14 +55,6 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { ); return Array.from(new Set(locales)); }, [themeTypes]); - const bundleForm = useForm({ mode: "onChange" }); - const { addAlert, addError } = useAlerts(); - const { realm: currentRealm } = useRealm(); - const { whoAmI } = useWhoAmI(); - - const defaultSupportedLocales = realm.supportedLocales?.length - ? realm.supportedLocales - : [DEFAULT_LOCALE]; const setupForm = () => { convertToFormValues(realm, setValue); @@ -115,7 +62,6 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { }; useEffect(setupForm, []); - const watchSupportedLocales: string[] = useWatch({ control, name: "supportedLocales", @@ -127,486 +73,209 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { defaultValue: realm.internationalizationEnabled, }); - const [tableKey, setTableKey] = useState(0); - const [max, setMax] = useState(10); - const [first, setFirst] = useState(0); - const [filter, setFilter] = useState(""); - - const refreshTable = () => { - setTableKey(tableKey + 1); - }; - - useFetch( - async () => { - let result = await adminClient.realms - .getRealmLocalizationTexts({ - first, - max, - realm: realm.realm!, - selectedLocale: - selectMenuLocale || - getValues("defaultLocale") || - whoAmI.getLocale(), - }) - // prevents server error in dev mode due to snowpack - .catch(() => []); - - const searchInBundles = (idx: number) => { - return Object.entries(result).filter((i) => i[idx].includes(filter)); - }; - - if (filter) { - const filtered = uniqWith( - searchInBundles(0).concat(searchInBundles(1)), - isEqual, - ); - - result = Object.fromEntries(filtered); - } - - return { result }; - }, - ({ result }) => { - const bundles = Object.entries(result).slice(first, first + max + 1); - setMessageBundles(bundles); - - const updatedRows = bundles.map((messageBundle) => ({ - rowEditBtnAriaLabel: () => - t("rowEditBtnAriaLabel", { - messageBundle: messageBundle[1], - }), - rowSaveBtnAriaLabel: () => - t("rowSaveBtnAriaLabel", { - messageBundle: messageBundle[1], - }), - rowCancelBtnAriaLabel: () => - t("rowCancelBtnAriaLabel", { - messageBundle: messageBundle[1], - }), - cells: [ - { - title: (value, rowIndex, cellIndex, props) => ( - - ), - props: { - value: messageBundle[0], - }, - }, - { - title: (value, rowIndex, cellIndex, props) => ( - - ), - props: { - value: messageBundle[1], - }, - }, - ], - })); - setTableRows(updatedRows); - - return bundles; - }, - [tableKey, filter, first, max], - ); - - const handleTextInputChange = ( - newValue: string, - evt: any, - rowIndex: number, - cellIndex: number, - ) => { - setTableRows((prev) => { - const newRows = cloneDeep(prev); - const textCell = newRows[rowIndex]?.cells?.[ - cellIndex - ] as IEditableTextCell; - textCell.props.editableValue = newValue; - return newRows; - }); - }; - - const updateEditableRows = async ( - type: RowEditType, - rowIndex?: number, - validationErrors?: RowErrors, - ) => { - if (rowIndex === undefined) { - return; - } - const newRows = cloneDeep(tableRows); - let newRow: IRow; - const invalid = - !!validationErrors && Object.keys(validationErrors).length > 0; - - if (invalid) { - newRow = validateCellEdits(newRows[rowIndex], type, validationErrors); - } else if (type === RowEditAction.Cancel) { - newRow = cancelCellEdits(newRows[rowIndex]); - } else { - newRow = applyCellEdits(newRows[rowIndex], type); - } - newRows[rowIndex] = newRow; - - // Update the copy of the retrieved data set so we can save it when the user saves changes - - if (!invalid && type === RowEditAction.Save) { - const key = (newRow.cells?.[0] as IRowCell).props.value; - const value = (newRow.cells?.[1] as IRowCell).props.value; - - // We only have one editable value, otherwise we'd need to save each - try { - await adminClient.realms.addLocalization( - { - realm: realm.realm!, - selectedLocale: - selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, - key, - }, - value, - ); - addAlert(t("updateMessageBundleSuccess"), AlertVariant.success); - } catch (error) { - addAlert(t("updateMessageBundleError"), AlertVariant.danger); - } - } - setTableRows(newRows); - }; - - const handleModalToggle = () => { - setAddMessageBundleModalOpen(!addMessageBundleModalOpen); - }; - - const options = [ - - - {localeToDisplayName(DEFAULT_LOCALE)} - - , - , - - {watchSupportedLocales.map((locale) => ( - - {localeToDisplayName(locale)} - - ))} - , - ]; - - const addKeyValue = async (pair: KeyValueType): Promise => { - try { - await adminClient.realms.addLocalization( - { - realm: currentRealm!, - selectedLocale: - selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, - key: pair.key, - }, - pair.value, - ); - - adminClient.setConfig({ - realmName: currentRealm!, - }); - refreshTable(); - addAlert(t("addMessageBundleSuccess"), AlertVariant.success); - } catch (error) { - addError(t("addMessageBundleError"), error); - } - }; - - const deleteKey = async (key: string) => { - try { - await adminClient.realms.deleteRealmLocalizationTexts({ - realm: currentRealm!, - selectedLocale: selectMenuLocale, - key, - }); - refreshTable(); - addAlert(t("deleteMessageBundleSuccess")); - } catch (error) { - addError("realm-settings:deleteMessageBundleError", error); - } - }; - return ( - <> - {addMessageBundleModalOpen && ( - { - addKeyValue(pair); - handleModalToggle(); - }} - form={bundleForm} - /> - )} - - - - } + setActiveTab(key as number)} + > + {t("locales")}} + data-testid="rs-localization-locales-tab" + > + + - ( - - )} - /> - - {internationalizationEnabled && ( - <> - - ( - - )} - /> - - - ( - - )} + - - - )} - - - - - - - - - {t("messageBundleDescription")} - -
- { - setFirst(first); - setMax(max); - }} - inputGroupName={"search"} - inputGroupOnEnter={(search) => { - setFilter(search); - setFirst(0); - setMax(10); - }} - inputGroupPlaceholder={t("searchForMessageBundle")} - toolbarItem={ - - } - searchTypeComponent={ - - - } > - {messageBundles.length === 0 && !filter && ( - - )} - {messageBundles.length === 0 && filter && ( - - )} - {messageBundles.length !== 0 && ( - - updateEditableRows(type, rowIndex, validation) - } - actions={[ - { - title: t("delete"), - onClick: (_, row) => - deleteKey( - (tableRows[row].cells?.[0] as IRowCell).props.value, - ), - }, - ]} + ( + + )} + /> + + {internationalizationEnabled && ( + <> + - - -
- )} -
-
-
-
- + ( + + )} + /> + + + ( + + )} + /> + + + )} + + + + + + + + + {t("realmOverrides")}{" "} + + + } + data-testid="rs-localization-realm-overrides-tab" + > + + + + + + {t("effectiveMessageBundles")} + + + } + data-testid="rs-localization-effective-message-bundles-tab" + > + + + + + ); }; diff --git a/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx b/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx new file mode 100644 index 000000000000..d2258db33b20 --- /dev/null +++ b/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx @@ -0,0 +1,7 @@ +import { useTranslation } from "react-i18next"; + +export const EffectiveMessageBundles = () => { + const { t } = useTranslation("realm-settings"); + + return

{t("effectiveMessageBundles")}

; +}; diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx new file mode 100644 index 000000000000..7370afc8e0fc --- /dev/null +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -0,0 +1,7 @@ +import { useTranslation } from "react-i18next"; + +export const RealmOverrides = () => { + const { t } = useTranslation("realm-settings"); + + return

{t("realmOverrides")}

; +}; From 245af3455c9d25c79aa1968330eefe23f1f63f8b Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Thu, 14 Sep 2023 19:14:13 +0100 Subject: [PATCH 02/15] added tab content for realm overrides - wip --- .../src/realm-settings/RealmSettingsTabs.tsx | 2 +- .../localization/EffectiveMessageBundles.tsx | 2 +- .../{ => localization}/LocalizationTab.tsx | 24 +- .../localization/RealmOverrides.tsx | 425 +++++++++++++++++- 4 files changed, 439 insertions(+), 14 deletions(-) rename js/apps/admin-ui/src/realm-settings/{ => localization}/LocalizationTab.tsx (93%) diff --git a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx index 7d8e45708a90..fcb6d3554c61 100644 --- a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx +++ b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx @@ -32,7 +32,7 @@ import { convertFormValuesToObject, convertToFormValues } from "../util"; import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled"; import { RealmSettingsEmailTab } from "./EmailTab"; import { RealmSettingsGeneralTab } from "./GeneralTab"; -import { LocalizationTab } from "./LocalizationTab"; +import { LocalizationTab } from "./localization/LocalizationTab"; import { RealmSettingsLoginTab } from "./LoginTab"; import { PartialExportDialog } from "./PartialExport"; import { PartialImportDialog } from "./PartialImport"; diff --git a/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx b/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx index d2258db33b20..71d748434361 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/EffectiveMessageBundles.tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; export const EffectiveMessageBundles = () => { - const { t } = useTranslation("realm-settings"); + const { t } = useTranslation(); return

{t("effectiveMessageBundles")}

; }; diff --git a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx b/js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx similarity index 93% rename from js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx rename to js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx index f6de05550de8..c15361023bf8 100644 --- a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx @@ -16,12 +16,12 @@ import { useEffect, useMemo, useState } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { HelpItem } from "ui-shared"; -import { FormAccess } from "../components/form/FormAccess"; -import { useServerInfo } from "../context/server-info/ServerInfoProvider"; -import { DEFAULT_LOCALE } from "../i18n/i18n"; -import { convertToFormValues } from "../util"; -import { RealmOverrides } from "./localization/RealmOverrides"; -import { EffectiveMessageBundles } from "./localization/EffectiveMessageBundles"; +import { FormAccess } from "../../components/form/FormAccess"; +import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; +import { DEFAULT_LOCALE } from "../../i18n/i18n"; +import { convertToFormValues } from "../../util"; +import { RealmOverrides } from "./RealmOverrides"; +import { EffectiveMessageBundles } from "./EffectiveMessageBundles"; type LocalizationTabProps = { save: (realm: RealmRepresentation) => void; @@ -62,11 +62,13 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { }; useEffect(setupForm, []); + const watchSupportedLocales: string[] = useWatch({ control, name: "supportedLocales", defaultValue: defaultSupportedLocales, }); + const internationalizationEnabled = useWatch({ control, name: "internationalizationEnabled", @@ -243,7 +245,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { {t("realmOverrides")}{" "} { data-testid="rs-localization-realm-overrides-tab" > - + { {t("effectiveMessageBundles")} { - const { t } = useTranslation("realm-settings"); +type RealmOverridesProps = { + internationalizationEnabled: boolean; + watchSupportedLocales: string[]; + realm: RealmRepresentation; +}; + +export type BundleForm = { + key: string; + value: string; + messageBundle: KeyValueType; +}; + +export enum RowEditAction { + Save = "save", + Cancel = "cancel", + Edit = "edit", + Delete = "delete", +} + +const localeToDisplayName = (locale: string) => { + try { + return new Intl.DisplayNames([locale], { type: "language" }).of(locale); + } catch (error) { + return locale; + } +}; + +export const RealmOverrides = ({ + internationalizationEnabled, + watchSupportedLocales, + realm, +}: RealmOverridesProps) => { + const { t } = useTranslation(); + const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] = + useState(false); + const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); + const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); + const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); + const { getValues } = useForm(); + const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); + const [tableRows, setTableRows] = useState([]); + const [tableKey, setTableKey] = useState(0); + const [max, setMax] = useState(10); + const [first, setFirst] = useState(0); + const [filter, setFilter] = useState(""); + const bundleForm = useForm({ mode: "onChange" }); + const { addAlert, addError } = useAlerts(); + const { realm: currentRealm } = useRealm(); + const { whoAmI } = useWhoAmI(); + + const refreshTable = () => { + setTableKey(tableKey + 1); + }; + + useFetch( + async () => { + let result = await adminClient.realms + .getRealmLocalizationTexts({ + first, + max, + realm: realm.realm!, + selectedLocale: + selectMenuLocale || + getValues("defaultLocale") || + whoAmI.getLocale(), + }) + // prevents server error in dev mode due to snowpack + .catch(() => []); + + const searchInBundles = (idx: number) => { + return Object.entries(result).filter((i) => i[idx].includes(filter)); + }; + + if (filter) { + const filtered = uniqWith( + searchInBundles(0).concat(searchInBundles(1)), + isEqual, + ); + + result = Object.fromEntries(filtered); + } + + return { result }; + }, + ({ result }) => { + const bundles = Object.entries(result).slice(first, first + max + 1); + setMessageBundles(bundles); + + const updatedRows = bundles.map((messageBundle) => ({ + rowEditBtnAriaLabel: () => + t("rowEditBtnAriaLabel", { + messageBundle: messageBundle[1], + }), + rowSaveBtnAriaLabel: () => + t("rowSaveBtnAriaLabel", { + messageBundle: messageBundle[1], + }), + rowCancelBtnAriaLabel: () => + t("rowCancelBtnAriaLabel", { + messageBundle: messageBundle[1], + }), + cells: [ + { + title: (value, rowIndex, cellIndex, props) => ( + + ), + props: { + value: messageBundle[0], + }, + }, + { + title: (value, rowIndex, cellIndex, props) => ( + + ), + props: { + value: messageBundle[1], + }, + }, + ], + })); + setTableRows(updatedRows); + + return bundles; + }, + [tableKey, filter, first, max], + ); + + const handleTextInputChange = ( + newValue: string, + evt: any, + rowIndex: number, + cellIndex: number, + ) => { + setTableRows((prev) => { + const newRows = cloneDeep(prev); + const textCell = newRows[rowIndex]?.cells?.[ + cellIndex + ] as IEditableTextCell; + textCell.props.editableValue = newValue; + return newRows; + }); + }; + + const updateEditableRows = async ( + type: RowEditType, + rowIndex?: number, + validationErrors?: RowErrors, + ) => { + if (rowIndex === undefined) { + return; + } + const newRows = cloneDeep(tableRows); + let newRow: IRow; + const invalid = + !!validationErrors && Object.keys(validationErrors).length > 0; + + if (invalid) { + newRow = validateCellEdits(newRows[rowIndex], type, validationErrors); + } else if (type === RowEditAction.Cancel) { + newRow = cancelCellEdits(newRows[rowIndex]); + } else { + newRow = applyCellEdits(newRows[rowIndex], type); + } + newRows[rowIndex] = newRow; + + // Update the copy of the retrieved data set so we can save it when the user saves changes + + if (!invalid && type === RowEditAction.Save) { + const key = (newRow.cells?.[0] as IRowCell).props.value; + const value = (newRow.cells?.[1] as IRowCell).props.value; + + // We only have one editable value, otherwise we'd need to save each + try { + await adminClient.realms.addLocalization( + { + realm: realm.realm!, + selectedLocale: + selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, + key, + }, + value, + ); + addAlert(t("updateMessageBundleSuccess"), AlertVariant.success); + } catch (error) { + addAlert(t("updateMessageBundleError"), AlertVariant.danger); + } + } + setTableRows(newRows); + }; + + const handleModalToggle = () => { + setAddMessageBundleModalOpen(!addMessageBundleModalOpen); + }; + + const options = [ + + + {localeToDisplayName(DEFAULT_LOCALE)} + + , + , + + {watchSupportedLocales.map((locale) => ( + + {localeToDisplayName(locale)} + + ))} + , + ]; + + const addKeyValue = async (pair: KeyValueType): Promise => { + try { + await adminClient.realms.addLocalization( + { + realm: currentRealm!, + selectedLocale: + selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, + key: pair.key, + }, + pair.value, + ); + + adminClient.setConfig({ + realmName: currentRealm!, + }); + refreshTable(); + addAlert(t("addMessageBundleSuccess"), AlertVariant.success); + } catch (error) { + addError(t("addMessageBundleError"), error); + } + }; + + const deleteKey = async (key: string) => { + try { + await adminClient.realms.deleteRealmLocalizationTexts({ + realm: currentRealm!, + selectedLocale: selectMenuLocale, + key, + }); + refreshTable(); + addAlert(t("deleteMessageBundleSuccess")); + } catch (error) { + addError("realm-settings:deleteMessageBundleError", error); + } + }; - return

{t("realmOverrides")}

; + return ( + <> + {addMessageBundleModalOpen && ( + { + addKeyValue(pair); + handleModalToggle(); + }} + form={bundleForm} + /> + )} + + + + {t("messageBundleDescription")} + +
+ { + setFirst(first); + setMax(max); + }} + inputGroupName={"search"} + inputGroupOnEnter={(search) => { + setFilter(search); + setFirst(0); + setMax(10); + }} + inputGroupPlaceholder={t("searchForMessageBundle")} + toolbarItem={ + + } + searchTypeComponent={ + + + + } + > + {messageBundles.length === 0 && !filter && ( + + )} + {messageBundles.length === 0 && filter && ( + + )} + {messageBundles.length !== 0 && ( + + updateEditableRows(type, rowIndex, validation) + } + actions={[ + { + title: t("delete"), + onClick: (_, row) => + deleteKey( + (tableRows[row].cells?.[0] as IRowCell).props.value, + ), + }, + ]} + > + + +
+ )} +
+
+
+
+ + ); }; From e8a0cfdacd99732a0afabd96d3b4d47eac913ba8 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Thu, 14 Sep 2023 19:45:16 +0100 Subject: [PATCH 03/15] cleaned up realm overrides tab --- .../public/locales/en/translation.json | 1 - .../public/locales/zh-CN/translation.json | 1 - .../localization/RealmOverrides.tsx | 202 +++++++++--------- .../realm-settings/realm-settings-section.css | 17 +- 4 files changed, 100 insertions(+), 121 deletions(-) diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index 246d98036fa1..c2b1ed1fadd6 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -2154,7 +2154,6 @@ "events-disable-confirm": "If \"Save events\" is disabled, subsequent events will not be displayed in the \"Events\" menu", "noMessageBundles": "No message bundles", "noMessageBundlesInstructions": "Add a message bundle to get started.", - "messageBundleDescription": "You can edit the supported locales. If you haven't selected supported locales yet, you can only edit the English locale.", "defaultRoles": "Default roles", "defaultGroups": "Default groups", "whatIsDefaultGroups": "What is the function of default groups?", diff --git a/js/apps/admin-ui/public/locales/zh-CN/translation.json b/js/apps/admin-ui/public/locales/zh-CN/translation.json index 79df6026bd5e..3b9a839ee446 100644 --- a/js/apps/admin-ui/public/locales/zh-CN/translation.json +++ b/js/apps/admin-ui/public/locales/zh-CN/translation.json @@ -2113,7 +2113,6 @@ "events-disable-confirm": "如果禁用“保存事件”,后续事件将不会展示在“事件”菜单中。", "noMessageBundles": "没有消息包", "noMessageBundlesInstructions": "添加消息包以开始使用。", - "messageBundleDescription": "您可以编辑支持的语言环境。如果您还没有选择支持的语言环境,您只能编辑英文语言环境。", "defaultRoles": "默认角色", "defaultGroups": "默认群组", "whatIsDefaultGroups": "默认群组的作用是什么?", diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index ad0ca6abc6b6..77dcde45975c 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -3,16 +3,13 @@ import { AlertVariant, Button, Divider, - PageSection, Select, SelectGroup, SelectOption, SelectVariant, - TextContent, ToolbarItem, } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; -import { FormPanel } from "../../components/scroll-form/FormPanel"; import { PaginatingTableToolbar } from "../../components/table-toolbar/PaginatingTableToolbar"; import { useFetch } from "../../utils/useFetch"; import { adminClient } from "../../admin-client"; @@ -316,111 +313,104 @@ export const RealmOverrides = ({ form={bundleForm} /> )} - - - - {t("messageBundleDescription")} - -
- { - setFirst(first); - setMax(max); - }} - inputGroupName={"search"} - inputGroupOnEnter={(search) => { - setFilter(search); - setFirst(0); - setMax(10); - }} - inputGroupPlaceholder={t("searchForMessageBundle")} - toolbarItem={ - - } - searchTypeComponent={ - - - +
+ { + setFirst(first); + setMax(max); + }} + inputGroupName={"search"} + inputGroupOnEnter={(search) => { + setFilter(search); + setFirst(0); + setMax(10); + }} + inputGroupPlaceholder={t("searchForMessageBundle")} + toolbarItem={ + + } + searchTypeComponent={ + + + + } + > + {messageBundles.length === 0 && !filter && ( + + )} + {messageBundles.length === 0 && filter && ( + + )} + {messageBundles.length !== 0 && ( + + updateEditableRows(type, rowIndex, validation) } + actions={[ + { + title: t("delete"), + onClick: (_, row) => + deleteKey( + (tableRows[row].cells?.[0] as IRowCell).props.value, + ), + }, + ]} > - {messageBundles.length === 0 && !filter && ( - - )} - {messageBundles.length === 0 && filter && ( - - )} - {messageBundles.length !== 0 && ( -
- updateEditableRows(type, rowIndex, validation) - } - actions={[ - { - title: t("delete"), - onClick: (_, row) => - deleteKey( - (tableRows[row].cells?.[0] as IRowCell).props.value, - ), - }, - ]} - > - - -
- )} -
-
- - + + + + )} +
+
); }; diff --git a/js/apps/admin-ui/src/realm-settings/realm-settings-section.css b/js/apps/admin-ui/src/realm-settings/realm-settings-section.css index 00917fe5dc1e..29e7c814a395 100644 --- a/js/apps/admin-ui/src/realm-settings/realm-settings-section.css +++ b/js/apps/admin-ui/src/realm-settings/realm-settings-section.css @@ -3,7 +3,6 @@ .pf-c-card.pf-m-flat.kc-user-info-settings, .pf-c-card.pf-m-flat.kc-email-template, .pf-c-card.pf-m-flat.kc-email-connection, -.pf-c-card.pf-m-flat.kc-message-bundles, .pf-c-card.pf-m-flat.kc-sso-session-template, .pf-c-card.pf-m-flat.kc-client-session-template, .pf-c-card.pf-m-flat.kc-offline-session-template, @@ -90,18 +89,6 @@ div#offline-session-max-label > .pf-c-form__group-label { overflow: visible; } -.pf-c-content.messageBundleDescription { - max-width: 1024px; - padding-bottom: var(--pf-global--spacer--lg); -} - -.kc-message-bundles > .pf-c-card__body.kc-form-panel__body > div.tableBorder { - border-style: solid; - border-width: 1px; - border-color: var(--pf-global--BorderColor--100); - max-width: 1024px; -} - .pf-c-form__group.kc-email-form-group { display: inline-block !important; } @@ -297,3 +284,7 @@ input#kc-scopes { .kc--attributes-validations--action-cell { text-align: right; } + +.kc-localization-table { + margin-left: -10px; +} From ad16fd80047eacb7a955e54029b45e1ad8c6d7fd Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Mon, 18 Sep 2023 17:08:11 +0100 Subject: [PATCH 04/15] adding realm overrides - wip --- .../public/locales/en/translation.json | 3 +- .../src/client-scopes/ClientScopesSection.tsx | 2 + .../src/realm-settings/RealmSettingsTabs.tsx | 7 +- .../localization/LocalizationTab.tsx | 1 - .../localization/RealmOverrides.tsx | 97 +++++++++++++++++-- 5 files changed, 93 insertions(+), 17 deletions(-) diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index c2b1ed1fadd6..9d8d7e87f9a6 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -3286,5 +3286,6 @@ "realmOverrides": "Realm overrides ", "realmOverridesHelp": "You can edit the supported locales. If you haven't selected supported locale yet, you only can edit English locale.", "effectiveMessageBundles": "Effective message bundles ", - "effectiveMessageBundlesHelp": "Todo" + "effectiveMessageBundlesHelp": "Todo", + "deleteMessageBundle": "Delete message bundle {{key}}" } diff --git a/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx b/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx index 31069d595be7..8a4de5259e46 100644 --- a/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx +++ b/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx @@ -142,6 +142,8 @@ export default function ClientScopesSection() { }) .filter(filter); + console.log(">>>> transformed ", transformed); + return localeSort(transformed, mapByKey("name")).slice( first, Number(first) + Number(max), diff --git a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx index fcb6d3554c61..766c8df00d9d 100644 --- a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx +++ b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx @@ -324,12 +324,7 @@ export const RealmSettingsTabs = ({ data-testid="rs-localization-tab" {...localizationTab} > - +
{t("securityDefences")}
} diff --git a/js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx b/js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx index c15361023bf8..681315571636 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/LocalizationTab.tsx @@ -25,7 +25,6 @@ import { EffectiveMessageBundles } from "./EffectiveMessageBundles"; type LocalizationTabProps = { save: (realm: RealmRepresentation) => void; - refresh: () => void; realm: RealmRepresentation; }; diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 77dcde45975c..9a2bcb10e24b 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -3,6 +3,9 @@ import { AlertVariant, Button, Divider, + Dropdown, + DropdownItem, + KebabToggle, Select, SelectGroup, SelectOption, @@ -78,6 +81,7 @@ export const RealmOverrides = ({ const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); + const [kebabOpen, setKebabOpen] = useState(false); const { getValues } = useForm(); const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); const [tableRows, setTableRows] = useState([]); @@ -89,11 +93,15 @@ export const RealmOverrides = ({ const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); + // const [selectedRows, setSelectedRows] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); const refreshTable = () => { setTableKey(tableKey + 1); }; + console.log(">>> selectedRows ", selectedRows); + useFetch( async () => { let result = await adminClient.realms @@ -174,6 +182,7 @@ export const RealmOverrides = ({ }, }, ], + isSelected: false, })); setTableRows(updatedRows); @@ -297,12 +306,53 @@ export const RealmOverrides = ({ refreshTable(); addAlert(t("deleteMessageBundleSuccess")); } catch (error) { - addError("realm-settings:deleteMessageBundleError", error); + addError("deleteMessageBundleError", error); + } + }; + + // const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + // titleKey: t("deleteMessageBundle", { + // count: selectedMessageBundles.length, + // name: selectedMessageBundles[0]?.key, + // }), + // messageKey: "", + // continueButtonLabel: "delete", + // continueButtonVariant: ButtonVariant.danger, + // onConfirm: async () => { + // try { + // await adminClient.realms.deleteRealmLocalizationTexts({ + // realm: currentRealm!, + // selectedLocale: selectMenuLocale, + // key: selectedMessageBundles[0]?.key, + // }); + // refreshTable(); + // addAlert(t("deleteMessageBundleSuccess")); + // } catch (error) { + // addError("deleteMessageBundleError", error); + // } + // }, + // }); + + const handleRowSelect = (isSelected: boolean, rowIndex: number) => { + if (isSelected) { + const updatedTableRows = [...tableRows]; + updatedTableRows[rowIndex].isSelected = isSelected; + setSelectedRows((prevSelected) => [ + ...prevSelected, + updatedTableRows[rowIndex], + ]); + } else { + setSelectedRows((prevSelected) => + prevSelected.filter((row) => row !== tableRows[rowIndex]), + ); } }; + console.log("selectedRows ", selectedRows); + return ( <> + {/* */} {addMessageBundleModalOpen && ( setAddMessageBundleModalOpen(true)} - > - {t("addMessageBundle")} - + <> + + + } + isOpen={kebabOpen} + isPlain + dropdownItems={[ + { + // toggleDeleteDialog(); + setKebabOpen(false); + }} + > + {t("delete")} + , + ]} + /> + + } searchTypeComponent={ @@ -392,16 +464,23 @@ export const RealmOverrides = ({ variant={TableVariant.compact} cells={[t("key"), t("value")]} rows={tableRows} + onSelect={(event, isSelected, rowIndex) => { + console.log("isSelected ", isSelected, rowIndex); + handleRowSelect(isSelected, rowIndex); + }} + // canSelectAll={canSelectAll} + selectVariant="checkbox" onRowEdit={(_, type, _b, rowIndex, validation) => updateEditableRows(type, rowIndex, validation) } actions={[ { title: t("delete"), - onClick: (_, row) => + onClick: (_, row) => { deleteKey( (tableRows[row].cells?.[0] as IRowCell).props.value, - ), + ); + }, }, ]} > From f1868322dc375e0c4d66e0ade70eb9b313cd4b31 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Tue, 19 Sep 2023 11:17:43 +0100 Subject: [PATCH 05/15] realm overrides - wip --- .../localization/RealmOverrides.tsx | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 9a2bcb10e24b..3a80516957c5 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { AlertVariant, Button, + ButtonVariant, Divider, Dropdown, DropdownItem, @@ -42,6 +43,7 @@ import { useWhoAmI } from "../../context/whoami/WhoAmI"; import { useAlerts } from "../../components/alert/Alerts"; import { KeyValueType } from "../../components/key-value-form/key-value-convert"; import RealmRepresentation from "libs/keycloak-admin-client/lib/defs/realmRepresentation"; +import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; type RealmOverridesProps = { internationalizationEnabled: boolean; @@ -93,14 +95,14 @@ export const RealmOverrides = ({ const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); - // const [selectedRows, setSelectedRows] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); + const [selectedRows, setSelectedRows] = useState([]); + const [selectedKey, setSelectedKey] = useState(""); const refreshTable = () => { setTableKey(tableKey + 1); }; - console.log(">>> selectedRows ", selectedRows); + console.log(">>> selectedKey ", selectedKey); useFetch( async () => { @@ -310,28 +312,27 @@ export const RealmOverrides = ({ } }; - // const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ - // titleKey: t("deleteMessageBundle", { - // count: selectedMessageBundles.length, - // name: selectedMessageBundles[0]?.key, - // }), - // messageKey: "", - // continueButtonLabel: "delete", - // continueButtonVariant: ButtonVariant.danger, - // onConfirm: async () => { - // try { - // await adminClient.realms.deleteRealmLocalizationTexts({ - // realm: currentRealm!, - // selectedLocale: selectMenuLocale, - // key: selectedMessageBundles[0]?.key, - // }); - // refreshTable(); - // addAlert(t("deleteMessageBundleSuccess")); - // } catch (error) { - // addError("deleteMessageBundleError", error); - // } - // }, - // }); + const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + titleKey: t("deleteMessageBundle", { + key: selectedKey, + }), + messageKey: "", + continueButtonLabel: "delete", + continueButtonVariant: ButtonVariant.danger, + onConfirm: async () => { + try { + await adminClient.realms.deleteRealmLocalizationTexts({ + realm: currentRealm!, + selectedLocale: selectMenuLocale, + key: selectedKey, + }); + refreshTable(); + addAlert(t("deleteMessageBundleSuccess")); + } catch (error) { + addError("deleteMessageBundleError", error); + } + }, + }); const handleRowSelect = (isSelected: boolean, rowIndex: number) => { if (isSelected) { @@ -352,7 +353,7 @@ export const RealmOverrides = ({ return ( <> - {/* */} + {addMessageBundleModalOpen && ( { - // toggleDeleteDialog(); + toggleDeleteDialog(); setKebabOpen(false); }} > @@ -465,8 +466,11 @@ export const RealmOverrides = ({ cells={[t("key"), t("value")]} rows={tableRows} onSelect={(event, isSelected, rowIndex) => { - console.log("isSelected ", isSelected, rowIndex); + console.log("row .... ", rowIndex); handleRowSelect(isSelected, rowIndex); + setSelectedKey( + (tableRows[rowIndex].cells?.[0] as IRowCell).props.value, + ); }} // canSelectAll={canSelectAll} selectVariant="checkbox" From caac171db0db6b16c19b2d4241f3b5bb33aa3ade Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Wed, 4 Oct 2023 14:15:56 +0100 Subject: [PATCH 06/15] cleanup --- .../realm-settings/localization/RealmOverrides.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 3a80516957c5..d7ffb7ecfaf7 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -102,8 +102,6 @@ export const RealmOverrides = ({ setTableKey(tableKey + 1); }; - console.log(">>> selectedKey ", selectedKey); - useFetch( async () => { let result = await adminClient.realms @@ -116,7 +114,6 @@ export const RealmOverrides = ({ getValues("defaultLocale") || whoAmI.getLocale(), }) - // prevents server error in dev mode due to snowpack .catch(() => []); const searchInBundles = (idx: number) => { @@ -338,13 +335,10 @@ export const RealmOverrides = ({ if (isSelected) { const updatedTableRows = [...tableRows]; updatedTableRows[rowIndex].isSelected = isSelected; - setSelectedRows((prevSelected) => [ - ...prevSelected, - updatedTableRows[rowIndex], - ]); + setSelectedRows((prevSelected) => [...prevSelected, rowIndex]); } else { setSelectedRows((prevSelected) => - prevSelected.filter((row) => row !== tableRows[rowIndex]), + prevSelected.filter((rowIndex) => rowIndex !== rowIndex), ); } }; @@ -472,7 +466,7 @@ export const RealmOverrides = ({ (tableRows[rowIndex].cells?.[0] as IRowCell).props.value, ); }} - // canSelectAll={canSelectAll} + canSelectAll selectVariant="checkbox" onRowEdit={(_, type, _b, rowIndex, validation) => updateEditableRows(type, rowIndex, validation) From 8b0c6ac943a236e791518e90b94dd3b0a9965255 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Wed, 4 Oct 2023 14:23:11 +0100 Subject: [PATCH 07/15] cleanup --- .../src/realm-settings/LocalizationTab.tsx | 612 ------------------ 1 file changed, 612 deletions(-) delete mode 100644 js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx diff --git a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx b/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx deleted file mode 100644 index 181efd897e6c..000000000000 --- a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx +++ /dev/null @@ -1,612 +0,0 @@ -import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; -import { - ActionGroup, - AlertVariant, - Button, - Divider, - FormGroup, - PageSection, - Select, - SelectGroup, - SelectOption, - SelectVariant, - Switch, - TextContent, - ToolbarItem, -} from "@patternfly/react-core"; -import { SearchIcon } from "@patternfly/react-icons"; -import { - EditableTextCell, - IEditableTextCell, - IRow, - IRowCell, - RowEditType, - RowErrors, - Table, - TableBody, - TableHeader, - TableVariant, - applyCellEdits, - cancelCellEdits, - validateCellEdits, -} from "@patternfly/react-table"; -import { cloneDeep, isEqual, uniqWith } from "lodash-es"; -import { useEffect, useMemo, useState } from "react"; -import { Controller, useForm, useWatch } from "react-hook-form"; -import { useTranslation } from "react-i18next"; -import { HelpItem } from "ui-shared"; - -import { adminClient } from "../admin-client"; -import { useAlerts } from "../components/alert/Alerts"; -import { FormAccess } from "../components/form/FormAccess"; -import type { KeyValueType } from "../components/key-value-form/key-value-convert"; -import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; -import { FormPanel } from "../components/scroll-form/FormPanel"; -import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar"; -import { useRealm } from "../context/realm-context/RealmContext"; -import { useServerInfo } from "../context/server-info/ServerInfoProvider"; -import { useWhoAmI } from "../context/whoami/WhoAmI"; -import { DEFAULT_LOCALE } from "../i18n/i18n"; -import { convertToFormValues } from "../util"; -import { useFetch } from "../utils/useFetch"; -import { AddMessageBundleModal } from "./AddMessageBundleModal"; - -type LocalizationTabProps = { - save: (realm: RealmRepresentation) => void; - refresh: () => void; - realm: RealmRepresentation; -}; - -export enum RowEditAction { - Save = "save", - Cancel = "cancel", - Edit = "edit", - Delete = "delete", -} - -export type BundleForm = { - key: string; - value: string; - messageBundle: KeyValueType; -}; - -const localeToDisplayName = (locale: string) => { - try { - return new Intl.DisplayNames([locale], { type: "language" }).of(locale); - } catch (error) { - return locale; - } -}; - -export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { - const { t } = useTranslation(); - const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] = - useState(false); - - const [supportedLocalesOpen, setSupportedLocalesOpen] = useState(false); - const [defaultLocaleOpen, setDefaultLocaleOpen] = useState(false); - const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); - const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); - - const { setValue, getValues, control, handleSubmit, formState } = useForm(); - const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); - const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); - const [tableRows, setTableRows] = useState([]); - - const themeTypes = useServerInfo().themes!; - const allLocales = useMemo(() => { - const locales = Object.values(themeTypes).flatMap((theme) => - theme.flatMap(({ locales }) => (locales ? locales : [])), - ); - return Array.from(new Set(locales)); - }, [themeTypes]); - const bundleForm = useForm({ mode: "onChange" }); - const { addAlert, addError } = useAlerts(); - const { realm: currentRealm } = useRealm(); - const { whoAmI } = useWhoAmI(); - - const defaultSupportedLocales = realm.supportedLocales?.length - ? realm.supportedLocales - : [DEFAULT_LOCALE]; - - const setupForm = () => { - convertToFormValues(realm, setValue); - setValue("supportedLocales", defaultSupportedLocales); - }; - - useEffect(setupForm, []); - - const watchSupportedLocales: string[] = useWatch({ - control, - name: "supportedLocales", - defaultValue: defaultSupportedLocales, - }); - const internationalizationEnabled = useWatch({ - control, - name: "internationalizationEnabled", - defaultValue: realm.internationalizationEnabled, - }); - - const [tableKey, setTableKey] = useState(0); - const [max, setMax] = useState(10); - const [first, setFirst] = useState(0); - const [filter, setFilter] = useState(""); - - const refreshTable = () => { - setTableKey(tableKey + 1); - }; - - useFetch( - async () => { - let result = await adminClient.realms - .getRealmLocalizationTexts({ - first, - max, - realm: realm.realm!, - selectedLocale: - selectMenuLocale || - getValues("defaultLocale") || - whoAmI.getLocale(), - }) - // prevents server error in dev mode due to snowpack - .catch(() => []); - - const searchInBundles = (idx: number) => { - return Object.entries(result).filter((i) => i[idx].includes(filter)); - }; - - if (filter) { - const filtered = uniqWith( - searchInBundles(0).concat(searchInBundles(1)), - isEqual, - ); - - result = Object.fromEntries(filtered); - } - - return { result }; - }, - ({ result }) => { - const bundles = Object.entries(result).slice(first, first + max + 1); - setMessageBundles(bundles); - - const updatedRows = bundles.map((messageBundle) => ({ - rowEditBtnAriaLabel: () => - t("rowEditBtnAriaLabel", { - messageBundle: messageBundle[1], - }), - rowSaveBtnAriaLabel: () => - t("rowSaveBtnAriaLabel", { - messageBundle: messageBundle[1], - }), - rowCancelBtnAriaLabel: () => - t("rowCancelBtnAriaLabel", { - messageBundle: messageBundle[1], - }), - cells: [ - { - title: (value, rowIndex, cellIndex, props) => ( - - ), - props: { - value: messageBundle[0], - }, - }, - { - title: (value, rowIndex, cellIndex, props) => ( - - ), - props: { - value: messageBundle[1], - }, - }, - ], - })); - setTableRows(updatedRows); - - return bundles; - }, - [tableKey, filter, first, max], - ); - - const handleTextInputChange = ( - newValue: string, - evt: any, - rowIndex: number, - cellIndex: number, - ) => { - setTableRows((prev) => { - const newRows = cloneDeep(prev); - const textCell = newRows[rowIndex]?.cells?.[ - cellIndex - ] as IEditableTextCell; - textCell.props.editableValue = newValue; - return newRows; - }); - }; - - const updateEditableRows = async ( - type: RowEditType, - rowIndex?: number, - validationErrors?: RowErrors, - ) => { - if (rowIndex === undefined) { - return; - } - const newRows = cloneDeep(tableRows); - let newRow: IRow; - const invalid = - !!validationErrors && Object.keys(validationErrors).length > 0; - - if (invalid) { - newRow = validateCellEdits(newRows[rowIndex], type, validationErrors); - } else if (type === RowEditAction.Cancel) { - newRow = cancelCellEdits(newRows[rowIndex]); - } else { - newRow = applyCellEdits(newRows[rowIndex], type); - } - newRows[rowIndex] = newRow; - - // Update the copy of the retrieved data set so we can save it when the user saves changes - - if (!invalid && type === RowEditAction.Save) { - const key = (newRow.cells?.[0] as IRowCell).props.value; - const value = (newRow.cells?.[1] as IRowCell).props.value; - - // We only have one editable value, otherwise we'd need to save each - try { - await adminClient.realms.addLocalization( - { - realm: realm.realm!, - selectedLocale: - selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, - key, - }, - value, - ); - addAlert(t("updateMessageBundleSuccess"), AlertVariant.success); - } catch (error) { - addAlert(t("updateMessageBundleError"), AlertVariant.danger); - } - } - setTableRows(newRows); - }; - - const handleModalToggle = () => { - setAddMessageBundleModalOpen(!addMessageBundleModalOpen); - }; - - const options = [ - - - {localeToDisplayName(DEFAULT_LOCALE)} - - , - , - - {watchSupportedLocales.map((locale) => ( - - {localeToDisplayName(locale)} - - ))} - , - ]; - - const addKeyValue = async (pair: KeyValueType): Promise => { - try { - await adminClient.realms.addLocalization( - { - realm: currentRealm!, - selectedLocale: - selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, - key: pair.key, - }, - pair.value, - ); - - adminClient.setConfig({ - realmName: currentRealm!, - }); - refreshTable(); - addAlert(t("addMessageBundleSuccess"), AlertVariant.success); - } catch (error) { - addError(t("addMessageBundleError"), error); - } - }; - - const deleteKey = async (key: string) => { - try { - await adminClient.realms.deleteRealmLocalizationTexts({ - realm: currentRealm!, - selectedLocale: selectMenuLocale, - key, - }); - refreshTable(); - addAlert(t("deleteMessageBundleSuccess")); - } catch (error) { - addError("deleteMessageBundleError", error); - } - }; - - return ( - <> - {addMessageBundleModalOpen && ( - { - addKeyValue(pair); - handleModalToggle(); - }} - form={bundleForm} - /> - )} - - - - } - > - ( - - )} - /> - - {internationalizationEnabled && ( - <> - - ( - - )} - /> - - - ( - - )} - /> - - - )} - - - - - - - - - {t("messageBundleDescription")} - -
- { - setFirst(first); - setMax(max); - }} - inputGroupName={"search"} - inputGroupOnEnter={(search) => { - setFilter(search); - setFirst(0); - setMax(10); - }} - inputGroupPlaceholder={t("searchForMessageBundle")} - toolbarItem={ - - } - searchTypeComponent={ - - - - } - > - {messageBundles.length === 0 && !filter && ( - - )} - {messageBundles.length === 0 && filter && ( - - )} - {messageBundles.length !== 0 && ( - - updateEditableRows(type, rowIndex, validation) - } - actions={[ - { - title: t("delete"), - onClick: (_, row) => - deleteKey( - (tableRows[row].cells?.[0] as IRowCell).props.value, - ), - }, - ]} - > - - -
- )} -
-
-
-
- - ); -}; From cf2373d4c16279c0e17e5adb5d05bad4db7cf1f4 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Wed, 4 Oct 2023 15:51:06 +0100 Subject: [PATCH 08/15] cleanup --- .../localization/RealmOverrides.tsx | 41 ++----------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index d7ffb7ecfaf7..758deebcf6f0 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { AlertVariant, Button, - ButtonVariant, Divider, Dropdown, DropdownItem, @@ -43,7 +42,6 @@ import { useWhoAmI } from "../../context/whoami/WhoAmI"; import { useAlerts } from "../../components/alert/Alerts"; import { KeyValueType } from "../../components/key-value-form/key-value-convert"; import RealmRepresentation from "libs/keycloak-admin-client/lib/defs/realmRepresentation"; -import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog"; type RealmOverridesProps = { internationalizationEnabled: boolean; @@ -95,8 +93,7 @@ export const RealmOverrides = ({ const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); - const [selectedRows, setSelectedRows] = useState([]); - const [selectedKey, setSelectedKey] = useState(""); + const [selectedRows, setSelectedRows] = useState([]); const refreshTable = () => { setTableKey(tableKey + 1); @@ -309,45 +306,20 @@ export const RealmOverrides = ({ } }; - const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ - titleKey: t("deleteMessageBundle", { - key: selectedKey, - }), - messageKey: "", - continueButtonLabel: "delete", - continueButtonVariant: ButtonVariant.danger, - onConfirm: async () => { - try { - await adminClient.realms.deleteRealmLocalizationTexts({ - realm: currentRealm!, - selectedLocale: selectMenuLocale, - key: selectedKey, - }); - refreshTable(); - addAlert(t("deleteMessageBundleSuccess")); - } catch (error) { - addError("deleteMessageBundleError", error); - } - }, - }); - const handleRowSelect = (isSelected: boolean, rowIndex: number) => { if (isSelected) { - const updatedTableRows = [...tableRows]; - updatedTableRows[rowIndex].isSelected = isSelected; - setSelectedRows((prevSelected) => [...prevSelected, rowIndex]); + setSelectedRows((prevSelected) => [...prevSelected, tableRows[rowIndex]]); } else { setSelectedRows((prevSelected) => - prevSelected.filter((rowIndex) => rowIndex !== rowIndex), + prevSelected.filter((row) => row !== tableRows[rowIndex]), ); } }; - console.log("selectedRows ", selectedRows); + console.log("selectedRows", selectedRows); return ( <> - {addMessageBundleModalOpen && ( { - toggleDeleteDialog(); setKebabOpen(false); }} > @@ -460,11 +431,7 @@ export const RealmOverrides = ({ cells={[t("key"), t("value")]} rows={tableRows} onSelect={(event, isSelected, rowIndex) => { - console.log("row .... ", rowIndex); handleRowSelect(isSelected, rowIndex); - setSelectedKey( - (tableRows[rowIndex].cells?.[0] as IRowCell).props.value, - ); }} canSelectAll selectVariant="checkbox" From 200fef23327d24db96666ddd717e03093e9d6688 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Wed, 4 Oct 2023 17:44:52 +0100 Subject: [PATCH 09/15] cleanup --- .../localization/RealmOverrides.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 758deebcf6f0..52c5d6fa2cb3 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -93,7 +93,6 @@ export const RealmOverrides = ({ const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); - const [selectedRows, setSelectedRows] = useState([]); const refreshTable = () => { setTableKey(tableKey + 1); @@ -225,13 +224,10 @@ export const RealmOverrides = ({ } newRows[rowIndex] = newRow; - // Update the copy of the retrieved data set so we can save it when the user saves changes - if (!invalid && type === RowEditAction.Save) { const key = (newRow.cells?.[0] as IRowCell).props.value; const value = (newRow.cells?.[1] as IRowCell).props.value; - // We only have one editable value, otherwise we'd need to save each try { await adminClient.realms.addLocalization( { @@ -306,17 +302,26 @@ export const RealmOverrides = ({ } }; - const handleRowSelect = (isSelected: boolean, rowIndex: number) => { - if (isSelected) { - setSelectedRows((prevSelected) => [...prevSelected, tableRows[rowIndex]]); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + // Handle row selection + const handleRowSelect = ( + event: React.ChangeEvent, + rowIndex: number, + ) => { + const selectedKey = tableRows[rowIndex].key; + if (event.target.checked) { + setSelectedRowKeys((prevSelected) => [...prevSelected, selectedKey]); } else { - setSelectedRows((prevSelected) => - prevSelected.filter((row) => row !== tableRows[rowIndex]), + setSelectedRowKeys((prevSelected) => + prevSelected.filter((key) => key !== selectedKey), ); } }; - console.log("selectedRows", selectedRows); + const isRowSelected = (rowKey: string) => { + return selectedRowKeys.includes(rowKey); + }; return ( <> @@ -429,9 +434,12 @@ export const RealmOverrides = ({ data-testid="editable-rows-table" variant={TableVariant.compact} cells={[t("key"), t("value")]} - rows={tableRows} + rows={tableRows.map((row) => ({ + ...row, + selected: isRowSelected(row.key), + }))} onSelect={(event, isSelected, rowIndex) => { - handleRowSelect(isSelected, rowIndex); + handleRowSelect(event, rowIndex); }} canSelectAll selectVariant="checkbox" From 14f8dc3947708246753131709d3e6f723ab31bb4 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Wed, 4 Oct 2023 17:57:17 +0100 Subject: [PATCH 10/15] cleanup --- .../src/realm-settings/localization/RealmOverrides.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 52c5d6fa2cb3..58051655c94a 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -93,6 +93,7 @@ export const RealmOverrides = ({ const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); const refreshTable = () => { setTableKey(tableKey + 1); @@ -302,8 +303,6 @@ export const RealmOverrides = ({ } }; - const [selectedRowKeys, setSelectedRowKeys] = useState([]); - // Handle row selection const handleRowSelect = ( event: React.ChangeEvent, @@ -436,7 +435,7 @@ export const RealmOverrides = ({ cells={[t("key"), t("value")]} rows={tableRows.map((row) => ({ ...row, - selected: isRowSelected(row.key), + selected: isRowSelected(row.key), // Set the correct isSelected property }))} onSelect={(event, isSelected, rowIndex) => { handleRowSelect(event, rowIndex); From cf5721c8232cd77f3eb45d6a381303b32b85c79f Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Wed, 4 Oct 2023 20:26:02 +0100 Subject: [PATCH 11/15] added selecting individual rows --- .../src/realm-settings/localization/RealmOverrides.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 58051655c94a..e43f6687b649 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -308,7 +308,8 @@ export const RealmOverrides = ({ event: React.ChangeEvent, rowIndex: number, ) => { - const selectedKey = tableRows[rowIndex].key; + const selectedKey = (tableRows[rowIndex].cells?.[0] as IRowCell).props + .value; if (event.target.checked) { setSelectedRowKeys((prevSelected) => [...prevSelected, selectedKey]); } else { @@ -435,7 +436,9 @@ export const RealmOverrides = ({ cells={[t("key"), t("value")]} rows={tableRows.map((row) => ({ ...row, - selected: isRowSelected(row.key), // Set the correct isSelected property + selected: isRowSelected( + (row.cells?.[0] as IRowCell).props.value, + ), }))} onSelect={(event, isSelected, rowIndex) => { handleRowSelect(event, rowIndex); From ca56009893aa70fa07a182a5869fbdbf5fdef884 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Mon, 9 Oct 2023 15:41:40 +0100 Subject: [PATCH 12/15] localization - wip --- .../localization/RealmOverrides.tsx | 262 ++++++++++++------ .../localization/localization.css | 4 + 2 files changed, 176 insertions(+), 90 deletions(-) create mode 100644 js/apps/admin-ui/src/realm-settings/localization/localization.css diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index e43f6687b649..885b5b13d3b2 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -5,6 +5,8 @@ import { Divider, Dropdown, DropdownItem, + Form, + FormGroup, KebabToggle, Select, SelectGroup, @@ -16,23 +18,25 @@ import { useTranslation } from "react-i18next"; import { PaginatingTableToolbar } from "../../components/table-toolbar/PaginatingTableToolbar"; import { useFetch } from "../../utils/useFetch"; import { adminClient } from "../../admin-client"; -import { useForm } from "react-hook-form"; +import { Controller, useForm } from "react-hook-form"; import { EditableTextCell, IEditableTextCell, IRow, IRowCell, - RowEditType, - RowErrors, Table, - TableBody, - TableHeader, - TableVariant, - applyCellEdits, - cancelCellEdits, - validateCellEdits, + Tbody, + Td, + Th, + Thead, + Tr, } from "@patternfly/react-table"; -import { SearchIcon } from "@patternfly/react-icons"; +import { + CheckIcon, + PencilAltIcon, + SearchIcon, + TimesIcon, +} from "@patternfly/react-icons"; import { AddMessageBundleModal } from "../AddMessageBundleModal"; import { DEFAULT_LOCALE } from "../../i18n/i18n"; import { cloneDeep, isEqual, uniqWith } from "lodash-es"; @@ -42,6 +46,8 @@ import { useWhoAmI } from "../../context/whoami/WhoAmI"; import { useAlerts } from "../../components/alert/Alerts"; import { KeyValueType } from "../../components/key-value-form/key-value-convert"; import RealmRepresentation from "libs/keycloak-admin-client/lib/defs/realmRepresentation"; +import { KeycloakTextInput } from "../../components/keycloak-text-input/KeycloakTextInput"; +import "./localization.css"; type RealmOverridesProps = { internationalizationEnabled: boolean; @@ -82,7 +88,7 @@ export const RealmOverrides = ({ const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); const [kebabOpen, setKebabOpen] = useState(false); - const { getValues } = useForm(); + const { getValues, handleSubmit, control } = useForm(); const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); const [tableRows, setTableRows] = useState([]); const [tableKey, setTableKey] = useState(0); @@ -94,7 +100,9 @@ export const RealmOverrides = ({ const { realm: currentRealm } = useRealm(); const { whoAmI } = useWhoAmI(); const [selectedRowKeys, setSelectedRowKeys] = useState([]); - + const [areAllRowsSelected, setAreAllRowsSelected] = useState(false); + const [isBundleMessageValueEdit, setIsBundleMessageValueEdit] = + useState(false); const refreshTable = () => { setTableKey(tableKey + 1); }; @@ -195,56 +203,25 @@ export const RealmOverrides = ({ ) => { setTableRows((prev) => { const newRows = cloneDeep(prev); - const textCell = newRows[rowIndex]?.cells?.[ - cellIndex - ] as IEditableTextCell; - textCell.props.editableValue = newValue; - return newRows; - }); - }; - const updateEditableRows = async ( - type: RowEditType, - rowIndex?: number, - validationErrors?: RowErrors, - ) => { - if (rowIndex === undefined) { - return; - } - const newRows = cloneDeep(tableRows); - let newRow: IRow; - const invalid = - !!validationErrors && Object.keys(validationErrors).length > 0; - - if (invalid) { - newRow = validateCellEdits(newRows[rowIndex], type, validationErrors); - } else if (type === RowEditAction.Cancel) { - newRow = cancelCellEdits(newRows[rowIndex]); - } else { - newRow = applyCellEdits(newRows[rowIndex], type); - } - newRows[rowIndex] = newRow; - - if (!invalid && type === RowEditAction.Save) { - const key = (newRow.cells?.[0] as IRowCell).props.value; - const value = (newRow.cells?.[1] as IRowCell).props.value; - - try { - await adminClient.realms.addLocalization( - { - realm: realm.realm!, - selectedLocale: - selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, - key, - }, - value, + // Add a check for undefined values + if (!newRows[rowIndex]?.cells?.[cellIndex]) { + console.error( + "Undefined rowIndex, cell or cellIndex:", + rowIndex, + cellIndex, ); - addAlert(t("updateMessageBundleSuccess"), AlertVariant.success); - } catch (error) { - addAlert(t("updateMessageBundleError"), AlertVariant.danger); + return prev; // return the previous state } - } - setTableRows(newRows); + + const textCell = newRows[rowIndex].cells[cellIndex] as + | IEditableTextCell + | undefined; + + // Check if textCell is undefined + textCell?.props.onChange?.(newValue, evt, rowIndex, cellIndex); + return newRows; + }); }; const handleModalToggle = () => { @@ -303,7 +280,6 @@ export const RealmOverrides = ({ } }; - // Handle row selection const handleRowSelect = ( event: React.ChangeEvent, rowIndex: number, @@ -317,10 +293,32 @@ export const RealmOverrides = ({ prevSelected.filter((key) => key !== selectedKey), ); } + + setAreAllRowsSelected( + tableRows.length === + selectedRowKeys.length + (event.target.checked ? 1 : -1), + ); + }; + + const toggleSelectAllRows = () => { + if (areAllRowsSelected) { + setSelectedRowKeys([]); + } else { + setSelectedRowKeys( + tableRows.map((row) => (row.cells?.[0] as IRowCell).props.value), + ); + } + setAreAllRowsSelected(!areAllRowsSelected); }; - const isRowSelected = (rowKey: string) => { - return selectedRowKeys.includes(rowKey); + const isRowSelected = (key: any) => { + return selectedRowKeys.includes(key); + }; + + const onSubmit = (messageBundle: BundleForm) => { + console.log("onSubmit ", messageBundle); + // setValue(messageBundle); + // setIsBundleMessageValueEdit(false); }; return ( @@ -432,35 +430,119 @@ export const RealmOverrides = ({ ({ - ...row, - selected: isRowSelected( - (row.cells?.[0] as IRowCell).props.value, - ), - }))} - onSelect={(event, isSelected, rowIndex) => { - handleRowSelect(event, rowIndex); - }} - canSelectAll - selectVariant="checkbox" - onRowEdit={(_, type, _b, rowIndex, validation) => - updateEditableRows(type, rowIndex, validation) - } - actions={[ - { - title: t("delete"), - onClick: (_, row) => { - deleteKey( - (tableRows[row].cells?.[0] as IRowCell).props.value, - ); - }, - }, - ]} > - - + + + + + + + + + {tableRows.map((row, rowIndex) => ( + + + + + + ))} +
+ + {t("key")}{t("value")}
+ handleRowSelect( + event as React.ChangeEvent, + rowIndex, + ), + isSelected: isRowSelected( + (row.cells?.[0] as IRowCell).props.value, + ), + }} + /> + + {(row.cells?.[0] as IRowCell).props.value} + +
{ + onSubmit(bundleValue.value); + })} + > + +
+ {isBundleMessageValueEdit && ( + <> + ( + + )} + /> +
+
+
+
+ +
)} diff --git a/js/apps/admin-ui/src/realm-settings/localization/localization.css b/js/apps/admin-ui/src/realm-settings/localization/localization.css new file mode 100644 index 000000000000..1a7ab7670529 --- /dev/null +++ b/js/apps/admin-ui/src/realm-settings/localization/localization.css @@ -0,0 +1,4 @@ +.kc-editBundleValue-btn, +.kc-editBundleValue-cancelBtn { + color: var(--pf-global--Color--200) !important; +} From 2570411b84dbeaa81d0f788808d515862191b03c Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Mon, 6 Nov 2023 13:28:13 +0000 Subject: [PATCH 13/15] localization - wip --- .../localization/RealmOverrides.tsx | 28 +++++++++++-------- .../localization/localization.css | 9 ++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 885b5b13d3b2..5f3c151fab1e 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -87,7 +87,7 @@ export const RealmOverrides = ({ const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); - const [kebabOpen, setKebabOpen] = useState(false); + const [kebabOpen1, setKebabOpen1] = useState(false); const { getValues, handleSubmit, control } = useForm(); const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); const [tableRows, setTableRows] = useState([]); @@ -321,14 +321,19 @@ export const RealmOverrides = ({ // setIsBundleMessageValueEdit(false); }; + const toggleKebab1 = () => { + setKebabOpen1(!kebabOpen1); + }; + return ( <> {addMessageBundleModalOpen && ( { + save={(pair: any, e) => { addKeyValue(pair); handleModalToggle(); + e.preventDefault(); }} form={bundleForm} /> @@ -361,17 +366,15 @@ export const RealmOverrides = ({ } - isOpen={kebabOpen} + toggle={} + isOpen={kebabOpen1} isPlain dropdownItems={[ { - setKebabOpen(false); - }} + onClick={() => setKebabOpen1(false)} > {t("delete")} , @@ -532,13 +535,14 @@ export const RealmOverrides = ({ - + /> ))} diff --git a/js/apps/admin-ui/src/realm-settings/localization/localization.css b/js/apps/admin-ui/src/realm-settings/localization/localization.css index 1a7ab7670529..0edc99c04c8b 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/localization.css +++ b/js/apps/admin-ui/src/realm-settings/localization/localization.css @@ -2,3 +2,12 @@ .kc-editBundleValue-cancelBtn { color: var(--pf-global--Color--200) !important; } + +.kc-realmOverrides-kebabToggle { + color: var(--pf-c-dropdown__toggle--m-plain--Color); + --pf-c-dropdown__toggle--before--BorderWidth: none; +} + +.kc-realmOverrides-kebabToggle:hover { + color: var(--pf-c-dropdown__toggle--Color); +} From 08177636713984c98075c59f2a22c09568c70ee8 Mon Sep 17 00:00:00 2001 From: Agnieszka Gancarczyk Date: Mon, 6 Nov 2023 19:01:58 +0000 Subject: [PATCH 14/15] localization - wip --- .../localization/RealmOverrides.tsx | 87 ++++++++++++------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx index 5f3c151fab1e..aa904927402f 100644 --- a/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx +++ b/js/apps/admin-ui/src/realm-settings/localization/RealmOverrides.tsx @@ -87,7 +87,7 @@ export const RealmOverrides = ({ const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); - const [kebabOpen1, setKebabOpen1] = useState(false); + const [kebabOpen, setKebabOpen] = useState(false); const { getValues, handleSubmit, control } = useForm(); const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); const [tableRows, setTableRows] = useState([]); @@ -101,8 +101,9 @@ export const RealmOverrides = ({ const { whoAmI } = useWhoAmI(); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [areAllRowsSelected, setAreAllRowsSelected] = useState(false); - const [isBundleMessageValueEdit, setIsBundleMessageValueEdit] = - useState(false); + const [isBundleMessageValueEdit, setIsBundleMessageValueEdit] = useState( + new Array(tableRows.length).fill(false), + ); const refreshTable = () => { setTableKey(tableKey + 1); }; @@ -188,6 +189,7 @@ export const RealmOverrides = ({ ], isSelected: false, })); + console.log("updatedRows ", updatedRows); setTableRows(updatedRows); return bundles; @@ -204,22 +206,20 @@ export const RealmOverrides = ({ setTableRows((prev) => { const newRows = cloneDeep(prev); - // Add a check for undefined values if (!newRows[rowIndex]?.cells?.[cellIndex]) { console.error( "Undefined rowIndex, cell or cellIndex:", rowIndex, cellIndex, ); - return prev; // return the previous state + return prev; } - const textCell = newRows[rowIndex].cells[cellIndex] as - | IEditableTextCell - | undefined; + const textCell = newRows[rowIndex]?.cells?.[ + cellIndex + ] as IEditableTextCell; - // Check if textCell is undefined - textCell?.props.onChange?.(newValue, evt, rowIndex, cellIndex); + textCell.props.onChange?.(newValue, evt, rowIndex, cellIndex); return newRows; }); }; @@ -315,14 +315,12 @@ export const RealmOverrides = ({ return selectedRowKeys.includes(key); }; - const onSubmit = (messageBundle: BundleForm) => { - console.log("onSubmit ", messageBundle); + const onSubmit = (value: string) => { + console.log("onSubmit ", value); // setValue(messageBundle); - // setIsBundleMessageValueEdit(false); - }; - const toggleKebab1 = () => { - setKebabOpen1(!kebabOpen1); + console.log(">>> isBundleMessageValueEdit ", isBundleMessageValueEdit); + // setIsBundleMessageValueEdit(false); }; return ( @@ -330,10 +328,9 @@ export const RealmOverrides = ({ {addMessageBundleModalOpen && ( { + save={(pair: any) => { addKeyValue(pair); handleModalToggle(); - e.preventDefault(); }} form={bundleForm} /> @@ -366,15 +363,17 @@ export const RealmOverrides = ({ } - isOpen={kebabOpen1} + toggle={ + setKebabOpen(!kebabOpen)} /> + } + isOpen={kebabOpen} isPlain dropdownItems={[ setKebabOpen1(false)} + onClick={() => setKebabOpen(false)} > {t("delete")} , @@ -471,8 +470,8 @@ export const RealmOverrides = ({
{ - onSubmit(bundleValue.value); + onSubmit={handleSubmit(() => { + onSubmit((row.cells?.[1] as IRowCell).props.value); })} >
- {isBundleMessageValueEdit && ( + {isBundleMessageValueEdit[rowIndex] && ( <> } + onClick={() => { + const updatedEditState = [ + ...isBundleMessageValueEdit, + ]; + updatedEditState[rowIndex] = true; + setIsBundleMessageValueEdit( + updatedEditState, + ); + }} /> + )}