From 2601bf96f2b6524d5dc5c86c4bddd3e19b4b1988 Mon Sep 17 00:00:00 2001 From: Oskar Nyberg Date: Thu, 28 Dec 2023 10:46:34 +0100 Subject: [PATCH 1/5] Prevent creating custom lists with empty names --- .../select-location/CustomLists.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/gui/src/renderer/components/select-location/CustomLists.tsx b/gui/src/renderer/components/select-location/CustomLists.tsx index 7fb66f630e10..6799a3486793 100644 --- a/gui/src/renderer/components/select-location/CustomLists.tsx +++ b/gui/src/renderer/components/select-location/CustomLists.tsx @@ -52,7 +52,11 @@ const StyledAddListCellButton = styled(StyledCellButton)({ const StyledSideButtonIcon = styled(Cell.Icon)({ padding: '3px', - [`${StyledCellButton}:hover &&, ${StyledAddListCellButton}:hover &&`]: { + [`${StyledCellButton}:disabled &&, ${StyledAddListCellButton}:disabled &&`]: { + backgroundColor: colors.white40, + }, + + [`${StyledCellButton}:not(:disabled):hover &&, ${StyledAddListCellButton}:not(:disabled):hover &&`]: { backgroundColor: colors.white, }, }); @@ -117,6 +121,7 @@ interface AddListFormProps { function AddListForm(props: AddListFormProps) { const [name, setName] = useState(''); + const nameValid = name.trim() !== ''; const [error, setError, unsetError] = useBoolean(); const containerRef = useStyledRef(); const inputRef = useStyledRef(); @@ -128,16 +133,18 @@ function AddListForm(props: AddListFormProps) { }, []); const createList = useCallback(async () => { - try { - const result = await props.onCreateList(name); - if (result) { - setError(); + if (nameValid) { + try { + const result = await props.onCreateList(name); + if (result) { + setError(); + } + } catch (e) { + const error = e as Error; + log.error('Failed to create list:', error.message); } - } catch (e) { - const error = e as Error; - log.error('Failed to create list:', error.message); } - }, [name, props.onCreateList]); + }, [name, props.onCreateList, nameValid]); const onBlur = useCallback( (event: React.FocusEvent) => { @@ -180,6 +187,7 @@ function AddListForm(props: AddListFormProps) { From 2accfd90d5a98f9b7a02a3c60fd4164e58bf5824 Mon Sep 17 00:00:00 2001 From: Oskar Nyberg Date: Thu, 28 Dec 2023 10:46:56 +0100 Subject: [PATCH 2/5] Show disabled chevron for custom lists without items --- .../renderer/components/select-location/LocationRow.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gui/src/renderer/components/select-location/LocationRow.tsx b/gui/src/renderer/components/select-location/LocationRow.tsx index 97c8528fa94d..431df2433130 100644 --- a/gui/src/renderer/components/select-location/LocationRow.tsx +++ b/gui/src/renderer/components/select-location/LocationRow.tsx @@ -165,12 +165,12 @@ function LocationRow(props: IProps) { // Expand/collapse should only be available if the expanded property is provided in the source const expanded = 'expanded' in props.source ? props.source.expanded : undefined; const toggleCollapse = useCallback(() => { - if (expanded !== undefined) { + if (expanded !== undefined && hasChildren) { userInvokedExpand.current = true; const callback = expanded ? props.onCollapse : props.onExpand; callback(props.source.location); } - }, [props.onExpand, props.onCollapse, props.source.location, expanded]); + }, [props.onExpand, props.onCollapse, props.source.location, expanded, hasChildren]); const handleClick = useCallback(() => { if (!props.source.selected) { @@ -275,10 +275,12 @@ function LocationRow(props: IProps) { ) : null} - {hasChildren ? ( + {hasChildren || + ('customList' in props.source.location && !('country' in props.source.location)) ? ( Date: Thu, 28 Dec 2023 10:56:52 +0100 Subject: [PATCH 3/5] Align custom list buttons --- gui/src/renderer/components/select-location/LocationRow.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gui/src/renderer/components/select-location/LocationRow.tsx b/gui/src/renderer/components/select-location/LocationRow.tsx index 431df2433130..e958bd0c98bb 100644 --- a/gui/src/renderer/components/select-location/LocationRow.tsx +++ b/gui/src/renderer/components/select-location/LocationRow.tsx @@ -105,6 +105,10 @@ const StyledHoverIconButton = styled.button Date: Tue, 2 Jan 2024 08:09:48 +0100 Subject: [PATCH 4/5] Add back action for custom list add input --- .../select-location/CustomLists.tsx | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/gui/src/renderer/components/select-location/CustomLists.tsx b/gui/src/renderer/components/select-location/CustomLists.tsx index 6799a3486793..737d66bf89cd 100644 --- a/gui/src/renderer/components/select-location/CustomLists.tsx +++ b/gui/src/renderer/components/select-location/CustomLists.tsx @@ -10,6 +10,7 @@ import { useBoolean, useStyledRef } from '../../lib/utilityHooks'; import Accordion from '../Accordion'; import * as Cell from '../cell'; import { measurements } from '../common-styles'; +import { BackAction } from '../KeyboardNavigation'; import SimpleInput from '../SimpleInput'; import { StyledLocationRowIcon } from './LocationRow'; import { useRelayListContext } from './RelayListContext'; @@ -169,35 +170,37 @@ function AddListForm(props: AddListFormProps) { }, [props.visible]); return ( - - - - - - - - - - - - - {messages.pgettext('select-location-view', 'List names must be unique.')} - - - + + + + + + + + + + + + + + {messages.pgettext('select-location-view', 'List names must be unique.')} + + + + ); } From 2bebcce89bab9b4de95ed2cfa3c496059602ea3b Mon Sep 17 00:00:00 2001 From: Oskar Nyberg Date: Tue, 2 Jan 2024 13:04:40 +0100 Subject: [PATCH 5/5] Add delete confirmation for custom lists --- gui/locales/messages.pot | 7 +++ .../select-location/CustomListDialogs.tsx | 44 ++++++++++++++++++- .../select-location/LocationRow.tsx | 16 +++++-- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 161ad721c7dc..70a89c22e725 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -99,6 +99,9 @@ msgstr "" msgid "Default" msgstr "" +msgid "Delete list" +msgstr "" + msgid "Disable" msgstr "" @@ -1035,6 +1038,10 @@ msgctxt "select-location-view" msgid "Custom lists" msgstr "" +msgctxt "select-location-view" +msgid "Do you want to delete the list %(list)s?" +msgstr "" + msgctxt "select-location-view" msgid "Edit list name" msgstr "" diff --git a/gui/src/renderer/components/select-location/CustomListDialogs.tsx b/gui/src/renderer/components/select-location/CustomListDialogs.tsx index 7af8c1b622df..56244e73ebcf 100644 --- a/gui/src/renderer/components/select-location/CustomListDialogs.tsx +++ b/gui/src/renderer/components/select-location/CustomListDialogs.tsx @@ -18,7 +18,7 @@ import { useSelector } from '../../redux/store'; import * as AppButton from '../AppButton'; import * as Cell from '../cell'; import { normalText, tinyText } from '../common-styles'; -import { ModalAlert, ModalMessage } from '../Modal'; +import { ModalAlert, ModalAlertType, ModalMessage } from '../Modal'; import SimpleInput from '../SimpleInput'; const StyledModalMessage = styled(ModalMessage)({ @@ -201,3 +201,45 @@ export function EditListDialog(props: EditListProps) { ); } + +interface DeleteConfirmDialogProps { + list: ICustomList; + isOpen: boolean; + hide: () => void; + confirm: () => void; +} + +// Dialog for changing the name of a custom list. +export function DeleteConfirmDialog(props: DeleteConfirmDialogProps) { + const confirm = useCallback(() => { + props.confirm(); + props.hide(); + }, []); + + return ( + + {messages.gettext('Delete list')} + , + + {messages.gettext('Cancel')} + , + ]} + close={props.hide}> + + {formatHtml( + sprintf( + messages.pgettext( + 'select-location-view', + 'Do you want to delete the list %(list)s?', + ), + { list: props.list.name }, + ), + )} + + + ); +} diff --git a/gui/src/renderer/components/select-location/LocationRow.tsx b/gui/src/renderer/components/select-location/LocationRow.tsx index e958bd0c98bb..79b44d372a66 100644 --- a/gui/src/renderer/components/select-location/LocationRow.tsx +++ b/gui/src/renderer/components/select-location/LocationRow.tsx @@ -19,7 +19,7 @@ import ChevronButton from '../ChevronButton'; import { measurements, normalText } from '../common-styles'; import ImageView from '../ImageView'; import RelayStatusIndicator from '../RelayStatusIndicator'; -import { AddToListDialog, EditListDialog } from './CustomListDialogs'; +import { AddToListDialog, DeleteConfirmDialog, EditListDialog } from './CustomListDialogs'; import { CitySpecification, CountrySpecification, @@ -162,6 +162,7 @@ function LocationRow(props: IProps) { const { updateCustomList, deleteCustomList } = useAppContext(); const [addToListDialogVisible, showAddToListDialog, hideAddToListDialog] = useBoolean(); const [editDialogVisible, showEditDialog, hideEditDialog] = useBoolean(); + const [deleteDialogVisible, showDeleteDialog, hideDeleteDialog] = useBoolean(); const background = getButtonColor(props.source.selected, props.level, props.source.disabled); const customLists = useSelector((state) => state.settings.customLists); @@ -218,7 +219,7 @@ function LocationRow(props: IProps) { }, [customLists, props.source.location]); // Remove an entire custom list. - const onRemoveCustomList = useCallback(async () => { + const confirmRemoveCustomList = useCallback(async () => { if (props.source.location.customList) { try { await deleteCustomList(props.source.location.customList); @@ -273,7 +274,7 @@ function LocationRow(props: IProps) { - + @@ -318,6 +319,15 @@ function LocationRow(props: IProps) { {'list' in props.source && ( )} + + {'list' in props.source && ( + + )} ); }