mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Added support for tag mode on short URLs list
This commit is contained in:
parent
1011b062ae
commit
2de0276195
7 changed files with 67 additions and 8 deletions
|
@ -86,6 +86,8 @@ export interface ShlinkDomainsResponse {
|
||||||
defaultRedirects?: ShlinkDomainRedirects; // Optional only for Shlink older than 2.10
|
defaultRedirects?: ShlinkDomainRedirects; // Optional only for Shlink older than 2.10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TagsFilteringMode = 'all' | 'any';
|
||||||
|
|
||||||
export interface ShlinkShortUrlsListParams {
|
export interface ShlinkShortUrlsListParams {
|
||||||
page?: string;
|
page?: string;
|
||||||
itemsPerPage?: number;
|
itemsPerPage?: number;
|
||||||
|
@ -94,6 +96,7 @@ export interface ShlinkShortUrlsListParams {
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
orderBy?: ShortUrlsOrder;
|
orderBy?: ShortUrlsOrder;
|
||||||
|
tagsMode?: TagsFilteringMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShlinkShortUrlsListNormalizedParams extends Omit<ShlinkShortUrlsListParams, 'orderBy'> {
|
export interface ShlinkShortUrlsListNormalizedParams extends Omit<ShlinkShortUrlsListParams, 'orderBy'> {
|
||||||
|
|
|
@ -9,15 +9,22 @@ import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
||||||
import { formatIsoDate } from '../utils/helpers/date';
|
import { formatIsoDate } from '../utils/helpers/date';
|
||||||
import ColorGenerator from '../utils/services/ColorGenerator';
|
import ColorGenerator from '../utils/services/ColorGenerator';
|
||||||
import { DateRange } from '../utils/dates/types';
|
import { DateRange } from '../utils/dates/types';
|
||||||
|
import { supportsAllTagsFiltering } from '../utils/helpers/features';
|
||||||
|
import { SelectedServer } from '../servers/data';
|
||||||
|
import { TooltipToggleSwitch } from '../utils/TooltipToggleSwitch';
|
||||||
import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks';
|
import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks';
|
||||||
import './ShortUrlsFilteringBar.scss';
|
import './ShortUrlsFilteringBar.scss';
|
||||||
|
|
||||||
export type ShortUrlsFilteringProps = RouteChildrenProps<ShortUrlListRouteParams>;
|
export type ShortUrlsFilteringProps = RouteChildrenProps<ShortUrlListRouteParams> & {
|
||||||
|
selectedServer: SelectedServer;
|
||||||
|
};
|
||||||
|
|
||||||
const dateOrNull = (date?: string) => date ? parseISO(date) : null;
|
const dateOrNull = (date?: string) => date ? parseISO(date) : null;
|
||||||
|
|
||||||
const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (props: ShortUrlsFilteringProps) => {
|
const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (
|
||||||
const [{ search, tags, startDate, endDate }, toFirstPage ] = useShortUrlsQuery(props);
|
{ selectedServer, ...rest }: ShortUrlsFilteringProps,
|
||||||
|
) => {
|
||||||
|
const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage ] = useShortUrlsQuery(rest);
|
||||||
const selectedTags = tags?.split(',') ?? [];
|
const selectedTags = tags?.split(',') ?? [];
|
||||||
const setDates = pipe(
|
const setDates = pipe(
|
||||||
({ startDate, endDate }: DateRange) => ({
|
({ startDate, endDate }: DateRange) => ({
|
||||||
|
@ -35,6 +42,11 @@ const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (props: ShortU
|
||||||
(tagsList) => tagsList.length === 0 ? undefined : tagsList.join(','),
|
(tagsList) => tagsList.length === 0 ? undefined : tagsList.join(','),
|
||||||
(tags) => toFirstPage({ tags }),
|
(tags) => toFirstPage({ tags }),
|
||||||
);
|
);
|
||||||
|
const canChangeTagsMode = supportsAllTagsFiltering(selectedServer);
|
||||||
|
const toggleTagsMode = pipe(
|
||||||
|
() => tagsMode === 'any' ? 'all' : 'any',
|
||||||
|
(tagsMode) => toFirstPage({ tagsMode }),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="short-urls-filtering-bar-container">
|
<div className="short-urls-filtering-bar-container">
|
||||||
|
@ -56,9 +68,20 @@ const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (props: ShortU
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedTags.length > 0 && (
|
{selectedTags.length > 0 && (
|
||||||
<h4 className="short-urls-filtering-bar__selected-tag mt-3">
|
<h4 className="mt-3">
|
||||||
<FontAwesomeIcon icon={tagsIcon} className="short-urls-filtering-bar__tags-icon" />
|
{canChangeTagsMode && selectedTags.length > 1 && (
|
||||||
|
<div className="float-right ml-2 mt-1">
|
||||||
|
<TooltipToggleSwitch
|
||||||
|
checked={tagsMode === 'all'}
|
||||||
|
tooltip={{ placement: 'left' }}
|
||||||
|
onChange={toggleTagsMode}
|
||||||
|
>
|
||||||
|
{tagsMode === 'all' && 'Short URLs including all tags.'}
|
||||||
|
{tagsMode !== 'all' && 'Short URLs including any tag.'}
|
||||||
|
</TooltipToggleSwitch>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FontAwesomeIcon icon={tagsIcon} className="short-urls-filtering-bar__tags-icon mr-1" />
|
||||||
{selectedTags.map((tag) =>
|
{selectedTags.map((tag) =>
|
||||||
<Tag colorGenerator={colorGenerator} key={tag} text={tag} clearable onClose={() => removeTag(tag)} />)}
|
<Tag colorGenerator={colorGenerator} key={tag} text={tag} clearable onClose={() => removeTag(tag)} />)}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
|
@ -33,7 +33,10 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, ShortUrlsFilteri
|
||||||
settings,
|
settings,
|
||||||
}: ShortUrlsListProps) => {
|
}: ShortUrlsListProps) => {
|
||||||
const serverId = getServerId(selectedServer);
|
const serverId = getServerId(selectedServer);
|
||||||
const [{ tags, search, startDate, endDate, orderBy }, toFirstPage ] = useShortUrlsQuery({ history, match, location });
|
const [
|
||||||
|
{ tags, search, startDate, endDate, orderBy, tagsMode },
|
||||||
|
toFirstPage,
|
||||||
|
] = useShortUrlsQuery({ history, match, location });
|
||||||
const [ actualOrderBy, setActualOrderBy ] = useState(
|
const [ actualOrderBy, setActualOrderBy ] = useState(
|
||||||
// This separated state handling is needed to be able to fall back to settings value, but only once when loaded
|
// This separated state handling is needed to be able to fall back to settings value, but only once when loaded
|
||||||
orderBy ?? settings.shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING,
|
orderBy ?? settings.shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING,
|
||||||
|
@ -61,8 +64,9 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, ShortUrlsFilteri
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
orderBy: actualOrderBy,
|
orderBy: actualOrderBy,
|
||||||
|
tagsMode,
|
||||||
});
|
});
|
||||||
}, [ match.params.page, search, selectedTags, startDate, endDate, actualOrderBy ]);
|
}, [ match.params.page, search, selectedTags, startDate, endDate, actualOrderBy, tagsMode ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { isEmpty, pipe } from 'ramda';
|
||||||
import { parseQuery, stringifyQuery } from '../../utils/helpers/query';
|
import { parseQuery, stringifyQuery } from '../../utils/helpers/query';
|
||||||
import { ShortUrlsOrder, ShortUrlsOrderableFields } from '../data';
|
import { ShortUrlsOrder, ShortUrlsOrderableFields } from '../data';
|
||||||
import { orderToString, stringToOrder } from '../../utils/helpers/ordering';
|
import { orderToString, stringToOrder } from '../../utils/helpers/ordering';
|
||||||
|
import { TagsFilteringMode } from '../../api/types';
|
||||||
|
|
||||||
type ServerIdRouteProps = RouteChildrenProps<{ serverId: string }>;
|
type ServerIdRouteProps = RouteChildrenProps<{ serverId: string }>;
|
||||||
type ToFirstPage = (extra: Partial<ShortUrlsFiltering>) => void;
|
type ToFirstPage = (extra: Partial<ShortUrlsFiltering>) => void;
|
||||||
|
@ -18,6 +19,7 @@ interface ShortUrlsQueryCommon {
|
||||||
search?: string;
|
search?: string;
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
tagsMode?: TagsFilteringMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShortUrlsQuery extends ShortUrlsQueryCommon {
|
interface ShortUrlsQuery extends ShortUrlsQueryCommon {
|
||||||
|
|
|
@ -51,6 +51,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator');
|
bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator');
|
||||||
|
bottle.decorator('ShortUrlsFilteringBar', connect([ 'selectedServer' ]));
|
||||||
bottle.decorator('ShortUrlsFilteringBar', withRouter);
|
bottle.decorator('ShortUrlsFilteringBar', withRouter);
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
|
24
src/utils/TooltipToggleSwitch.tsx
Normal file
24
src/utils/TooltipToggleSwitch.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { FC, useRef } from 'react';
|
||||||
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
|
import { UncontrolledTooltipProps } from 'reactstrap/lib/Tooltip';
|
||||||
|
import { BooleanControlProps } from './BooleanControl';
|
||||||
|
import ToggleSwitch from './ToggleSwitch';
|
||||||
|
|
||||||
|
export const TooltipToggleSwitch: FC<BooleanControlProps & { tooltip?: Omit<UncontrolledTooltipProps, 'target'> }> = (
|
||||||
|
{ children, tooltip = {}, ...rest },
|
||||||
|
) => {
|
||||||
|
const ref = useRef<HTMLSpanElement>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
ref={(el) => {
|
||||||
|
ref.current = el ?? undefined;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ToggleSwitch {...rest} />
|
||||||
|
</span>
|
||||||
|
<UncontrolledTooltip target={(() => ref.current) as any} {...tooltip}>{children}</UncontrolledTooltip>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -25,3 +25,5 @@ export const supportsDomainRedirects = supportsQrErrorCorrection;
|
||||||
export const supportsForwardQuery = serverMatchesVersions({ minVersion: '2.9.0' });
|
export const supportsForwardQuery = serverMatchesVersions({ minVersion: '2.9.0' });
|
||||||
|
|
||||||
export const supportsDefaultDomainRedirectsEdition = serverMatchesVersions({ minVersion: '2.10.0' });
|
export const supportsDefaultDomainRedirectsEdition = serverMatchesVersions({ minVersion: '2.10.0' });
|
||||||
|
|
||||||
|
export const supportsAllTagsFiltering = serverMatchesVersions({ minVersion: '3.0.0' });
|
||||||
|
|
Loading…
Reference in a new issue