From 4b97abaf7221f6ad267f92b5575771cc8440834e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 14 May 2022 12:53:02 +0200 Subject: [PATCH] Improved tags filtering for short URLs, allowing to select from any existing tag --- src/common/react-tag-autocomplete.scss | 6 ++ src/short-urls/ShortUrlsFilteringBar.scss | 1 + src/short-urls/ShortUrlsFilteringBar.tsx | 64 ++++++++----------- src/short-urls/services/provideServices.ts | 2 +- src/tags/helpers/TagsSelector.tsx | 5 +- .../short-urls/ShortUrlsFilteringBar.test.tsx | 3 +- 6 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/common/react-tag-autocomplete.scss b/src/common/react-tag-autocomplete.scss index d410ac8d..5b0c8ab5 100644 --- a/src/common/react-tag-autocomplete.scss +++ b/src/common/react-tag-autocomplete.scss @@ -1,5 +1,11 @@ @import '../utils/base'; +.input-group > .react-tags { + flex: 1 1 auto; + width: 1%; + min-width: 0; +} + .react-tags { position: relative; padding: 5px 0 0 6px; diff --git a/src/short-urls/ShortUrlsFilteringBar.scss b/src/short-urls/ShortUrlsFilteringBar.scss index 905210fd..32c75b20 100644 --- a/src/short-urls/ShortUrlsFilteringBar.scss +++ b/src/short-urls/ShortUrlsFilteringBar.scss @@ -1,3 +1,4 @@ .short-urls-filtering-bar__tags-icon { vertical-align: bottom; + font-size: 1.6rem; } diff --git a/src/short-urls/ShortUrlsFilteringBar.tsx b/src/short-urls/ShortUrlsFilteringBar.tsx index 6b21b20b..5f87009a 100644 --- a/src/short-urls/ShortUrlsFilteringBar.tsx +++ b/src/short-urls/ShortUrlsFilteringBar.tsx @@ -1,24 +1,22 @@ import { FC } from 'react'; -import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { isEmpty, pipe } from 'ramda'; import { parseISO } from 'date-fns'; -import { Row } from 'reactstrap'; +import { Button, InputGroup, Row, UncontrolledTooltip } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTag, faTags } from '@fortawesome/free-solid-svg-icons'; import classNames from 'classnames'; import SearchField from '../utils/SearchField'; -import Tag from '../tags/helpers/Tag'; import { DateRangeSelector } from '../utils/dates/DateRangeSelector'; import { formatIsoDate } from '../utils/helpers/date'; -import ColorGenerator from '../utils/services/ColorGenerator'; import { DateRange } from '../utils/dates/types'; import { supportsAllTagsFiltering } from '../utils/helpers/features'; import { SelectedServer } from '../servers/data'; -import { TooltipToggleSwitch } from '../utils/TooltipToggleSwitch'; import { OrderDir } from '../utils/helpers/ordering'; import { OrderingDropdown } from '../utils/OrderingDropdown'; import { useShortUrlsQuery } from './helpers/hooks'; import { SHORT_URLS_ORDERABLE_FIELDS, ShortUrlsOrder, ShortUrlsOrderableFields } from './data'; import { ExportShortUrlsBtnProps } from './helpers/ExportShortUrlsBtn'; +import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; import './ShortUrlsFilteringBar.scss'; export interface ShortUrlsFilteringProps { @@ -32,8 +30,8 @@ export interface ShortUrlsFilteringProps { const dateOrNull = (date?: string) => (date ? parseISO(date) : null); const ShortUrlsFilteringBar = ( - colorGenerator: ColorGenerator, ExportShortUrlsBtn: FC, + TagsSelector: FC, ): FC => ({ selectedServer, className, shortUrlsAmount, order, handleOrderBy }) => { const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage] = useShortUrlsQuery(); const setDates = pipe( @@ -47,10 +45,7 @@ const ShortUrlsFilteringBar = ( (searchTerm: string) => (isEmpty(searchTerm) ? undefined : searchTerm), (searchTerm) => toFirstPage({ search: searchTerm }), ); - const removeTag = pipe( - (tag: string) => tags.filter((selectedTag) => selectedTag !== tag), - (updateTags) => toFirstPage({ tags: updateTags }), - ); + const changeTagSelection = (selectedTags: string[]) => toFirstPage({ tags: selectedTags }); const canChangeTagsMode = supportsAllTagsFiltering(selectedServer); const toggleTagsMode = pipe( () => (tagsMode === 'any' ? 'all' : 'any'), @@ -61,13 +56,21 @@ const ShortUrlsFilteringBar = (
- -
- -
-
- -
+ + + {canChangeTagsMode && tags.length > 1 && ( + <> + + + {tagsMode === 'all' ? 'With all the tags.' : 'With any of the tags.'} + + + )} + + +
+
+ +
+
+ +
- - {tags.length > 0 && ( -

- {canChangeTagsMode && tags.length > 1 && ( -
- - {tagsMode === 'all' ? 'Short URLs including all tags.' : 'Short URLs including any tag.'} - -
- )} - - {tags.map((tag) => - removeTag(tag)} />)} -

- )}
); }; diff --git a/src/short-urls/services/provideServices.ts b/src/short-urls/services/provideServices.ts index f895bb72..f0c31f58 100644 --- a/src/short-urls/services/provideServices.ts +++ b/src/short-urls/services/provideServices.ts @@ -50,7 +50,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('QrCodeModal', QrCodeModal, 'ImageDownloader'); bottle.decorator('QrCodeModal', connect(['selectedServer'])); - bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator', 'ExportShortUrlsBtn'); + bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ExportShortUrlsBtn', 'TagsSelector'); bottle.serviceFactory('ExportShortUrlsBtn', ExportShortUrlsBtn, 'buildShlinkApiClient', 'ReportExporter'); bottle.decorator('ExportShortUrlsBtn', connect(['selectedServer'])); diff --git a/src/tags/helpers/TagsSelector.tsx b/src/tags/helpers/TagsSelector.tsx index deb336d4..6f47b58f 100644 --- a/src/tags/helpers/TagsSelector.tsx +++ b/src/tags/helpers/TagsSelector.tsx @@ -10,6 +10,7 @@ export interface TagsSelectorProps { selectedTags: string[]; onChange: (tags: string[]) => void; placeholder?: string; + allowNew?: boolean; } interface TagsSelectorConnectProps extends TagsSelectorProps { @@ -21,7 +22,7 @@ interface TagsSelectorConnectProps extends TagsSelectorProps { const toComponentTag = (tag: string) => ({ id: tag, name: tag }); const TagsSelector = (colorGenerator: ColorGenerator) => ( - { selectedTags, onChange, placeholder, listTags, tagsList, settings }: TagsSelectorConnectProps, + { selectedTags, onChange, placeholder, listTags, tagsList, settings, allowNew = true }: TagsSelectorConnectProps, ) => { useEffect(() => { listTags(); @@ -43,7 +44,7 @@ const TagsSelector = (colorGenerator: ColorGenerator) => ( tagComponent={ReactTagsTag} suggestions={tagsList.tags.filter((tag) => !selectedTags.includes(tag)).map(toComponentTag)} suggestionComponent={ReactTagsSuggestion} - allowNew + allowNew={allowNew} addOnBlur placeholderText={placeholder ?? 'Add tags to the URL'} minQueryLength={1} diff --git a/test/short-urls/ShortUrlsFilteringBar.test.tsx b/test/short-urls/ShortUrlsFilteringBar.test.tsx index d7307aca..e6adb7ee 100644 --- a/test/short-urls/ShortUrlsFilteringBar.test.tsx +++ b/test/short-urls/ShortUrlsFilteringBar.test.tsx @@ -6,7 +6,6 @@ import filteringBarCreator from '../../src/short-urls/ShortUrlsFilteringBar'; import SearchField from '../../src/utils/SearchField'; import Tag from '../../src/tags/helpers/Tag'; import { DateRangeSelector } from '../../src/utils/dates/DateRangeSelector'; -import ColorGenerator from '../../src/utils/services/ColorGenerator'; import { ReachableServer, SelectedServer } from '../../src/servers/data'; import { TooltipToggleSwitch } from '../../src/utils/TooltipToggleSwitch'; import { OrderingDropdown } from '../../src/utils/OrderingDropdown'; @@ -21,7 +20,7 @@ jest.mock('react-router-dom', () => ({ describe('', () => { let wrapper: ShallowWrapper; const ExportShortUrlsBtn = () => null; - const ShortUrlsFilteringBar = filteringBarCreator(Mock.all(), ExportShortUrlsBtn); + const ShortUrlsFilteringBar = filteringBarCreator(ExportShortUrlsBtn, () => null); const navigate = jest.fn(); const handleOrderBy = jest.fn(); const now = new Date();