From 4cf1ad709b8a565cddfbe9470156f85d351e667b Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 02:10:07 -0700 Subject: [PATCH 01/31] chore: remove unused dropdown group wrapper --- src/app/AppLayout/AppLayout.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index 8dab6d7fe..5bb3907b3 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -283,13 +283,9 @@ export const AppLayout: React.FC = ({ children }) => { const userInfoItems = React.useMemo( () => [ - - Language preference - + Language preference , - - Log out - , + Log out, ], [handleLogout, handleLanguagePref], ); From ec369256903fa5ba24cfdf23227b4e116050dac6 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 02:31:50 -0700 Subject: [PATCH 02/31] fix: fix broken feature level settings --- src/app/Settings/Config/FeatureLevels.tsx | 16 +++++++--------- src/app/Settings/utils.ts | 2 ++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/Settings/Config/FeatureLevels.tsx b/src/app/Settings/Config/FeatureLevels.tsx index 66c38c626..e6965482a 100644 --- a/src/app/Settings/Config/FeatureLevels.tsx +++ b/src/app/Settings/Config/FeatureLevels.tsx @@ -22,6 +22,7 @@ import { MenuToggle, MenuToggleElement, Select, SelectList, SelectOption } from import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { SettingTab, UserSetting } from '../types'; +import { isDevNodeEnv } from '../utils'; const Component = () => { const { t } = useTranslation(); @@ -47,7 +48,7 @@ const Component = () => { const toggle = React.useCallback( (toggleRef: React.Ref) => ( - + {t(FeatureLevel[featureLevel])} ), @@ -65,19 +66,16 @@ const Component = () => { appendTo: portalRoot, }} toggle={toggle} + onOpenChange={setOpen} + onOpenChangeKeys={['Escape']} > {Object.values(FeatureLevel) .filter((v) => typeof v === 'string') - .map((v): { key: string; value: number } => ({ key: String(v), value: FeatureLevel[v] })) - .filter((v) => { - if ((process.env.NODE_ENV ?? '').toLowerCase() === 'development') { - return true; - } - return v.value !== FeatureLevel.DEVELOPMENT; - }) + .map((v): { key: string; value: number } => ({ key: v.toString(), value: FeatureLevel[v] })) + .filter((v) => isDevNodeEnv() || v.value !== FeatureLevel.DEVELOPMENT) .map((level) => ( - + {t(level.key)} ))} diff --git a/src/app/Settings/utils.ts b/src/app/Settings/utils.ts index bc85fcd03..33dc46cda 100644 --- a/src/app/Settings/utils.ts +++ b/src/app/Settings/utils.ts @@ -45,3 +45,5 @@ export const getGroupFeatureLevel = (settings: _TransformedUserSetting[]): Featu } return settings.slice().sort((a, b) => b.featureLevel - a.featureLevel)[0].featureLevel; }; + +export const isDevNodeEnv = () => (process.env.NODE_ENV ?? '').toLowerCase() === 'development'; From d6a097aff19293bdf3c81b72dec98e6bdfe0e558 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 02:51:53 -0700 Subject: [PATCH 03/31] fix: fix react warning about invalid child node --- src/app/AppLayout/AppLayout.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index 5bb3907b3..fca29cb47 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -64,14 +64,12 @@ import { MenuToggleElement, MenuToggle, DropdownList, - DropdownGroup, DropdownItem, Dropdown, } from '@patternfly/react-core'; import { BarsIcon, BellIcon, - CaretDownIcon, CogIcon, ExternalLinkAltIcon, PlusCircleIcon, @@ -283,16 +281,16 @@ export const AppLayout: React.FC = ({ children }) => { const userInfoItems = React.useMemo( () => [ - Language preference + Language preference , - Log out, + Log out, ], [handleLogout, handleLanguagePref], ); - const UserInfoToggle = React.useCallback( + const userInfoToggle = React.useCallback( (toggleRef: React.Ref) => ( - + {username || ( @@ -427,7 +425,7 @@ export const AppLayout: React.FC = ({ children }) => { )} isOpen={showHelpDropdown} - onOpenChange={(v) => setShowHelpDropdown(v)} + onOpenChange={setShowHelpDropdown} onOpenChangeKeys={['Escape']} popperProps={{ position: 'right', @@ -440,9 +438,9 @@ export const AppLayout: React.FC = ({ children }) => { setShowUserInfoDropdown(false)} - toggle={UserInfoToggle} + toggle={userInfoToggle} isOpen={showUserInfoDropdown} - onOpenChange={(v) => setShowUserInfoDropdown(v)} + onOpenChange={setShowUserInfoDropdown} onOpenChangeKeys={['Escape']} popperProps={{ position: 'right', @@ -465,7 +463,7 @@ export const AppLayout: React.FC = ({ children }) => { setShowUserInfoDropdown, showUserInfoDropdown, showHelpDropdown, - UserInfoToggle, + userInfoToggle, userInfoItems, helpItems, ], From 7bf86e9fa24b495ecc4412e4f33df374467381c0 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 03:28:35 -0700 Subject: [PATCH 04/31] chore: use cyan color --- src/app/AppLayout/AppLayout.tsx | 10 +++++++--- src/app/Settings/Settings.tsx | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index fca29cb47..bfe4a93df 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -281,9 +281,13 @@ export const AppLayout: React.FC = ({ children }) => { const userInfoItems = React.useMemo( () => [ - Language preference + + Language preference + , - Log out, + + Log out + , ], [handleLogout, handleLanguagePref], ); @@ -354,7 +358,7 @@ export const AppLayout: React.FC = ({ children }) => { diff --git a/src/app/Settings/Settings.tsx b/src/app/Settings/Settings.tsx index 019c073a5..1c402c81c 100644 --- a/src/app/Settings/Settings.tsx +++ b/src/app/Settings/Settings.tsx @@ -166,7 +166,7 @@ export const Settings: React.FC = (_) => { marginLeft: '1ch', textTransform: 'capitalize', }} - color={s.featureLevel === FeatureLevel.BETA ? 'green' : 'red'} + color={s.featureLevel === FeatureLevel.BETA ? 'cyan' : 'red'} > {FeatureLevel[s.featureLevel].toLowerCase()} From 983fa9541677a10ea0402867136d6bc18eeb8ab3 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 03:36:16 -0700 Subject: [PATCH 05/31] fix: fix Language and AutoRefresh settings --- src/app/Settings/Config/AutoRefresh.tsx | 2 +- src/app/Settings/Config/Language.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/Settings/Config/AutoRefresh.tsx b/src/app/Settings/Config/AutoRefresh.tsx index 37b00bbd7..07494f279 100644 --- a/src/app/Settings/Config/AutoRefresh.tsx +++ b/src/app/Settings/Config/AutoRefresh.tsx @@ -41,7 +41,7 @@ const Component = () => { }, [setState, context.settings]); const handleAutoRefreshEnabledChange = React.useCallback( - (autoRefreshEnabled) => { + (_, autoRefreshEnabled) => { setState((state) => ({ ...state, autoRefreshEnabled })); context.settings.setAutoRefreshEnabled(autoRefreshEnabled); }, diff --git a/src/app/Settings/Config/Language.tsx b/src/app/Settings/Config/Language.tsx index 70bccf42c..c85a8112d 100644 --- a/src/app/Settings/Config/Language.tsx +++ b/src/app/Settings/Config/Language.tsx @@ -44,8 +44,8 @@ const Component = () => { const toggle = React.useCallback( (toggleRef: React.Ref) => ( - - {i18n.language} + + {localeReadable(i18n.language)} ), [handleLanguageToggle, open, i18n.language], From 50eba4394e4aff41e7832076f335beb5f8478169 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 03:40:41 -0700 Subject: [PATCH 06/31] fix: fix broken notification settings --- src/app/Settings/Config/DatetimeControl.tsx | 2 ++ src/app/Settings/Config/DeletionDialogControl.tsx | 8 ++++---- src/app/Settings/Config/NotificationControl.tsx | 13 +++++++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/app/Settings/Config/DatetimeControl.tsx b/src/app/Settings/Config/DatetimeControl.tsx index 29c8fc157..61fd97625 100644 --- a/src/app/Settings/Config/DatetimeControl.tsx +++ b/src/app/Settings/Config/DatetimeControl.tsx @@ -147,6 +147,8 @@ const Component = () => { }} selected={datetimeFormat.dateLocale} onSelect={handleDateLocaleSelect} + menuHeight="20vh" + isScrollable > {dateLocaleOptions} diff --git a/src/app/Settings/Config/DeletionDialogControl.tsx b/src/app/Settings/Config/DeletionDialogControl.tsx index cdb58d62c..b8027f142 100644 --- a/src/app/Settings/Config/DeletionDialogControl.tsx +++ b/src/app/Settings/Config/DeletionDialogControl.tsx @@ -37,8 +37,8 @@ const Component = () => { const [expanded, setExpanded] = React.useState(false); const handleCheckboxChange = React.useCallback( - (checked, element) => { - state.set(DeleteOrDisableWarningType[element.target.id], checked); + (id, checked) => { + state.set(DeleteOrDisableWarningType[id], checked); context.settings.setDeletionDialogsEnabled(state); setState(new Map(state)); }, @@ -46,7 +46,7 @@ const Component = () => { ); const handleCheckAll = React.useCallback( - (checked) => { + (_, checked) => { const newState = new Map(); Array.from(state.entries()).forEach((v) => newState.set(v[0], checked)); context.settings.setDeletionDialogsEnabled(newState); @@ -68,7 +68,7 @@ const Component = () => { id={key} label={getFromWarningMap(key)?.label || key.toString()} isChecked={value} - onChange={handleCheckboxChange} + onChange={(_, checked) => handleCheckboxChange(key, checked)} /> )); diff --git a/src/app/Settings/Config/NotificationControl.tsx b/src/app/Settings/Config/NotificationControl.tsx index b5398553e..c145a2e7c 100644 --- a/src/app/Settings/Config/NotificationControl.tsx +++ b/src/app/Settings/Config/NotificationControl.tsx @@ -48,8 +48,8 @@ const Component = () => { }, [addSubscription, context.settings, setVisibleNotificationsCount]); const handleCheckboxChange = React.useCallback( - (checked, element) => { - state.set(NotificationCategory[element.target.id], checked); + (id, checked) => { + state.set(NotificationCategory[id], checked); context.settings.setNotificationsEnabled(state); setState(new Map(state)); }, @@ -57,7 +57,7 @@ const Component = () => { ); const handleCheckAll = React.useCallback( - (checked) => { + (_, checked) => { const newState = new Map(); Array.from(state.entries()).forEach((v) => newState.set(v[0], checked)); context.settings.setNotificationsEnabled(newState); @@ -104,7 +104,12 @@ const Component = () => { const switches = React.useMemo(() => { return Array.from(state.entries(), ([key, value]) => ( - + handleCheckboxChange(key, checked)} + /> )); }, [handleCheckboxChange, state, labels]); From 229a8992229d2eeedef7f06b9321b32261589a36 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 03:57:54 -0700 Subject: [PATCH 07/31] fix: fix broken theme select (functionality is still broken --- src/app/Settings/Config/Theme.tsx | 36 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/app/Settings/Config/Theme.tsx b/src/app/Settings/Config/Theme.tsx index e540597d2..2793ba1b2 100644 --- a/src/app/Settings/Config/Theme.tsx +++ b/src/app/Settings/Config/Theme.tsx @@ -37,13 +37,29 @@ const Component = () => { [context.settings, setOpen], ); + const getThemeDisplay = React.useCallback( + (theme: ThemeSetting) => { + switch (theme) { + case ThemeSetting.AUTO: + return t('SETTINGS.THEME.AUTO'); + case ThemeSetting.DARK: + return t('SETTINGS.THEME.DARK'); + case ThemeSetting.LIGHT: + return t('SETTINGS.THEME.LIGHT'); + default: + return `${theme}`; + } + }, + [t], + ); + const toggle = React.useCallback( (toggleRef: React.Ref) => ( - - {themeSetting} + + {getThemeDisplay(themeSetting)} ), - [handleThemeToggle, open, themeSetting], + [handleThemeToggle, open, getThemeDisplay, themeSetting], ); return ( @@ -59,15 +75,11 @@ const Component = () => { toggle={toggle} > - - {t('SETTINGS.THEME.AUTO')} - - - {t('SETTINGS.THEME.LIGHT')} - - - {t('SETTINGS.THEME.DARK')} - + {Object.values(ThemeSetting).map((theme) => ( + + {getThemeDisplay(theme)} + + ))} ); From d4d6d4c0775863dfda2329d3f81ed2845f52a0fe Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 04:00:12 -0700 Subject: [PATCH 08/31] chore: menu should close on click outside and escape btn clicked --- src/app/Settings/Config/DatetimeControl.tsx | 2 ++ src/app/Settings/Config/Language.tsx | 2 ++ src/app/Settings/Config/Theme.tsx | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/app/Settings/Config/DatetimeControl.tsx b/src/app/Settings/Config/DatetimeControl.tsx index 61fd97625..ffe3bd210 100644 --- a/src/app/Settings/Config/DatetimeControl.tsx +++ b/src/app/Settings/Config/DatetimeControl.tsx @@ -149,6 +149,8 @@ const Component = () => { onSelect={handleDateLocaleSelect} menuHeight="20vh" isScrollable + onOpenChange={setDateLocaleOpen} + onOpenChangeKeys={['Escape']} > {dateLocaleOptions} diff --git a/src/app/Settings/Config/Language.tsx b/src/app/Settings/Config/Language.tsx index c85a8112d..6b0083d0c 100644 --- a/src/app/Settings/Config/Language.tsx +++ b/src/app/Settings/Config/Language.tsx @@ -62,6 +62,8 @@ const Component = () => { enableFlip: true, appendTo: portalRoot, }} + onOpenChange={setOpen} + onOpenChangeKeys={['Escape']} > {Object.keys(i18nResources).map((l) => ( diff --git a/src/app/Settings/Config/Theme.tsx b/src/app/Settings/Config/Theme.tsx index 2793ba1b2..132db675e 100644 --- a/src/app/Settings/Config/Theme.tsx +++ b/src/app/Settings/Config/Theme.tsx @@ -73,6 +73,8 @@ const Component = () => { appendTo: portalRoot, }} toggle={toggle} + onOpenChange={setOpen} + onOpenChangeKeys={['Escape']} > {Object.values(ThemeSetting).map((theme) => ( From c3959f89162eebd55a46b079805497aca0ab51ee Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Mon, 19 Aug 2024 04:09:03 -0700 Subject: [PATCH 09/31] chore: featureLevel should follow locale config --- locales/en/common.json | 6 +++--- src/app/AppLayout/AppLayout.tsx | 25 ++++++++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/locales/en/common.json b/locales/en/common.json index e2a354a54..541c19fa3 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -7,7 +7,7 @@ "MAXIMUM_SIZE_UNITS_INPUT": "Maximum size units input" }, "AUTOMATED_RULES": "Automated Rules", - "BETA": "BETA", + "BETA": "Beta", "CANCEL": "Cancel", "CARD_TYPE": "Card type", "CLEAN": "Clean", @@ -22,7 +22,7 @@ "DATE": "Date", "DELETE": "Delete", "DESCRIPTION": "Description", - "DEVELOPMENT": "DEVELOPMENT", + "DEVELOPMENT": "Development", "DISABLE": "Disable", "DONOT_ASK_AGAIN": "Don't ask me again", "DOWNLOAD": "Download", @@ -51,7 +51,7 @@ "NO_DESCRIPTION": "No description", "OK": "OK", "PRESERVED_ARCHIVES": "Preserved Archives", - "PRODUCTION": "PRODUCTION", + "PRODUCTION": "Production", "REFRESH": "Refresh", "REMOVE": "Remove", "RENAME": "Rename", diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index bfe4a93df..10a6e2e8a 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -353,17 +353,20 @@ export const AppLayout: React.FC = ({ children }) => { ]; }, [t, handleOpenDocumentation, handleOpenGuidedTour, handleOpenDiscussion, handleOpenAboutModal]); - const levelBadge = React.useCallback((level: FeatureLevel) => { - return ( - - ); - }, []); + const levelBadge = React.useCallback( + (level: FeatureLevel) => { + return ( + + ); + }, + [t], + ); const headerToolbar = React.useMemo( () => ( From 4db244adc48d88432565880f6f6b4238b71523a7 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 14:31:37 -0700 Subject: [PATCH 10/31] feat: add descriptions for feature level selections --- locales/en/public.json | 3 +++ src/app/Settings/Config/FeatureLevels.tsx | 7 ++++++- src/app/Settings/i18n.ts | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/locales/en/public.json b/locales/en/public.json index 7b541af51..c329be618 100644 --- a/locales/en/public.json +++ b/locales/en/public.json @@ -381,7 +381,10 @@ "TITLE": "Show deletion dialogs" }, "FEATURE_LEVEL": { + "BETA_DESCRIPTION": "Experimental features", "DESCRIPTION": "Control which graphical features appear in the application.", + "DEVELOPMENT_DESCRIPTION": "Under development features", + "PRODUCTION_DESCRIPTION": "Stable production-ready features", "TITLE": "Feature level" }, "LANGUAGE": { diff --git a/src/app/Settings/Config/FeatureLevels.tsx b/src/app/Settings/Config/FeatureLevels.tsx index e6965482a..0a8e30a96 100644 --- a/src/app/Settings/Config/FeatureLevels.tsx +++ b/src/app/Settings/Config/FeatureLevels.tsx @@ -75,7 +75,12 @@ const Component = () => { .map((v): { key: string; value: number } => ({ key: v.toString(), value: FeatureLevel[v] })) .filter((v) => isDevNodeEnv() || v.value !== FeatureLevel.DEVELOPMENT) .map((level) => ( - + {t(level.key)} ))} diff --git a/src/app/Settings/i18n.ts b/src/app/Settings/i18n.ts index 235cd4758..af5171b04 100644 --- a/src/app/Settings/i18n.ts +++ b/src/app/Settings/i18n.ts @@ -32,6 +32,9 @@ * t('SETTINGS.DELETION_DIALOG_CONTROL.DESCRIPTION') * t('SETTINGS.FEATURE_LEVEL.TITLE') * t('SETTINGS.FEATURE_LEVEL.DESCRIPTION') + * t('SETTINGS.FEATURE_LEVEL.PRODUCTION_DESCRIPTION') + * t('SETTINGS.FEATURE_LEVEL.BETA_DESCRIPTION') + * t('SETTINGS.FEATURE_LEVEL.DEVELOPMENT_DESCRIPTION') * t('SETTINGS.LANGUAGE.TITLE') * t('SETTINGS.LANGUAGE.DESCRIPTION') * t('SETTINGS.NOTIFICATION_CONTROL.TITLE') From a9789704b103ca06a048644cabefc31d99d540ce Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 16:18:15 -0700 Subject: [PATCH 11/31] fix: fix broken locale selections --- src/app/Settings/Config/DatetimeControl.tsx | 66 ++++++++------------- 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/src/app/Settings/Config/DatetimeControl.tsx b/src/app/Settings/Config/DatetimeControl.tsx index ffe3bd210..c4cc5a9ab 100644 --- a/src/app/Settings/Config/DatetimeControl.tsx +++ b/src/app/Settings/Config/DatetimeControl.tsx @@ -19,22 +19,20 @@ import useDayjs from '@app/utils/hooks/useDayjs'; import { portalRoot } from '@app/utils/utils'; import { locales, Timezone } from '@i18n/datetime'; import { - Button, FormGroup, HelperText, HelperTextItem, + MenuSearch, + MenuSearchInput, MenuToggle, MenuToggleElement, + SearchInput, Select, SelectList, SelectOption, Stack, StackItem, - TextInputGroup, - TextInputGroupMain, - TextInputGroupUtilities, } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { SettingTab, UserSetting } from '../types'; @@ -44,7 +42,7 @@ const Component = () => { const context = React.useContext(ServiceContext); const [dateLocaleOpen, setDateLocaleOpen] = React.useState(false); const [_, datetimeFormat] = useDayjs(); - const [filterValue, setFilterValue] = React.useState(''); + const [searchTerm, setSearchTerm] = React.useState(''); const handleDateLocaleSelect = React.useCallback( (_, locale) => { @@ -77,57 +75,36 @@ const Component = () => { () => locales .filter((locale) => { - if (!filterValue) { + if (!searchTerm) { return true; } - const matchExp = new RegExp(filterValue, 'i'); + const matchExp = new RegExp(searchTerm, 'i'); return matchExp.test(locale.name) || matchExp.test(locale.key); }) .map((locale) => ( - + {locale.name} )), - [filterValue], + [searchTerm, datetimeFormat.dateLocale], ); const onToggle = React.useCallback(() => setDateLocaleOpen((open) => !open), [setDateLocaleOpen]); - const onInputChange = React.useCallback((_, value: string) => setFilterValue(value), [setFilterValue]); + const onInputChange = React.useCallback((_, value: string) => setSearchTerm(value), [setSearchTerm]); const toggle = React.useCallback( (toggleRef: React.Ref) => ( - - - - - {filterValue ? ( - - ) : null} - - + + {datetimeFormat.dateLocale.name} ), - [onToggle, dateLocaleOpen, filterValue, onInputChange, setFilterValue, t], + [onToggle, dateLocaleOpen, datetimeFormat.dateLocale], ); return ( @@ -152,7 +129,14 @@ const Component = () => { onOpenChange={setDateLocaleOpen} onOpenChangeKeys={['Escape']} > - {dateLocaleOptions} + + + + + + + {dateLocaleOptions} + From 119242628a762c840f07ffc3bc0c36cc3267bbf7 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 16:33:13 -0700 Subject: [PATCH 12/31] chore: use full word for units --- locales/en/common.json | 1 + src/app/Settings/Config/WebSocketDebounce.tsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/en/common.json b/locales/en/common.json index 541c19fa3..ebbad8ccf 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -43,6 +43,7 @@ "MAXIMUM_SIZE_HELPER_TEXT": "The maximum size of Recording data saved to disk.", "MERIDIEM_AM": "AM", "MERIDIEM_PM": "PM", + "MILLISECOND": "Millisecond", "MINUTE": "Minute", "MINUTE_one": "Minute", "MINUTE_other": "Minutes", diff --git a/src/app/Settings/Config/WebSocketDebounce.tsx b/src/app/Settings/Config/WebSocketDebounce.tsx index 44227525e..e9274e66f 100644 --- a/src/app/Settings/Config/WebSocketDebounce.tsx +++ b/src/app/Settings/Config/WebSocketDebounce.tsx @@ -17,6 +17,7 @@ import { ServiceContext } from '@app/Shared/Services/Services'; import { NumberInput } from '@patternfly/react-core'; import * as React from 'react'; +import { useTranslation } from 'react-i18next'; import { SettingTab, UserSetting } from '../types'; const defaultPreferences = { @@ -27,6 +28,7 @@ const debounceMin = 1; const debounceMax = 1000; const Component = () => { + const { t } = useTranslation(); const context = React.useContext(ServiceContext); const [state, setState] = React.useState(defaultPreferences); @@ -87,7 +89,7 @@ const Component = () => { onChange={handleWebSocketDebounceChange} onMinus={handleWebSocketDebounceMinus} onPlus={handleWebSocketDebouncePlus} - unit="ms" + unit={t('MILLISECOND', { ns: 'common' })} /> ); From 5ca0f991f3bd84f7e56eb158f046ceda4979fbfe Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 17:26:20 -0700 Subject: [PATCH 13/31] feat: new design for AA recording form --- locales/en/common.json | 1 + locales/en/public.json | 7 +- .../AutomatedAnalysisConfigForm.tsx | 169 +++++++++--------- src/app/TargetView/TargetSelect.tsx | 156 ++++++++-------- src/app/app.css | 4 + 5 files changed, 169 insertions(+), 168 deletions(-) diff --git a/locales/en/common.json b/locales/en/common.json index ebbad8ccf..b1f4abf31 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -58,6 +58,7 @@ "RENAME": "Rename", "RESET": "Reset", "RETRY": "Retry", + "SAVE": "Save", "SCORE": "Score", "SECOND": "Second", "SECOND_one": "Second", diff --git a/locales/en/public.json b/locales/en/public.json index c329be618..df6603d6a 100644 --- a/locales/en/public.json +++ b/locales/en/public.json @@ -75,9 +75,8 @@ "CURRENT_CONFIG": "Current configuration", "FORM_TITLE": "Profiling Recording configuration", "FORMATTED_TEMPLATE": "Name: {{template.name}}, Type: {{template.type}}", - "MAXIMUM_AGE": "Maximum age ({{unit}})", - "MAXIMUM_SIZE": "Maximum size ({{unit}})", - "SAVE_CHANGES": "Save changes", + "MAXIMUM_AGE": "Maximum age", + "MAXIMUM_SIZE": "Maximum size", "TEMPLATE_HELPER_TEXT": "The Event Template to be applied to automated analysis Recordings.", "TEMPLATE_INVALID_WARNING": "WARNING: Setting a target template as a default template type configuration may not apply to all target JVMs if the JVMs do not support them." }, @@ -339,7 +338,7 @@ }, "AUTOMATED_ANALYSIS_CONFIG": { "DESCRIPTION": "Set the Recording configuration for automated analysis Recordings. You may want smaller or larger values for max-age and max-size depending on how recent you want events to be recorded from the analysis.", - "TITLE": "Automated analysis Recording configuration" + "TITLE": "Recording configuration for Automated analysis" }, "CATEGORIES": { "ADVANCED": "Advanced", diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx index f74402f2d..dafdcfdcc 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx @@ -23,7 +23,10 @@ import type { AutomatedAnalysisRecordingConfig } from '@app/Shared/Services/serv import { ServiceContext } from '@app/Shared/Services/Services'; import { TargetSelect } from '@app/TargetView/TargetSelect'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; +import { formatBytes, formatDuration } from '@app/utils/utils'; import { + ActionList, + ActionListItem, Button, Card, CardBody, @@ -72,13 +75,13 @@ export const AutomatedAnalysisConfigForm: React.FC(1)); - const targetSubject = targetSubjectRef.current; + const _targetSubjectRef = React.useRef(new ReplaySubject(1)); + const _targetSubject = _targetSubjectRef.current; const [recordingConfig, setRecordingConfig] = React.useState( context.settings.automatedAnalysisRecordingConfig(), ); - const [formData, setFormConfig] = React.useState({ + const [formData, setFormData] = React.useState({ maxAge: context.settings.automatedAnalysisRecordingConfig().maxAge, maxAgeUnit: 1, maxSize: context.settings.automatedAnalysisRecordingConfig().maxSize, @@ -112,7 +115,7 @@ export const AutomatedAnalysisConfigForm: React.FC { setErrorMessage(''); setTemplates(templates); - setFormConfig((old) => { + setFormData((old) => { const oldTemplate = old.template; const matched = templates.find((t) => t.name === oldTemplate?.name && t.type === oldTemplate?.type); return { @@ -135,16 +138,12 @@ export const AutomatedAnalysisConfigForm: React.FC { - addSubscription( - targetSubject.subscribe((target) => { - refreshTemplates(target); - }), - ); - }, [targetSubject, addSubscription, refreshTemplates, setIsLoading, editing]); + addSubscription(_targetSubject.subscribe(refreshTemplates)); + }, [_targetSubject, addSubscription, refreshTemplates, setIsLoading, editing]); const setAAConfig = React.useCallback( (config: AutomatedAnalysisRecordingConfig) => { @@ -158,63 +157,53 @@ export const AutomatedAnalysisConfigForm: React.FC { - setFormConfig((old) => { - return { - ...old, - maxAge: Number(evt), - }; - }); + (_, value: string) => { + setFormData((old) => ({ + ...old, + maxAge: Number(value), + })); }, - [setFormConfig], + [setFormData], ); const handleMaxAgeUnitChange = React.useCallback( - (evt) => { - setFormConfig((old) => { - return { - ...old, - maxAgeUnits: Number(evt), - }; - }); + (_, unit: string) => { + setFormData((old) => ({ + ...old, + maxAgeUnit: Number(unit), + })); }, - [setFormConfig], + [setFormData], ); const handleMaxSizeChange = React.useCallback( - (evt) => { - setFormConfig((old) => { - return { - ...old, - maxSize: Number(evt), - }; - }); + (_, value: string) => { + setFormData((old) => ({ + ...old, + maxSize: Number(value), + })); }, - [setFormConfig], + [setFormData], ); const handleMaxSizeUnitChange = React.useCallback( - (evt) => { - setFormConfig((old) => { - return { - ...old, - maxSizeUnits: Number(evt), - }; - }); + (_, unit: string) => { + setFormData((old) => ({ + ...old, + maxSizeUnit: Number(unit), + })); }, - [setFormConfig], + [setFormData], ); const handleTemplateChange = React.useCallback( (template) => { - setFormConfig((old) => { - return { - ...old, - template, - }; - }); + setFormData((old) => ({ + ...old, + template, + })); }, - [setFormConfig], + [setFormData], ); const handleSubmit = React.useCallback(() => { @@ -240,8 +229,8 @@ export const AutomatedAnalysisConfigForm: React.FC { - return editing && targetSubject.next(target)} />; - }, [editing, targetSubject]); + return editing && _targetSubject.next(target)} />; + }, [editing, _targetSubject]); const configData = React.useMemo(() => { if (editing) { @@ -327,6 +316,7 @@ export const AutomatedAnalysisConfigForm: React.FC - {t('AutomatedAnalysisConfigForm.MAXIMUM_SIZE', { unit: 'B' })} - {recordingConfig.maxSize} + {t('AutomatedAnalysisConfigForm.MAXIMUM_SIZE')} + {formatBytes(recordingConfig.maxSize)} - {t('AutomatedAnalysisConfigForm.MAXIMUM_AGE', { unit: 's' })} - {recordingConfig.maxAge} + {t('AutomatedAnalysisConfigForm.MAXIMUM_AGE')} + {formatDuration(recordingConfig.maxAge)} ); @@ -385,14 +375,14 @@ export const AutomatedAnalysisConfigForm: React.FC { setEditing((edit) => !edit); - setFormConfig({ + setFormData({ template: recordingConfig.template, maxSize: recordingConfig.maxSize, maxAge: recordingConfig.maxAge, maxSizeUnit: 1, maxAgeUnit: 1, }); - }, [setEditing, setFormConfig, recordingConfig]); + }, [setEditing, setFormData, recordingConfig]); const authModal = React.useMemo(() => { return ( @@ -402,15 +392,15 @@ export const AutomatedAnalysisConfigForm: React.FC { setIsAuthModalOpen(false); addSubscription( - targetSubject.pipe(take(1)).subscribe((target) => { + _targetSubject.pipe(take(1)).subscribe((target) => { refreshTemplates(target); }), ); }} - targetObs={targetSubject} + targetObs={_targetSubject} /> ); - }, [addSubscription, isAuthModalOpen, setIsAuthModalOpen, refreshTemplates, targetSubject]); + }, [addSubscription, isAuthModalOpen, setIsAuthModalOpen, refreshTemplates, _targetSubject]); const formContent = React.useMemo( () => ( @@ -419,39 +409,46 @@ export const AutomatedAnalysisConfigForm: React.FC - {editing && ( - - )} - - + ), - hasNoOffset: false, - className: undefined, }} > - - - {t('AutomatedAnalysisConfigForm.CURRENT_CONFIG')} - - + {!editing && ( + + + {t('AutomatedAnalysisConfigForm.CURRENT_CONFIG')} + + + )} {targetSelect} {configData} + {editing && ( + + + + + + + + + )} diff --git a/src/app/TargetView/TargetSelect.tsx b/src/app/TargetView/TargetSelect.tsx index e705f1eff..2af54df04 100644 --- a/src/app/TargetView/TargetSelect.tsx +++ b/src/app/TargetView/TargetSelect.tsx @@ -15,7 +15,7 @@ */ import { LoadingView } from '@app/Shared/Components/LoadingView'; import { Target } from '@app/Shared/Services/api.types'; -import { includesTarget } from '@app/Shared/Services/api.utils'; +import { includesTarget, isEqualTarget } from '@app/Shared/Services/api.utils'; import { ServiceContext } from '@app/Shared/Services/Services'; import { NoTargetSelected } from '@app/TargetView/NoTargetSelected'; import { SerializedTarget } from '@app/TargetView/SerializedTarget'; @@ -29,16 +29,17 @@ import { CardHeader, CardTitle, Dropdown, - SelectGroup, - SelectOption, - SelectList, MenuToggle, SearchInput, MenuSearch, MenuSearchInput, DropdownGroup, + MenuToggleElement, + Divider, + DropdownList, + DropdownItem, } from '@patternfly/react-core'; -import { ContainerNodeIcon, SearchIcon } from '@patternfly/react-icons'; +import { ContainerNodeIcon } from '@patternfly/react-icons'; import * as React from 'react'; export interface TargetSelectProps { @@ -54,12 +55,13 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. const [isExpanded, setExpanded] = React.useState(false); const [selected, setSelected] = React.useState(); const [targets, setTargets] = React.useState([]); - const [isDropdownOpen, setDropdownOpen] = React.useState(false); + const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); const [isLoading, setLoading] = React.useState(false); + const [searchTerm, setSearchTerm] = React.useState(''); - const onExpand = React.useCallback(() => { - setExpanded((v) => !v); - }, [setExpanded]); + const handleToggle = React.useCallback(() => setIsDropdownOpen((v) => !v), [setIsDropdownOpen]); + + const handleExpand = React.useCallback(() => setExpanded((v) => !v), [setExpanded]); const _refreshTargetList = React.useCallback(() => { setLoading(true); @@ -72,13 +74,14 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. }, [addSubscription, context.targets, setLoading]); const handleSelect = React.useCallback( - (_, selection, isPlaceholder) => { - setDropdownOpen(false); - const toSelect: Target = isPlaceholder ? undefined : selection; - onSelect && onSelect(toSelect); - setSelected(toSelect); + (_, target) => { + setIsDropdownOpen(false); + if (!isEqualTarget(target, selected)) { + onSelect && onSelect(target); + setSelected(target); + } }, - [setDropdownOpen, onSelect, setSelected], + [setIsDropdownOpen, onSelect, setSelected, selected], ); React.useEffect(() => { @@ -98,70 +101,56 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. React.useEffect(() => { if (!!selected && !includesTarget(targets, selected)) { - handleSelect(undefined, undefined, true); + handleSelect(undefined, undefined); } if (targets.length && !firstLoadRef.current) { firstLoadRef.current = true; const cachedUrl = getFromLocalStorage('TARGET', undefined); const matched = targets.find((tn) => tn.connectUrl === cachedUrl); if (matched) { - handleSelect(undefined, matched, false); + handleSelect(undefined, matched); } } }, [handleSelect, targets, selected, firstLoadRef]); const selectOptions = React.useMemo(() => { - let options = [] as JSX.Element[]; + const matchExp = new RegExp(searchTerm, 'i'); + const filteredTargets = targets.filter((t) => + [t.alias, t.connectUrl, getAnnotation(t.annotations.cryostat, 'REALM') ?? ''].some((v) => matchExp.test(v)), + ); const groupNames = new Set(); - targets.forEach((t) => groupNames.add(getAnnotation(t.annotations.cryostat, 'REALM') || 'Others')); - - options = options.concat( - Array.from(groupNames) - .map((name) => ( - - - {targets - .filter((t) => (getAnnotation(t.annotations.cryostat, 'REALM') || 'Others') === name) - .map((t: Target) => ( - - {!t.alias || t.alias === t.connectUrl ? `${t.connectUrl}` : `${t.alias} (${t.connectUrl})`} - - ))} - - - )) - .sort((a, b) => `${a.props['label']}`.localeCompare(`${b.props['label']}`)), - ); - return options; - }, [targets]); + filteredTargets.forEach((t) => groupNames.add(getAnnotation(t.annotations.cryostat, 'REALM') || 'Others')); - const handleTargetFilter = React.useCallback( - (_, value: string) => { - if (!value) { - return selectOptions; - } - const matchExp = new RegExp(value, 'i'); - return selectOptions - .filter((grp) => grp.props.children) - .map((grp) => - React.cloneElement(grp, { - children: grp.props.children.filter( - (child) => matchExp.test(child.props.value.connectUrl) || matchExp.test(child.props.value.alias), - ), - }), - ) - .filter((grp) => grp.props.children.length > 0); - }, - [selectOptions], - ); + if (filteredTargets.length === 0) { + return [ + + No target found + , + ]; + } + + return Array.from(groupNames) + .map((name) => ( + + {filteredTargets + .filter((t) => getAnnotation(t.annotations.cryostat, 'REALM') === name) + .map((t: Target) => ( + + {t.alias} + + ))} + + )) + .sort((a, b) => `${a.props['label']}`.localeCompare(`${b.props['label']}`)); + }, [targets, searchTerm]); const cardHeaderProps = React.useMemo( () => simple ? {} : { - onExpand: onExpand, + onExpand: handleExpand, toggleButtonProps: { id: 'target-select-expand-button', 'aria-label': 'Details', @@ -169,7 +158,23 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. 'aria-expanded': isExpanded, }, }, - [simple, onExpand, isExpanded], + [simple, handleExpand, isExpanded], + ); + + const toggle = React.useCallback( + (toggleRef: React.Ref) => ( + } + isFullWidth + > + {selected?.alias || selected?.connectUrl} + + ), + [isDropdownOpen, selected, handleToggle], ); return ( @@ -183,33 +188,28 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. <> ( - handleSelect(undefined, undefined, true)} - isExpanded={isExpanded} - icon={} - variant="plain" - > - {selected?.alias || selected?.connectUrl} - - )} + onOpenChange={setIsDropdownOpen} + onOpenChangeKeys={['Escape']} + onSelect={handleSelect} + toggle={toggle} popperProps={{ - appendTo: portalRoot, enableFlip: true, - //maxHeight="20em" }} > - - + setSearchTerm(v)} + /> - {selectOptions} + + {selectOptions} diff --git a/src/app/app.css b/src/app/app.css index 4e5323d06..bb82fd514 100644 --- a/src/app/app.css +++ b/src/app/app.css @@ -319,6 +319,10 @@ html, body, #root { margin-top: 0.5em; } +.automated-analysis__form_select { + min-width: 10ch; +} + :where(.pf-v5-theme-dark) #automated-analysis-config-drawer-create-recording-button { --pf-v5-c-button--after--BorderColor: var(--pf-v5-c-button--m-control--after--BorderTopColor) var(--pf-v5-c-button--m-control--after--BorderRightColor) var(--pf-v5-c-button--m-control--after--BorderBottomColor) var(--pf-v5-global--BorderColor--300); } From 5d9fe270e897728a7a60dfcfd361fcfcc05b33f4 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 18:30:39 -0700 Subject: [PATCH 14/31] feat: divider for search bar --- src/app/DateTimePicker/TimezonePicker.tsx | 2 ++ src/app/Settings/Config/DatetimeControl.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/app/DateTimePicker/TimezonePicker.tsx b/src/app/DateTimePicker/TimezonePicker.tsx index 06c56f6d0..45b876153 100644 --- a/src/app/DateTimePicker/TimezonePicker.tsx +++ b/src/app/DateTimePicker/TimezonePicker.tsx @@ -18,6 +18,7 @@ import { useDayjs } from '@app/utils/hooks/useDayjs'; import { portalRoot } from '@app/utils/utils'; import { supportedTimezones, Timezone } from '@i18n/datetime'; import { + Divider, Icon, MenuSearch, MenuSearchInput, @@ -150,6 +151,7 @@ export const TimezonePicker: React.FC = ({ /> + {filteredTimezones.length > 0 ? ( filteredTimezones.map(mapToSelection) diff --git a/src/app/Settings/Config/DatetimeControl.tsx b/src/app/Settings/Config/DatetimeControl.tsx index c4cc5a9ab..15947e317 100644 --- a/src/app/Settings/Config/DatetimeControl.tsx +++ b/src/app/Settings/Config/DatetimeControl.tsx @@ -19,6 +19,7 @@ import useDayjs from '@app/utils/hooks/useDayjs'; import { portalRoot } from '@app/utils/utils'; import { locales, Timezone } from '@i18n/datetime'; import { + Divider, FormGroup, HelperText, HelperTextItem, @@ -135,6 +136,7 @@ const Component = () => { + {dateLocaleOptions} From 916421bcddd9751f9b7cd5ae84d62a4bcbb12970 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 18:33:55 -0700 Subject: [PATCH 15/31] fix: notification action smenu should show shadow --- src/app/AppLayout/NotificationCenter.tsx | 3 ++- src/app/TargetView/TargetSelect.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/AppLayout/NotificationCenter.tsx b/src/app/AppLayout/NotificationCenter.tsx index f0e5048a9..d6e862363 100644 --- a/src/app/AppLayout/NotificationCenter.tsx +++ b/src/app/AppLayout/NotificationCenter.tsx @@ -148,7 +148,6 @@ export const NotificationCenter: React.FC = ({ onClose ) => ( = ({ onClose popperProps={{ position: 'right', }} + onOpenChange={setHeaderDropdownOpen} + onOpenChangeKeys={['Escape']} > {drawerDropdownItems} diff --git a/src/app/TargetView/TargetSelect.tsx b/src/app/TargetView/TargetSelect.tsx index 2af54df04..07b24c145 100644 --- a/src/app/TargetView/TargetSelect.tsx +++ b/src/app/TargetView/TargetSelect.tsx @@ -21,7 +21,7 @@ import { NoTargetSelected } from '@app/TargetView/NoTargetSelected'; import { SerializedTarget } from '@app/TargetView/SerializedTarget'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { getFromLocalStorage } from '@app/utils/LocalStorage'; -import { getAnnotation, portalRoot } from '@app/utils/utils'; +import { getAnnotation } from '@app/utils/utils'; import { Card, CardBody, From d78e519b389d40950bf0b01b394f7711767dafb2 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 18:53:41 -0700 Subject: [PATCH 16/31] fix: dark mode should be functional --- src/app/AppLayout/AppLayout.tsx | 6 +++--- src/app/Settings/utils.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index 10a6e2e8a..1318ba698 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -24,7 +24,7 @@ import { useJoyride } from '@app/Joyride/JoyrideProvider'; import { GlobalQuickStartDrawer } from '@app/QuickStarts/QuickStartDrawer'; import { IAppRoute, navGroups, routes } from '@app/routes'; import { ThemeSetting, SettingTab } from '@app/Settings/types'; -import { selectTab, tabAsParam } from '@app/Settings/utils'; +import { DARK_THEME_CLASS, selectTab, tabAsParam } from '@app/Settings/utils'; import { DynamicFeatureFlag, FeatureFlag } from '@app/Shared/Components/FeatureFlag'; import { NotificationCategory, Notification } from '@app/Shared/Services/api.types'; import { NotificationsContext } from '@app/Shared/Services/Notifications.service'; @@ -118,9 +118,9 @@ export const AppLayout: React.FC = ({ children }) => { React.useEffect(() => { if (theme === ThemeSetting.DARK) { - document.documentElement.classList.add('pf-theme-dark'); + document.documentElement.classList.add(DARK_THEME_CLASS); } else { - document.documentElement.classList.remove('pf-theme-dark'); + document.documentElement.classList.remove(DARK_THEME_CLASS); } }, [theme]); diff --git a/src/app/Settings/utils.ts b/src/app/Settings/utils.ts index 33dc46cda..e6ab6580c 100644 --- a/src/app/Settings/utils.ts +++ b/src/app/Settings/utils.ts @@ -47,3 +47,5 @@ export const getGroupFeatureLevel = (settings: _TransformedUserSetting[]): Featu }; export const isDevNodeEnv = () => (process.env.NODE_ENV ?? '').toLowerCase() === 'development'; + +export const DARK_THEME_CLASS = 'pf-v5-theme-dark'; From 62308675ffada3f7acf625a92f40c3e9685eabcf Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 19:59:51 -0700 Subject: [PATCH 17/31] feat: added theme toggle on masthead --- src/app/AppLayout/AppLayout.tsx | 6 +++ src/app/AppLayout/ThemeToggle.tsx | 61 +++++++++++++++++++++++++++++++ src/app/app.css | 48 ++++++++++++++++-------- 3 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/app/AppLayout/ThemeToggle.tsx diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index 1318ba698..b222ffcd3 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -81,6 +81,7 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { Link, matchPath, NavLink, useLocation, useNavigate } from 'react-router-dom'; import { map } from 'rxjs/operators'; +import { ThemeToggle } from './ThemeToggle'; export interface AppLayoutProps { children?: React.ReactNode; @@ -387,6 +388,11 @@ export const AppLayout: React.FC = ({ children }) => { /> + + + + + = () => { + const context = React.useContext(ServiceContext); + const [_theme] = useTheme(); + + const handleThemeSelect = React.useCallback( + (_, setting: ThemeSetting) => { + context.settings.setThemeSetting(setting); + }, + [context.settings], + ); + + return ( + + + + + } + buttonId="light-theme" + isSelected={_theme === ThemeSetting.LIGHT} + onClick={(e) => handleThemeSelect(e, ThemeSetting.LIGHT)} + /> + + + + } + buttonId="dark-theme" + isSelected={_theme === ThemeSetting.DARK} + onClick={(e) => handleThemeSelect(e, ThemeSetting.DARK)} + /> + + ); +}; diff --git a/src/app/app.css b/src/app/app.css index bb82fd514..e6b2ccad3 100644 --- a/src/app/app.css +++ b/src/app/app.css @@ -13,7 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -html, body, #root { +html, +body, +#root { height: 100%; } @@ -26,8 +28,8 @@ html, body, #root { .cryostat-text { margin-top: -0.5em !important; - font-family: "Montserrat", sans-serif; - font-weight: 900; + font-family: "Montserrat", sans-serif; + font-weight: 900; text-align: center; font-size: 4.5em; color: var(--cryostat-indigo); @@ -68,7 +70,7 @@ html, body, #root { width: 10rem; } -.pf-v5-c-chip-group { +.pf-v5-c-chip-group { max-width: 100ch; } @@ -187,7 +189,7 @@ html, body, #root { .clickable-automated-analysis-label-popover-body-score { font-weight: bold; - margin-bottom: 0.5rem; + margin-bottom: 0.5rem; } .clickable-automated-analysis-label-popover-body-score.pf-v5-m-danger { @@ -360,7 +362,7 @@ html, body, #root { box-shadow: 0 0.5rem 1rem 0 rgba(3, 3, 3, 0.25); } -.dashboard-card-resizable-wrapper { +.dashboard-card-resizable-wrapper { display: flex; } @@ -379,7 +381,8 @@ html, body, #root { cursor: col-resize; position: relative; border-right: 4px solid var(--pf-v5-global--palette--black-500); - border-top-right-radius: var(--pf-v5-global--BorderRadius--sm); /* 3px */ + border-top-right-radius: var(--pf-v5-global--BorderRadius--sm); + /* 3px */ border-bottom-right-radius: var(--pf-v5-global--BorderRadius--sm); } @@ -416,7 +419,7 @@ html, body, #root { } .datetime-picker__meridiem-tile:last-child { - border-radius: 0 0 0.5rem 0.5rem ; + border-radius: 0 0 0.5rem 0.5rem; } .datetime-picker__meridiem-tile.selected { @@ -449,7 +452,8 @@ html, body, #root { } .datetime-picker__number-input input[type=number] { - -moz-appearance: textfield; /* Firefox */ + -moz-appearance: textfield; + /* Firefox */ width: 3.6rem; height: 3.6em; background-color: var(--pf-v5-global--palette--black-100); @@ -493,12 +497,13 @@ html, body, #root { color: var(--pf-v5-global--palette--black-300); } -.datetime-picker__calendar .pf-v5-c-calendar-month__header-year{ +.datetime-picker__calendar .pf-v5-c-calendar-month__header-year { min-width: 6em !important; } .recording-filter__toolbar-filter { - z-index: 199; /* Fix input border overlap */ + z-index: 199; + /* Fix input border overlap */ } .pf-v5-c-check__input { @@ -525,7 +530,7 @@ html, body, #root { .expandable-form__help-block { font-size: var(--pf-v5-global--FontSize--sm); white-space: pre-line; - color: var(--pf-v5-global--palette--black-700); + color: var(--pf-v5-global--palette--black-700); margin-bottom: 8px; margin-top: 0; } @@ -563,15 +568,19 @@ html, body, #root { padding: 0.3em 1em 0.3em 1em; } -.linear-dot-spinner{ +.linear-dot-spinner { width: 2em; aspect-ratio: 4; - background: radial-gradient(circle closest-side, var(--pf-v5-global--palette--blue-200) 90%,#0000) 0/calc(100%/3) 100% space; + background: radial-gradient(circle closest-side, var(--pf-v5-global--palette--blue-200) 90%, #0000) 0/calc(100%/3) 100% space; clip-path: inset(0 100% 0 0); animation: linear-dot-spinner-animate 1s steps(4) infinite; } -@keyframes linear-dot-spinner-animate {to {clip-path: inset(0 -34% 0 0)}} +@keyframes linear-dot-spinner-animate { + to { + clip-path: inset(0 -34% 0 0) + } +} .target-context-selector__linear-dot-spinner { width: 2em; @@ -718,3 +727,12 @@ svg.topology__node-decorator-icon.progress { .duration-picker__form_select { min-width: 10ch; } + +.theme__toggle-group .pf-v5-c-toggle-group__button { + --pf-v5-c-toggle-group__button--hover--BackgroundColor: var(--pf-v5-global--palette--black-700); + --pf-v5-c-toggle-group__button--focus--BackgroundColor: var(--pf-v5-global--palette--black-700); +} + +.theme__toggle-group .pf-v5-c-toggle-group__button.pf-m-selected { + --pf-v5-c-toggle-group__button--m-selected--BackgroundColor: var(--pf-v5-global--palette--blue-500); +} From dc0b6f1c0441cc6ea32d7060cb0f2dde47d2dcab Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 21:20:29 -0700 Subject: [PATCH 18/31] fix: adjust css for darkmode --- src/app/Topology/styles/base.css | 36 +++++++++++++++++--------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/app/Topology/styles/base.css b/src/app/Topology/styles/base.css index 6d92c42cf..a8f4613bf 100644 --- a/src/app/Topology/styles/base.css +++ b/src/app/Topology/styles/base.css @@ -28,7 +28,7 @@ Below CSS rules only apply to Topology components margin: 1em; } -:where(.pf-theme-dark) .sample-node-donut__node-wrapper { +:where(.pf-v5-theme-dark) .sample-node-donut__node-wrapper { background-color: var(--pf-v5-global--BackgroundColor--dark-300); } @@ -38,7 +38,8 @@ Below CSS rules only apply to Topology components aspect-ratio: 1; top: 50%; left: 50%; - margin: -45% 0 0 -45%; /* Ensure true center */ + margin: -45% 0 0 -45%; + /* Ensure true center */ border-radius: 50%; padding: 1em; border: 0.8em solid var(--pf-v5-global--palette--blue-400); @@ -69,7 +70,7 @@ Below CSS rules only apply to Topology components background-color: var(--pf-v5-global--palette--white); } -:where(.pf-theme-dark) .sample-node-donut__node-wrapper .sample-node-donut__status-indicator { +:where(.pf-v5-theme-dark) .sample-node-donut__node-wrapper .sample-node-donut__status-indicator { background-color: var(--pf-v5-global--BackgroundColor--dark-300); } @@ -90,7 +91,7 @@ Below CSS rules only apply to Topology components background-color: var(--pf-v5-global--palette--white); } -:where(.pf-theme-dark) .sample-node-donut__node-label { +:where(.pf-v5-theme-dark) .sample-node-donut__node-label { background-color: var(--pf-v5-global--BackgroundColor--dark-300); } @@ -149,10 +150,10 @@ Below CSS rules only apply to Topology components .entity-overview__entity-title-wrapper { font-weight: 700; font-size: 1.2em; - color: var(--pf-v5-global--palette--blue-500); + color: var(--pf-v5-global--palette--blue-500); } -:where(.pf-theme-dark) .entity-overview__entity-title-wrapper { +:where(.pf-v5-theme-dark) .entity-overview__entity-title-wrapper { color: var(--pf-v5-global--palette--blue-300); } @@ -187,11 +188,11 @@ Below CSS rules only apply to Topology components .topology-listview__realm-title { font-size: 1.2em; - color: var(--pf-v5-global--palette--blue-500); + color: var(--pf-v5-global--palette--blue-500); margin-right: 0.5em; } -:where(.pf-theme-dark) .topology-listview__realm-title { +:where(.pf-v5-theme-dark) .topology-listview__realm-title { color: var(--pf-v5-global--palette--blue-300); } @@ -223,7 +224,7 @@ Below CSS rules only apply to Topology components font-weight: 700; } -#topology__visualization-container.topology__main-container { +#topology__visualization-container.topology__main-container { padding: 1em; } @@ -249,7 +250,8 @@ Below CSS rules only apply to Topology components } .topology__toolbar-chip-content { - padding: 0 0.5em 0.4em 1em; /* Subtract 0.5em chip group right margin */ + padding: 0 0.5em 0.4em 1em; + /* Subtract 0.5em chip group right margin */ } .topology__filter-chip-group { @@ -265,7 +267,7 @@ Below CSS rules only apply to Topology components background-color: var(--pf-v5-global--palette--white); } -:where(.pf-theme-dark) .topology__quicksearch__tab-icon { +:where(.pf-v5-theme-dark) .topology__quicksearch__tab-icon { background-color: var(--pf-v5-global--BackgroundColor--dark-400); } @@ -277,11 +279,11 @@ Below CSS rules only apply to Topology components margin: 0 !important; } -:where(.pf-theme-dark) .topology__quicksearch__tabs { - background-color: var(--pf-v5-global--BackgroundColor--dark-300); +:where(.pf-v5-theme-dark) .topology__quicksearch__tabs { + background-color: var(--pf-v5-global--BackgroundColor--dark-300); } -:where(.pf-theme-dark) .topology__quicksearch__tabs .pf-v5-c-label { +:where(.pf-v5-theme-dark) .topology__quicksearch__tabs .pf-v5-c-label { background-color: var(--pf-v5-global--BackgroundColor--dark-200); } @@ -289,11 +291,11 @@ Below CSS rules only apply to Topology components background-color: var(--pf-v5-global--BackgroundColor--200); } -:where(.pf-theme-dark) .topology__quicksearch__tab.pf-m-current { +:where(.pf-v5-theme-dark) .topology__quicksearch__tab.pf-m-current { background-color: var(--pf-v5-global--BackgroundColor--100); } -:where(.pf-theme-dark) .topology__quicksearch__tab-content { +:where(.pf-v5-theme-dark) .topology__quicksearch__tab-content { background-color: var(--pf-v5-global--BackgroundColor--dark-300); } @@ -328,7 +330,7 @@ Below CSS rules only apply to Topology components height: 25em !important; } -:where(.pf-theme-dark) .topology__list-view__entity-details { +:where(.pf-v5-theme-dark) .topology__list-view__entity-details { background-color: var(--pf-v5-global--BackgroundColor--dark-100); } From 91ac178cc1b6d4f4792065fcdba200c9bd68dff3 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 22:31:47 -0700 Subject: [PATCH 19/31] chore: adjust css for topology shortcut --- src/app/Topology/Shared/Components/Shortcuts.tsx | 8 +++++++- src/app/Topology/styles/base.css | 10 +++++++++- src/app/app.css | 1 - 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/app/Topology/Shared/Components/Shortcuts.tsx b/src/app/Topology/Shared/Components/Shortcuts.tsx index 65a40a279..a9c323194 100644 --- a/src/app/Topology/Shared/Components/Shortcuts.tsx +++ b/src/app/Topology/Shared/Components/Shortcuts.tsx @@ -29,7 +29,13 @@ export interface ShortcutsProps { export const Shortcuts: React.FC = ({ shortcuts, ...props }) => { return ( - +
{shortcuts.map((sc) => ( diff --git a/src/app/Topology/styles/base.css b/src/app/Topology/styles/base.css index a8f4613bf..b72432135 100644 --- a/src/app/Topology/styles/base.css +++ b/src/app/Topology/styles/base.css @@ -38,8 +38,8 @@ Below CSS rules only apply to Topology components aspect-ratio: 1; top: 50%; left: 50%; - margin: -45% 0 0 -45%; /* Ensure true center */ + margin: -45% 0 0 -45%; border-radius: 50%; padding: 1em; border: 0.8em solid var(--pf-v5-global--palette--blue-400); @@ -211,6 +211,10 @@ Below CSS rules only apply to Topology components color: var(--pf-v5-global--palette--black-600); } +:where(.pf-v5-theme-dark) .topology__shortcut-command { + color: var(--pf-v5-global--palette--white); +} + .topology__shortcut-command-icon { margin-right: 0.5em; } @@ -219,6 +223,10 @@ Below CSS rules only apply to Topology components margin: 0 0.5em } +:where(.pf-v5-theme-dark) .topology__short-cuts.pf-v5-c-table { + --pf-v5-c-table--BackgroundColor: var(--pf-v5-global--BackgroundColor--300); +} + .topology__node-badge text { fill: var(--pf-v5-global--palette--white) !important; font-weight: 700; diff --git a/src/app/app.css b/src/app/app.css index e6b2ccad3..e36424a1a 100644 --- a/src/app/app.css +++ b/src/app/app.css @@ -382,7 +382,6 @@ body, position: relative; border-right: 4px solid var(--pf-v5-global--palette--black-500); border-top-right-radius: var(--pf-v5-global--BorderRadius--sm); - /* 3px */ border-bottom-right-radius: var(--pf-v5-global--BorderRadius--sm); } From 439bc8c64fcdb7102ac95b9396b2bc2828f02826 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Tue, 20 Aug 2024 23:03:31 -0700 Subject: [PATCH 20/31] chore: adjust group node label --- src/app/Topology/GraphView/CustomGroup.tsx | 2 ++ src/app/Topology/styles/base.css | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/app/Topology/GraphView/CustomGroup.tsx b/src/app/Topology/GraphView/CustomGroup.tsx index 02e6f107e..f3ca318e7 100644 --- a/src/app/Topology/GraphView/CustomGroup.tsx +++ b/src/app/Topology/GraphView/CustomGroup.tsx @@ -17,6 +17,7 @@ import openjdkSvg from '@app/assets/openjdk.svg'; import { RootState } from '@app/Shared/Redux/ReduxStore'; import { EnvironmentNode, NodeType } from '@app/Shared/Services/api.types'; +import { css } from '@patternfly/react-styles'; import { DefaultGroup, Node, @@ -99,6 +100,7 @@ const CustomGroup: React.FC = ({ collapsedHeight: collapsedHeight, collapsedWidth: collapsedWidth, badge: showBadge ? data.nodeType : undefined, + badgeClassName: css('topology__group-node-badge'), showLabel: true, contextMenuOpen: contextMenuOpen, onContextMenu: onContextMenu, diff --git a/src/app/Topology/styles/base.css b/src/app/Topology/styles/base.css index b72432135..68a1cd630 100644 --- a/src/app/Topology/styles/base.css +++ b/src/app/Topology/styles/base.css @@ -232,6 +232,14 @@ Below CSS rules only apply to Topology components font-weight: 700; } +.topology__group-node-badge { + fill: var(--pf-v5-global--palette--white); +} + +:where(.pf-v5-theme-dark) .topology__group-node-badge { + fill: var(--pf-v5-global--palette--black-300); +} + #topology__visualization-container.topology__main-container { padding: 1em; } From a5ef359cf6c1a087cd5290db3ce0f38c787a912f Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Wed, 21 Aug 2024 14:26:22 -0700 Subject: [PATCH 21/31] fix: escape search term --- src/app/DateTimePicker/TimezonePicker.tsx | 5 +++-- src/app/SecurityPanel/Credentials/CredentialTestTable.tsx | 3 ++- src/app/Settings/Config/DatetimeControl.tsx | 5 +++-- src/app/TargetView/TargetContextSelector.tsx | 3 ++- src/app/TargetView/TargetSelect.tsx | 3 ++- src/app/Topology/Actions/QuickSearchPanel.tsx | 3 ++- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/app/DateTimePicker/TimezonePicker.tsx b/src/app/DateTimePicker/TimezonePicker.tsx index 45b876153..8765a4856 100644 --- a/src/app/DateTimePicker/TimezonePicker.tsx +++ b/src/app/DateTimePicker/TimezonePicker.tsx @@ -31,6 +31,7 @@ import { } from '@patternfly/react-core'; import { GlobeIcon } from '@patternfly/react-icons'; import { css } from '@patternfly/react-styles'; +import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -50,7 +51,7 @@ export const TimezonePicker: React.FC = ({ onTimezoneChange = (_) => undefined, }) => { const { t } = useTranslation(); - const [dayjs, _] = useDayjs(); + const [dayjs, _dateFormat] = useDayjs(); const [numOfOptions, setNumOfOptions] = React.useState(DEFAULT_NUM_OPTIONS); const [isTimezoneOpen, setIsTimezoneOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); @@ -100,7 +101,7 @@ export const TimezonePicker: React.FC = ({ const filteredTimezones = React.useMemo(() => { let _opts = timezones; if (searchTerm) { - const matchExp = new RegExp(searchTerm.replace(/([+])/gi, `\\$1`), 'i'); + const matchExp = new RegExp(_.escapeRegExp(searchTerm), 'i'); _opts = _opts.filter((tz) => matchExp.test(tz.full) || matchExp.test(tz.short)); } return _opts.slice(0, numOfOptions); diff --git a/src/app/SecurityPanel/Credentials/CredentialTestTable.tsx b/src/app/SecurityPanel/Credentials/CredentialTestTable.tsx index d8c15fb1b..8c9dfb180 100644 --- a/src/app/SecurityPanel/Credentials/CredentialTestTable.tsx +++ b/src/app/SecurityPanel/Credentials/CredentialTestTable.tsx @@ -55,6 +55,7 @@ import { Thead, Tr, } from '@patternfly/react-table'; +import _ from 'lodash'; import * as React from 'react'; import { catchError, combineLatest, of, switchMap, tap } from 'rxjs'; import { TestPoolContext, useAuthCredential } from './utils'; @@ -217,7 +218,7 @@ export const CredentialTestRow: React.FC = ({ const isEmptyCredential = React.useMemo(() => credential.password === '' || credential.username === '', [credential]); const isShowed = React.useMemo(() => { - const regex = new RegExp(searchText, 'i'); + const regex = new RegExp(_.escapeRegExp(searchText), 'i'); if (searchText !== '' && !(regex.test(target.alias) || regex.test(target.connectUrl))) { return false; } diff --git a/src/app/Settings/Config/DatetimeControl.tsx b/src/app/Settings/Config/DatetimeControl.tsx index 15947e317..1c1a2efba 100644 --- a/src/app/Settings/Config/DatetimeControl.tsx +++ b/src/app/Settings/Config/DatetimeControl.tsx @@ -34,6 +34,7 @@ import { Stack, StackItem, } from '@patternfly/react-core'; +import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { SettingTab, UserSetting } from '../types'; @@ -42,7 +43,7 @@ const Component = () => { const { t } = useTranslation(); const context = React.useContext(ServiceContext); const [dateLocaleOpen, setDateLocaleOpen] = React.useState(false); - const [_, datetimeFormat] = useDayjs(); + const [_dayjs, datetimeFormat] = useDayjs(); const [searchTerm, setSearchTerm] = React.useState(''); const handleDateLocaleSelect = React.useCallback( @@ -79,7 +80,7 @@ const Component = () => { if (!searchTerm) { return true; } - const matchExp = new RegExp(searchTerm, 'i'); + const matchExp = new RegExp(_.escapeRegExp(searchTerm), 'i'); return matchExp.test(locale.name) || matchExp.test(locale.key); }) .map((locale) => ( diff --git a/src/app/TargetView/TargetContextSelector.tsx b/src/app/TargetView/TargetContextSelector.tsx index 68a76d237..1187e4625 100644 --- a/src/app/TargetView/TargetContextSelector.tsx +++ b/src/app/TargetView/TargetContextSelector.tsx @@ -36,6 +36,7 @@ import { Split, SplitItem, } from '@patternfly/react-core'; +import _ from 'lodash'; import * as React from 'react'; import { Link } from 'react-router-dom'; @@ -129,7 +130,7 @@ export const TargetContextSelector: React.FC = ({ cl ]; } - const matchExp = new RegExp(searchTerm, 'i'); + const matchExp = new RegExp(_.escapeRegExp(searchTerm), 'i'); const filteredTargets = targets.filter((t) => [t.alias, t.connectUrl, getAnnotation(t.annotations.cryostat, 'REALM') ?? ''].some((v) => matchExp.test(v)), ); diff --git a/src/app/TargetView/TargetSelect.tsx b/src/app/TargetView/TargetSelect.tsx index 07b24c145..9979143ae 100644 --- a/src/app/TargetView/TargetSelect.tsx +++ b/src/app/TargetView/TargetSelect.tsx @@ -40,6 +40,7 @@ import { DropdownItem, } from '@patternfly/react-core'; import { ContainerNodeIcon } from '@patternfly/react-icons'; +import _ from 'lodash'; import * as React from 'react'; export interface TargetSelectProps { @@ -114,7 +115,7 @@ export const TargetSelect: React.FC = ({ onSelect, simple, .. }, [handleSelect, targets, selected, firstLoadRef]); const selectOptions = React.useMemo(() => { - const matchExp = new RegExp(searchTerm, 'i'); + const matchExp = new RegExp(_.escapeRegExp(searchTerm), 'i'); const filteredTargets = targets.filter((t) => [t.alias, t.connectUrl, getAnnotation(t.annotations.cryostat, 'REALM') ?? ''].some((v) => matchExp.test(v)), ); diff --git a/src/app/Topology/Actions/QuickSearchPanel.tsx b/src/app/Topology/Actions/QuickSearchPanel.tsx index 0217972ce..caa6d2432 100644 --- a/src/app/Topology/Actions/QuickSearchPanel.tsx +++ b/src/app/Topology/Actions/QuickSearchPanel.tsx @@ -46,6 +46,7 @@ import { import { SearchIcon } from '@patternfly/react-icons'; import { css } from '@patternfly/react-styles'; import { useHover } from '@patternfly/react-topology'; +import _ from 'lodash'; import * as React from 'react'; import { Link, useNavigate } from 'react-router-dom'; import QuickSearchIcon from '../../Shared/Components/QuickSearchIcon'; @@ -135,7 +136,7 @@ export const QuickSearchPanel: React.FC = ({ ...props }) const filteredQuicksearches = React.useMemo(() => { let items = quickSearches.filter((qs) => activeLevel <= qs.featureLevel); if (searchText && searchText !== '') { - const regex = new RegExp(searchText, 'i'); + const regex = new RegExp(_.escapeRegExp(searchText), 'i'); items = items.filter(({ name, descriptionFull = '', descriptionShort = '', labels = [] }) => { let matchResult = regex.test(name) || regex.test(descriptionFull) || regex.test(descriptionShort); From e7cd01408e8ae9cbb883e45360639d935cf79100 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Wed, 21 Aug 2024 14:52:50 -0700 Subject: [PATCH 22/31] fix: ensure timezone is searched by what is displayed --- src/app/DateTimePicker/TimezonePicker.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/DateTimePicker/TimezonePicker.tsx b/src/app/DateTimePicker/TimezonePicker.tsx index 8765a4856..77213c507 100644 --- a/src/app/DateTimePicker/TimezonePicker.tsx +++ b/src/app/DateTimePicker/TimezonePicker.tsx @@ -82,6 +82,11 @@ export const TimezonePicker: React.FC = ({ [setNumOfOptions, timezones], ); + const getTimezoneDisplay = React.useCallback( + (timezone: Timezone) => `(UTC${dayjs().tz(timezone.full).format('Z')}) ${timezone.full}`, + [dayjs], + ); + const mapToSelection = React.useCallback( (timezone: Timezone) => { return ( @@ -91,21 +96,21 @@ export const TimezonePicker: React.FC = ({ description={timezone.short} isSelected={selected.full === timezone.full} > - {`(UTC${dayjs().tz(timezone.full).format('Z')}) ${timezone.full}`} + {getTimezoneDisplay(timezone)} ); }, - [dayjs, selected], + [selected, getTimezoneDisplay], ); const filteredTimezones = React.useMemo(() => { let _opts = timezones; if (searchTerm) { const matchExp = new RegExp(_.escapeRegExp(searchTerm), 'i'); - _opts = _opts.filter((tz) => matchExp.test(tz.full) || matchExp.test(tz.short)); + _opts = _opts.filter((tz) => matchExp.test(getTimezoneDisplay(tz))); } return _opts.slice(0, numOfOptions); - }, [timezones, numOfOptions, searchTerm]); + }, [timezones, numOfOptions, searchTerm, getTimezoneDisplay]); const toggle = React.useCallback( (toggleRef: React.Ref) => ( From 7671e1dc5a5626bf59abfa054ed280970d73a225 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Wed, 21 Aug 2024 14:54:28 -0700 Subject: [PATCH 23/31] chore: use plural --- locales/en/common.json | 2 +- src/app/Settings/Config/ChartCards.tsx | 2 +- src/app/Settings/Config/WebSocketDebounce.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en/common.json b/locales/en/common.json index b1f4abf31..78fccfecd 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -43,7 +43,7 @@ "MAXIMUM_SIZE_HELPER_TEXT": "The maximum size of Recording data saved to disk.", "MERIDIEM_AM": "AM", "MERIDIEM_PM": "PM", - "MILLISECOND": "Millisecond", + "MILLISECOND_other": "Milliseconds", "MINUTE": "Minute", "MINUTE_one": "Minute", "MINUTE_other": "Minutes", diff --git a/src/app/Settings/Config/ChartCards.tsx b/src/app/Settings/Config/ChartCards.tsx index 1d343920e..38e11a70c 100644 --- a/src/app/Settings/Config/ChartCards.tsx +++ b/src/app/Settings/Config/ChartCards.tsx @@ -75,7 +75,7 @@ const Component = () => { onChange={handleChange} onMinus={handleVisibleStep(-1)} onPlus={handleVisibleStep(1)} - unit={t('SECOND', { ns: 'common' })} + unit={t('SECOND_other', { ns: 'common' })} /> diff --git a/src/app/Settings/Config/WebSocketDebounce.tsx b/src/app/Settings/Config/WebSocketDebounce.tsx index e9274e66f..7156089e7 100644 --- a/src/app/Settings/Config/WebSocketDebounce.tsx +++ b/src/app/Settings/Config/WebSocketDebounce.tsx @@ -89,7 +89,7 @@ const Component = () => { onChange={handleWebSocketDebounceChange} onMinus={handleWebSocketDebounceMinus} onPlus={handleWebSocketDebouncePlus} - unit={t('MILLISECOND', { ns: 'common' })} + unit={t('MILLISECOND_other', { ns: 'common' })} /> ); From 9a82b363c4b42780c685f34bd28de137084dd9be Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Wed, 21 Aug 2024 23:01:18 -0700 Subject: [PATCH 24/31] chore: adjust dark theme css --- .../ClickableAutomatedAnalysisLabel.tsx | 1 + src/app/DateTimePicker/TimePicker.tsx | 2 +- src/app/TargetView/TargetContextSelector.tsx | 16 +++--- src/app/app.css | 51 ++++++++++--------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/app/Dashboard/AutomatedAnalysis/ClickableAutomatedAnalysisLabel.tsx b/src/app/Dashboard/AutomatedAnalysis/ClickableAutomatedAnalysisLabel.tsx index e55dce124..d7fc03728 100644 --- a/src/app/Dashboard/AutomatedAnalysis/ClickableAutomatedAnalysisLabel.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/ClickableAutomatedAnalysisLabel.tsx @@ -102,6 +102,7 @@ export const ClickableAutomatedAnalysisLabel: React.FC } appendTo={portalRoot} + className={`${clickableAutomatedAnalysisKey}-popover`} >