Skip to content

Commit

Permalink
Merge branch 'prevent-empty-custom-lists-names-des-530'
Browse files Browse the repository at this point in the history
  • Loading branch information
raksooo committed Jan 5, 2024
2 parents 1b97311 + 2bebcce commit 70eee39
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 44 deletions.
7 changes: 7 additions & 0 deletions gui/locales/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ msgstr ""
msgid "Default"
msgstr ""

msgid "Delete list"
msgstr ""

msgid "Disable"
msgstr ""

Expand Down Expand Up @@ -1035,6 +1038,10 @@ msgctxt "select-location-view"
msgid "Custom lists"
msgstr ""

msgctxt "select-location-view"
msgid "Do you want to delete the list <b>%(list)s</b>?"
msgstr ""

msgctxt "select-location-view"
msgid "Edit list name"
msgstr ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)({
Expand Down Expand Up @@ -201,3 +201,45 @@ export function EditListDialog(props: EditListProps) {
</ModalAlert>
);
}

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 (
<ModalAlert
type={ModalAlertType.warning}
isOpen={props.isOpen}
buttons={[
<AppButton.RedButton key="save" onClick={confirm}>
{messages.gettext('Delete list')}
</AppButton.RedButton>,
<AppButton.BlueButton key="cancel" onClick={props.hide}>
{messages.gettext('Cancel')}
</AppButton.BlueButton>,
]}
close={props.hide}>
<ModalMessage>
{formatHtml(
sprintf(
messages.pgettext(
'select-location-view',
'Do you want to delete the list <b>%(list)s</b>?',
),
{ list: props.list.name },
),
)}
</ModalMessage>
</ModalAlert>
);
}
85 changes: 48 additions & 37 deletions gui/src/renderer/components/select-location/CustomLists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -52,7 +53,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,
},
});
Expand Down Expand Up @@ -117,6 +122,7 @@ interface AddListFormProps {

function AddListForm(props: AddListFormProps) {
const [name, setName] = useState('');
const nameValid = name.trim() !== '';
const [error, setError, unsetError] = useBoolean();
const containerRef = useStyledRef<HTMLDivElement>();
const inputRef = useStyledRef<HTMLInputElement>();
Expand All @@ -128,16 +134,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<HTMLInputElement>) => {
Expand All @@ -162,34 +170,37 @@ function AddListForm(props: AddListFormProps) {
}, [props.visible]);

return (
<Accordion expanded={props.visible} onTransitionEnd={onTransitionEnd}>
<StyledCellContainer ref={containerRef}>
<StyledInputContainer>
<StyledInput
ref={inputRef}
value={name}
onChangeValue={onChange}
onSubmitValue={createList}
onBlur={onBlur}
maxLength={30}
$error={error}
autoFocus
/>
</StyledInputContainer>

<StyledAddListCellButton
$backgroundColor={colors.blue}
$backgroundColorHover={colors.blue80}
onClick={createList}>
<StyledSideButtonIcon source="icon-check" tintColor={colors.white60} width={18} />
</StyledAddListCellButton>
</StyledCellContainer>
<Cell.CellFooter>
<Cell.CellFooterText>
{messages.pgettext('select-location-view', 'List names must be unique.')}
</Cell.CellFooterText>
</Cell.CellFooter>
</Accordion>
<BackAction disabled={!props.visible} action={props.cancel}>
<Accordion expanded={props.visible} onTransitionEnd={onTransitionEnd}>
<StyledCellContainer ref={containerRef}>
<StyledInputContainer>
<StyledInput
ref={inputRef}
value={name}
onChangeValue={onChange}
onSubmitValue={createList}
onBlur={onBlur}
maxLength={30}
$error={error}
autoFocus
/>
</StyledInputContainer>

<StyledAddListCellButton
$backgroundColor={colors.blue}
$backgroundColorHover={colors.blue80}
disabled={!nameValid}
onClick={createList}>
<StyledSideButtonIcon source="icon-check" tintColor={colors.white60} width={18} />
</StyledAddListCellButton>
</StyledCellContainer>
<Cell.CellFooter>
<Cell.CellFooterText>
{messages.pgettext('select-location-view', 'List names must be unique.')}
</Cell.CellFooterText>
</Cell.CellFooter>
</Accordion>
</BackAction>
);
}

Expand Down
28 changes: 22 additions & 6 deletions gui/src/renderer/components/select-location/LocationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -105,6 +105,10 @@ const StyledHoverIconButton = styled.button<IButtonColorProps & { $isLast?: bool
height: measurements.rowMinHeight,
appearance: 'none',

'&&:last-child': {
paddingRight: '25px',
},

'&&:not(:disabled):hover': {
backgroundColor: props.$backgroundColor,
},
Expand Down Expand Up @@ -158,19 +162,20 @@ function LocationRow<C extends LocationSpecification>(props: IProps<C>) {
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);

// 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) {
Expand Down Expand Up @@ -214,7 +219,7 @@ function LocationRow<C extends LocationSpecification>(props: IProps<C>) {
}, [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);
Expand Down Expand Up @@ -269,16 +274,18 @@ function LocationRow<C extends LocationSpecification>(props: IProps<C>) {
<StyledHoverIconButton onClick={showEditDialog} {...background}>
<StyledHoverIcon source="icon-edit" />
</StyledHoverIconButton>
<StyledHoverIconButton onClick={onRemoveCustomList} $isLast {...background}>
<StyledHoverIconButton onClick={showDeleteDialog} $isLast {...background}>
<StyledHoverIcon source="icon-close" />
</StyledHoverIconButton>
</>
) : null}

{hasChildren ? (
{hasChildren ||
('customList' in props.source.location && !('country' in props.source.location)) ? (
<StyledLocationRowIcon
as={ChevronButton}
onClick={toggleCollapse}
disabled={!hasChildren}
up={expanded ?? false}
aria-label={sprintf(
expanded === true
Expand Down Expand Up @@ -312,6 +319,15 @@ function LocationRow<C extends LocationSpecification>(props: IProps<C>) {
{'list' in props.source && (
<EditListDialog list={props.source.list} isOpen={editDialogVisible} hide={hideEditDialog} />
)}

{'list' in props.source && (
<DeleteConfirmDialog
list={props.source.list}
isOpen={deleteDialogVisible}
hide={hideDeleteDialog}
confirm={confirmRemoveCustomList}
/>
)}
</>
);
}
Expand Down

0 comments on commit 70eee39

Please sign in to comment.