Skip to content

Commit

Permalink
Fix season in series info, allow filtering with season and tag, renam…
Browse files Browse the repository at this point in the history
…er QoL (#1008)

* Fix season filter from series page

* Allow tag filtering from series

* Renamer: Show success status if there are no changes
  • Loading branch information
harshithmohan authored Aug 22, 2024
1 parent d16c1b3 commit 3098e93
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 69 deletions.
9 changes: 8 additions & 1 deletion src/components/Collection/CollectionTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import cx from 'classnames';

type Props = {
count: number;
filterActive: boolean;
filterOrGroup?: string;
searchQuery: string;
};

const CollectionTitle = memo(({ count, filterOrGroup, searchQuery }: Props) => (
const CollectionTitle = memo(({ count, filterActive, filterOrGroup, searchQuery }: Props) => (
<div className="flex items-center gap-x-2 text-xl font-semibold">
<Link to="/webui/collection" className={cx(filterOrGroup ? 'text-panel-text-primary' : 'pointer-events-none')}>
Entire Collection
Expand All @@ -21,6 +22,12 @@ const CollectionTitle = memo(({ count, filterOrGroup, searchQuery }: Props) => (
{filterOrGroup}
</>
)}
{!filterOrGroup && filterActive && (
<>
<Icon path={mdiChevronRight} size={1} />
Filtered
</>
)}
{searchQuery && (
<>
<Icon path={mdiChevronRight} size={1} />
Expand Down
18 changes: 7 additions & 11 deletions src/components/Collection/Filter/FilterSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { type ReactNode, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { mdiFilterPlusOutline } from '@mdi/js';
import { forEach, keys, map, values } from 'lodash';
import { keys, map, values } from 'lodash';

import AddCriteriaModal from '@/components/Collection/Filter/AddCriteriaModal';
import DefaultCriteria from '@/components/Collection/Filter/DefaultCriteria';
Expand All @@ -10,13 +10,13 @@ import TagCriteria from '@/components/Collection/Filter/TagCriteria';
import Button from '@/components/Input/Button';
import IconButton from '@/components/Input/IconButton';
import ShokoPanel from '@/components/Panels/ShokoPanel';
import { buildSidebarFilter } from '@/core/buildFilter';
import {
removeFilterCriteria,
resetActiveFilter,
resetFilter,
selectActiveCriteriaWithValues,
setActiveFilter,
} from '@/core/slices/collection';
import { buildSidebarFilter } from '@/core/utilities/filter';
import useEventCallback from '@/hooks/useEventCallback';

import type { RootState } from '@/core/store';
Expand All @@ -42,7 +42,6 @@ type OptionsProps = {
const Options = ({ showModal }: OptionsProps) => <OptionButton onClick={showModal} icon={mdiFilterPlusOutline} />;

const FilterSidebar = () => {
const activeFilter = useSelector((state: RootState) => state.collection.activeFilter);
const [criteriaModal, setCriteriaModal] = useState(false);
const dispatch = useDispatch();
const selectedCriteria = useSelector((state: RootState) => state.collection.filterCriteria);
Expand All @@ -57,18 +56,16 @@ const FilterSidebar = () => {
setCriteriaModal(state);
};

const resetFilter = useEventCallback(() => {
forEach(selectedCriteria, (criteria) => {
dispatch(removeFilterCriteria(criteria));
});
const handleResetFilter = useEventCallback(() => {
dispatch(resetFilter());
});

useEffect(() => {
const count = keys(selectedCriteria).length;
if (count !== keys(activeCriteriaWithValues).length) return;
if (count > 0) applyFilter();
else dispatch(resetActiveFilter());
}, [activeCriteriaWithValues, applyFilter, dispatch, resetFilter, selectedCriteria]);
}, [activeCriteriaWithValues, applyFilter, dispatch, selectedCriteria]);

return (
<ShokoPanel
Expand All @@ -84,8 +81,7 @@ const FilterSidebar = () => {
<Button
buttonType="danger"
className="px-4 py-3"
onClick={resetFilter}
disabled={!activeFilter}
onClick={handleResetFilter}
>
Clear filter
</Button>
Expand Down
5 changes: 3 additions & 2 deletions src/components/Collection/Group/EditGroupModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ const renderTab = (activeTab: string, groupId: number) => {
const EditGroupModal = () => {
const dispatch = useDispatch();

const groupId = useSelector((state: RootState) => state.modals.editGroup.groupId);

const onClose = useEventCallback(() => {
if (groupId === -1) return;
dispatch(setGroupId(-1));
});

useEffect(() => onClose, [onClose]);

const groupId = useSelector((state: RootState) => state.modals.editGroup.groupId);

const [activeTab, setActiveTab] = useState<keyof typeof tabs>('name');

return (
Expand Down
5 changes: 3 additions & 2 deletions src/components/Collection/Series/EditSeriesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ const renderTab = (activeTab: string, seriesId = -1) => {
const EditSeriesModal = () => {
const dispatch = useDispatch();

const seriesId = useSelector((state: RootState) => state.modals.editSeries.seriesId);

const onClose = useEventCallback(() => {
if (seriesId === -1) return;
dispatch(setSeriesId(-1));
});

useEffect(() => onClose, [onClose]);

const seriesId = useSelector((state: RootState) => state.modals.editSeries.seriesId);

const [activeTab, setActiveTab] = useState('name');

return (
Expand Down
41 changes: 28 additions & 13 deletions src/components/Collection/SeriesInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React, { useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import cx from 'classnames';
import { toNumber } from 'lodash';

import { useSeriesOverviewQuery } from '@/core/react-query/webui/queries';
import { resetFilter, setFilterValues } from '@/core/slices/collection';
import { convertTimeSpanToMs, dayjs } from '@/core/util';
import { addFilterCriteriaToStore } from '@/core/utilities/filter';
import useEventCallback from '@/hooks/useEventCallback';

import type { SeriesType } from '@/core/types/api/series';
import type { WebuiSeriesDetailsType } from '@/core/types/api/webui';
Expand All @@ -16,6 +21,9 @@ type SeriesInfoProps = {
const SeriesInfo = ({ series }: SeriesInfoProps) => {
const { seriesId } = useParams();

const dispatch = useDispatch();
const navigate = useNavigate();

// Series Data;
const seriesOverviewQuery = useSeriesOverviewQuery(toNumber(seriesId!), !!seriesId);
const overview = seriesOverviewQuery?.data ?? {} as WebuiSeriesDetailsType;
Expand Down Expand Up @@ -44,6 +52,16 @@ const SeriesInfo = ({ series }: SeriesInfoProps) => {
return 'Finished';
}, [startDate, endDate]);

const handleSeasonFilter = useEventCallback(() => {
if (!overview.FirstAirSeason) return;
dispatch(resetFilter());
const [season, year] = overview.FirstAirSeason.split(' ');
addFilterCriteriaToStore('InSeason').then(() => {
dispatch(setFilterValues({ InSeason: [`${year}: ${season}`] }));
navigate('/webui/collection');
}).catch(console.error);
});

if (!seriesId) return null;

return (
Expand Down Expand Up @@ -102,24 +120,21 @@ const SeriesInfo = ({ series }: SeriesInfoProps) => {
<div className="truncate">
&nbsp;
{overview.RuntimeLength
? `${dayjs.duration(convertTimeSpanToMs(overview.RuntimeLength)).asMinutes()} Min/Ep`
? `${dayjs.duration(convertTimeSpanToMs(overview.RuntimeLength)).asMinutes()} Mins/Episode`
: '--'}
</div>
</div>
<div className="flex justify-between capitalize">
<div className="font-semibold">Season</div>
<div className="truncate">
<div
className={cx(
'truncate',
overview.FirstAirSeason && 'cursor-pointer font-semibold text-panel-text-primary',
)}
onClick={handleSeasonFilter}
>
&nbsp;
{overview?.FirstAirSeason
? (
<Link
className="font-semibold text-panel-text-primary"
to={`/webui/collection/filter/${overview.FirstAirSeason.IDs.ID}`}
>
{overview.FirstAirSeason.Name}
</Link>
)
: '--'}
{overview?.FirstAirSeason ?? '--'}
</div>
</div>
<div className="flex justify-between capitalize">
Expand Down
40 changes: 29 additions & 11 deletions src/components/Collection/SeriesTopPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router';
import { useNavigate } from 'react-router-dom';
import { mdiTagTextOutline } from '@mdi/js';
import { Icon } from '@mdi/react';
import cx from 'classnames';
Expand All @@ -12,6 +14,9 @@ import SeriesUserStats from '@/components/Collection/SeriesUserStats';
import ShokoPanel from '@/components/Panels/ShokoPanel';
import { useSeriesImagesQuery, useSeriesTagsQuery } from '@/core/react-query/series/queries';
import { useSettingsQuery } from '@/core/react-query/settings/queries';
import { resetFilter, setFilterTag } from '@/core/slices/collection';
import { addFilterCriteriaToStore } from '@/core/utilities/filter';
import useEventCallback from '@/hooks/useEventCallback';

import type { ImageType } from '@/core/types/api/common';
import type { SeriesType } from '@/core/types/api/series';
Expand All @@ -20,17 +25,30 @@ type SeriesSidePanelProps = {
series: SeriesType;
};

const SeriesTag = ({ text, type }) => (
<div
className={cx(
'text-sm font-semibold flex gap-x-3 items-center border-2 border-panel-tags rounded-lg py-2 px-3 whitespace-nowrap capitalize h-fit',
type === 'User' ? 'text-panel-icon-important' : 'text-panel-icon-action',
)}
>
<Icon path={mdiTagTextOutline} size="1.25rem" />
<span className="text-panel-text">{text}</span>
</div>
);
const SeriesTag = React.memo(({ text, type }: { text: string, type: 'User' | 'AniDB' }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const handleClick = useEventCallback(() => {
dispatch(resetFilter());
addFilterCriteriaToStore('HasTag').then(() => {
dispatch(setFilterTag({ HasTag: [{ Name: text, isExcluded: false }] }));
navigate('/webui/collection');
}).catch(console.error);
});

return (
<div
className={cx(
'text-sm font-semibold flex gap-x-3 items-center border-2 border-panel-tags rounded-lg py-2 px-3 whitespace-nowrap capitalize h-fit cursor-pointer',
type === 'User' ? 'text-panel-icon-important' : 'text-panel-icon-action',
)}
onClick={handleClick}
>
<Icon path={mdiTagTextOutline} size="1.25rem" />
<span className="text-panel-text">{text}</span>
</div>
);
});

const SeriesTopPanel = React.memo(({ series }: SeriesSidePanelProps) => {
const { WebUI_Settings: { collection: { image: { showRandomPoster } } } } = useSettingsQuery().data;
Expand Down
32 changes: 27 additions & 5 deletions src/components/Collection/TitleOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { memo } from 'react';
import React from 'react';
import type { Dispatch, SetStateAction } from 'react';
import { useParams } from 'react-router';
import {
mdiCogOutline,
mdiFilterMenuOutline,
Expand All @@ -25,10 +26,24 @@ type Props = {
toggleMode: () => void;
};

const OptionButton = memo(
const OptionButton = React.memo(
(
{ icon, onClick, tooltip }: { icon: string, onClick: React.MouseEventHandler<HTMLDivElement>, tooltip?: string },
) => <IconButton icon={icon} buttonType="secondary" buttonSize="normal" onClick={onClick} tooltip={tooltip} />,
{ disabled, icon, onClick, tooltip }: {
disabled?: boolean;
icon: string;
onClick: React.MouseEventHandler<HTMLDivElement>;
tooltip?: string;
},
) => (
<IconButton
disabled={disabled}
icon={icon}
buttonType="secondary"
buttonSize="normal"
onClick={onClick}
tooltip={tooltip}
/>
),
);

const TitleOptions = (props: Props) => {
Expand All @@ -42,6 +57,8 @@ const TitleOptions = (props: Props) => {
toggleMode,
} = props;

const { filterId } = useParams();

const [showFilterModal, toggleFilterModal] = useToggle(false);
const [showDisplaySettingsModal, toggleDisplaySettingsModal] = useToggle(false);

Expand All @@ -59,7 +76,12 @@ const TitleOptions = (props: Props) => {
{!isSeries && (
<>
<OptionButton onClick={toggleFilterModal} icon={mdiFilterMenuOutline} tooltip="Filter Presets" />
<OptionButton onClick={toggleFilterSidebar} icon={mdiFilterOutline} tooltip="Filter" />
<OptionButton
onClick={toggleFilterSidebar}
icon={mdiFilterOutline}
tooltip={!filterId ? 'Filter' : ''}
disabled={!!filterId}
/>
</>
)}
<OptionButton
Expand Down
5 changes: 3 additions & 2 deletions src/components/Input/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { ButtonType, SizeType } from '@/components/Input/Button.utils';

type IconButtonProps = {
icon: string;

disabled?: boolean;
className?: string;
onClick: React.MouseEventHandler<HTMLDivElement | HTMLButtonElement>;
buttonType: ButtonType;
Expand All @@ -18,7 +18,7 @@ type IconButtonProps = {
};

const IconButton = (
{ buttonSize = 'normal', buttonType = 'secondary', className, icon, onClick, tooltip }: IconButtonProps,
{ buttonSize = 'normal', buttonType = 'secondary', className, disabled, icon, onClick, tooltip }: IconButtonProps,
) => (
<Button
className={cx(
Expand All @@ -29,6 +29,7 @@ const IconButton = (
)}
onClick={onClick}
tooltip={tooltip}
disabled={disabled}
>
<Icon path={icon} size={1} />
</Button>
Expand Down
1 change: 1 addition & 0 deletions src/core/react-query/filter/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const useFilterExpressionsQuery = (enabled = true) =>
queryFn: () => axios.get('Filter/Expressions'),
select: transformFilterExpressions,
enabled,
staleTime: Infinity, // This query does not return different results each time, so we can cache it forever.
});

export const useFilteredSeriesInfiniteQuery = (
Expand Down
Loading

0 comments on commit 3098e93

Please sign in to comment.