From f31881ac2eba50c15a6d7aa8ef150f34f16696c4 Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Mon, 25 Mar 2024 12:16:33 +0800 Subject: [PATCH 1/2] Immediate apply on destructive language change --- .../LocalizationConfigurationScreen.tsx | 56 ++----- .../graphql/portal/ManageLanguageWidget.tsx | 145 ++++++++++++------ .../src/graphql/portal/UISettingsScreen.tsx | 60 ++------ portal/src/hook/useDelayedSave.ts | 26 ++++ 4 files changed, 156 insertions(+), 131 deletions(-) create mode 100644 portal/src/hook/useDelayedSave.ts diff --git a/portal/src/graphql/portal/LocalizationConfigurationScreen.tsx b/portal/src/graphql/portal/LocalizationConfigurationScreen.tsx index c8b3b81bdc..7e87ca6c93 100644 --- a/portal/src/graphql/portal/LocalizationConfigurationScreen.tsx +++ b/portal/src/graphql/portal/LocalizationConfigurationScreen.tsx @@ -60,7 +60,7 @@ import { useResourceForm } from "../../hook/useResourceForm"; import FormContainer from "../../FormContainer"; import { useSystemConfig } from "../../context/SystemConfigContext"; import styles from "./LocalizationConfigurationScreen.module.css"; -import ReplaceLanguagesConfirmationDialog from "./ReplaceLanguagesConfirmationDialog"; +import { useDelayedSave } from "../../hook/useDelayedSave"; interface ConfigFormState { supportedLanguages: string[]; @@ -216,6 +216,18 @@ const ResourcesConfigurationContent: React.VFC { + onChangeLanguages(supportedLanguages, fallbackLanguage); + enqueueSave(); + }, + [enqueueSave, onChangeLanguages] + ); + const [selectedKey, setSelectedKey] = useState(PIVOT_KEY_DEFAULT); const onLinkClick = useCallback((item?: PivotItem) => { const itemKey = item?.props.itemKey; @@ -805,6 +817,7 @@ const ResourcesConfigurationContent: React.VFC @@ -971,21 +984,6 @@ const LocalizationConfigurationScreen: React.VFC = ] ); - const allExistingLanguageAreRemoved = useMemo(() => { - return initialSupportedLanguages.every( - (locale) => !state.supportedLanguages.includes(locale) - ); - }, [initialSupportedLanguages, state.supportedLanguages]); - - const [ - isClearLocalizationConfirmationDialogVisible, - setIsClearLocalizationConfirmationDialogVisible, - ] = useState(false); - - const dismissClearLocalizationConfirmationDialog = useCallback(() => { - setIsClearLocalizationConfirmationDialogVisible(false); - }, []); - const form: FormModel = useMemo( () => ({ isLoading: config.isLoading || resources.isLoading, @@ -1020,25 +1018,6 @@ const LocalizationConfigurationScreen: React.VFC = [config, resources, state] ); - const confirmFormSave = useCallback(async () => { - if (allExistingLanguageAreRemoved) { - setIsClearLocalizationConfirmationDialogVisible(true); - return; - } - - await form.save(); - }, [allExistingLanguageAreRemoved, form]); - - const doFormSave = useCallback(async () => { - dismissClearLocalizationConfirmationDialog(); - await form.save(); - }, [dismissClearLocalizationConfirmationDialog, form]); - - const formWithConfirmation = { - ...form, - save: confirmFormSave, - }; - if (form.isLoading) { return ; } @@ -1048,7 +1027,7 @@ const LocalizationConfigurationScreen: React.VFC = } return ( - + - ); }; diff --git a/portal/src/graphql/portal/ManageLanguageWidget.tsx b/portal/src/graphql/portal/ManageLanguageWidget.tsx index 96e07187c7..96113f9386 100644 --- a/portal/src/graphql/portal/ManageLanguageWidget.tsx +++ b/portal/src/graphql/portal/ManageLanguageWidget.tsx @@ -4,6 +4,7 @@ import React, { useMemo, useState, useEffect, + CSSProperties, } from "react"; import cn from "classnames"; import { @@ -20,7 +21,7 @@ import { List, Text, IRenderFunction, - IDropdownStyles, + useTheme, } from "@fluentui/react"; import { Context, FormattedMessage } from "@oursky/react-messageformat"; @@ -32,6 +33,7 @@ import styles from "./ManageLanguageWidget.module.css"; import PrimaryButton from "../../PrimaryButton"; import DefaultButton from "../../DefaultButton"; import LinkButton from "../../LinkButton"; +import ReplaceLanguagesConfirmationDialog from "./ReplaceLanguagesConfirmationDialog"; interface ManageLanguageWidgetProps { className?: string; @@ -51,6 +53,10 @@ interface ManageLanguageWidgetProps { supportedLanguages: LanguageTag[], fallbackLanguage: LanguageTag ) => void; + onChangeAndSaveLanguages: ( + supportedLanguages: LanguageTag[], + fallbackLanguage: LanguageTag + ) => void; } interface ManageLanguageWidgetDialogProps { @@ -63,6 +69,10 @@ interface ManageLanguageWidgetDialogProps { supportedLanguages: LanguageTag[], fallbackLanguage: LanguageTag ) => void; + onChangeAndSaveLanguages: ( + supportedLanguages: LanguageTag[], + fallbackLanguage: LanguageTag + ) => void; } interface CellProps { @@ -129,20 +139,16 @@ const Cell: React.VFC = function Cell(props: CellProps) { ); }; -const dropdownStyles: Partial = { - dropdownItemDisabled: { - fontStyle: "italic", - }, -}; - const ManageLanguageWidgetDialog: React.VFC = function ManageLanguageWidgetDialog(props: ManageLanguageWidgetDialogProps) { const { presented, onDismiss, fallbackLanguage, + existingLanguages, supportedLanguages, onChangeLanguages, + onChangeAndSaveLanguages, } = props; const { renderToString } = useContext(Context); @@ -170,6 +176,21 @@ const ManageLanguageWidgetDialog: React.VFC = return search(searchString); }, [search, searchString]); + const [ + isClearLocalizationConfirmationDialogVisible, + setIsClearLocalizationConfirmationDialogVisible, + ] = useState(false); + + const dismissClearLocalizationConfirmationDialog = useCallback(() => { + setIsClearLocalizationConfirmationDialogVisible(false); + }, []); + + const allExistingLanguageAreRemoved = useMemo(() => { + return existingLanguages.every( + (locale) => !newSupportedLanguages.includes(locale) + ); + }, [existingLanguages, newSupportedLanguages]); + const onSearch = useCallback((_e, value?: string) => { if (value == null) { return; @@ -238,15 +259,33 @@ const ManageLanguageWidgetDialog: React.VFC = }, [onDismiss]); const onApplyClick = useCallback(() => { + if (allExistingLanguageAreRemoved) { + setIsClearLocalizationConfirmationDialogVisible(true); + return; + } + onChangeLanguages(newSupportedLanguages, newFallbackLanguage); onDismiss(); }, [ + allExistingLanguageAreRemoved, onChangeLanguages, newSupportedLanguages, newFallbackLanguage, onDismiss, ]); + const onConfirmReplaceLanguages = useCallback(() => { + onChangeAndSaveLanguages(newSupportedLanguages, newFallbackLanguage); + dismissClearLocalizationConfirmationDialog(); + onDismiss(); + }, [ + onChangeAndSaveLanguages, + newSupportedLanguages, + newFallbackLanguage, + dismissClearLocalizationConfirmationDialog, + onDismiss, + ]); + const modalProps = useMemo(() => { return { isBlocking: true, @@ -259,42 +298,49 @@ const ManageLanguageWidgetDialog: React.VFC = }, [supportedLanguages, fallbackLanguage]); return ( - + + + +
+ +
+ + } + /> + } + /> + + + + ); }; @@ -308,9 +354,11 @@ const ManageLanguageWidget: React.VFC = onChangeSelectedLanguage, fallbackLanguage, onChangeLanguages, + onChangeAndSaveLanguages, } = props; const { renderToString } = useContext(Context); + const theme = useTheme(); const [isDialogPresented, setIsDialogPresented] = useState(false); @@ -387,8 +435,15 @@ const ManageLanguageWidget: React.VFC = const onRenderOption: IRenderFunction = useCallback( (option?: IDropdownOption) => { + const style: CSSProperties | undefined = option?.disabled + ? { + fontStyle: "italic", + color: theme.semanticColors.disabledText, + } + : undefined; + return ( - + = ); }, - [] + [theme.semanticColors.disabledText] ); const onRenderTitle: IRenderFunction = useCallback( @@ -429,6 +484,7 @@ const ManageLanguageWidget: React.VFC = supportedLanguages={supportedLanguages} fallbackLanguage={fallbackLanguage} onChangeLanguages={onChangeLanguages} + onChangeAndSaveLanguages={onChangeAndSaveLanguages} />
@@ -439,7 +495,6 @@ const ManageLanguageWidget: React.VFC = { + onChangeLanguages(supportedLanguages, fallbackLanguage); + enqueueSave(); + }, + [enqueueSave, onChangeLanguages] + ); + const getValueIgnoreEmptyString = useCallback( (def: ResourceDefinition) => { for (const extension of def.extensions) { @@ -673,6 +685,7 @@ const ResourcesConfigurationContent: React.VFC
@@ -887,21 +900,6 @@ const UISettingsScreen: React.VFC = function UISettingsScreen() { ] ); - const allExistingLanguageAreRemoved = useMemo(() => { - return initialSupportedLanguages.every( - (locale) => !state.supportedLanguages.includes(locale) - ); - }, [initialSupportedLanguages, state.supportedLanguages]); - - const [ - isClearLocalizationConfirmationDialogVisible, - setIsClearLocalizationConfirmationDialogVisible, - ] = useState(false); - - const dismissClearLocalizationConfirmationDialog = useCallback(() => { - setIsClearLocalizationConfirmationDialogVisible(false); - }, []); - const form: FormModel = useMemo( () => ({ isLoading: @@ -944,25 +942,6 @@ const UISettingsScreen: React.VFC = function UISettingsScreen() { [config, featureConfig, resources, state] ); - const confirmFormSave = useCallback(async () => { - if (allExistingLanguageAreRemoved) { - setIsClearLocalizationConfirmationDialogVisible(true); - return; - } - - await form.save(); - }, [allExistingLanguageAreRemoved, form]); - - const doFormSave = useCallback(async () => { - dismissClearLocalizationConfirmationDialog(); - await form.save(); - }, [dismissClearLocalizationConfirmationDialog, form]); - - const formWithConfirmation = { - ...form, - save: confirmFormSave, - }; - const imageSizeTooLargeErrorRule = useCallback( (apiError: APIError): ParsedAPIError[] => { if (apiError.reason === "RequestEntityTooLarge") { @@ -1022,20 +1001,11 @@ const UISettingsScreen: React.VFC = function UISettingsScreen() { } return ( - + - ); }; diff --git a/portal/src/hook/useDelayedSave.ts b/portal/src/hook/useDelayedSave.ts new file mode 100644 index 0000000000..b83fe5892e --- /dev/null +++ b/portal/src/hook/useDelayedSave.ts @@ -0,0 +1,26 @@ +import { useState, useEffect, useCallback } from "react"; + +interface FormModel { + state: unknown; + save: () => Promise; +} + +export function useDelayedSave(form: FormModel): () => void { + const [delaySave, setDelaySave] = useState(false); + + useEffect(() => { + if (!delaySave) { + return; + } + + setDelaySave(false); + // eslint-disable-next-line no-void + void form.save(); + }, [form, delaySave]); + + const triggerSave = useCallback(() => { + setDelaySave(true); + }, []); + + return triggerSave; +} From 0a1c2f8082485f57af5842246fe97de41287500a Mon Sep 17 00:00:00 2001 From: Newman Chow Date: Wed, 27 Mar 2024 12:06:18 +0800 Subject: [PATCH 2/2] Use styles prop for manage language widget dropdown text --- .../graphql/portal/ManageLanguageWidget.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/portal/src/graphql/portal/ManageLanguageWidget.tsx b/portal/src/graphql/portal/ManageLanguageWidget.tsx index 96113f9386..566897cf3e 100644 --- a/portal/src/graphql/portal/ManageLanguageWidget.tsx +++ b/portal/src/graphql/portal/ManageLanguageWidget.tsx @@ -4,7 +4,6 @@ import React, { useMemo, useState, useEffect, - CSSProperties, } from "react"; import cn from "classnames"; import { @@ -21,7 +20,6 @@ import { List, Text, IRenderFunction, - useTheme, } from "@fluentui/react"; import { Context, FormattedMessage } from "@oursky/react-messageformat"; @@ -358,7 +356,6 @@ const ManageLanguageWidget: React.VFC = } = props; const { renderToString } = useContext(Context); - const theme = useTheme(); const [isDialogPresented, setIsDialogPresented] = useState(false); @@ -435,15 +432,17 @@ const ManageLanguageWidget: React.VFC = const onRenderOption: IRenderFunction = useCallback( (option?: IDropdownOption) => { - const style: CSSProperties | undefined = option?.disabled - ? { - fontStyle: "italic", - color: theme.semanticColors.disabledText, - } - : undefined; - return ( - + ({ + root: option?.disabled + ? { + fontStyle: "italic", + color: theme.semanticColors.disabledText, + } + : undefined, + })} + > = ); }, - [theme.semanticColors.disabledText] + [] ); const onRenderTitle: IRenderFunction = useCallback(