Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: Dashboard Missing Ep+Tmdb Link Shortcuts / Collection Tag Links and Sticky Searchbar+Sidebars #1093

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/components/Collection/Filter/Criteria.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { mdiCircleEditOutline, mdiMinusCircleOutline } from '@mdi/js';
import { Icon } from '@mdi/react';

Expand Down Expand Up @@ -43,6 +44,8 @@ const ParameterList = ({ expression, value }: { expression: string, value: strin

const Criteria = ({ criteria, parameterExists, transformedParameter, type }: Props) => {
const dispatch = useDispatch();
const location = useLocation();
const navigate = useNavigate();
const [showModal, setShowModal] = useState(false);

const openModal = useEventCallback(() => {
Expand All @@ -58,8 +61,14 @@ const Criteria = ({ criteria, parameterExists, transformedParameter, type }: Pro
});

useEffect(() => {
if (parameterExists) return;
setShowModal(true);
if (parameterExists) {
navigate('/webui/collection', { replace: true, state: { isTagLink: false } });
return;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!location.state?.isTagLink) setShowModal(true);
// locate/navigate are only used to check/clear the tag link state, adding it to the deps would cause them to loop
// eslint-disable-next-line react-hooks/exhaustive-deps
natyusha marked this conversation as resolved.
Show resolved Hide resolved
}, [parameterExists]);

const Modal = useMemo(() => getModalComponent(type), [type]);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Collection/Filter/FilterSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const FilterSidebar = () => {
return (
<ShokoPanel
title="Filter"
className="ml-8 w-full"
className="sticky top-24 ml-6 !h-[calc(100vh-18rem)] w-full"
harshithmohan marked this conversation as resolved.
Show resolved Hide resolved
contentClassName="gap-y-6"
options={<Options showModal={showCriteriaModal(true)} />}
>
Expand Down
53 changes: 33 additions & 20 deletions src/components/Collection/ListViewItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import {
mdiAlertCircleOutline,
mdiCalendarMonthOutline,
Expand All @@ -17,11 +17,14 @@ import { reduce } from 'lodash';

import BackgroundImagePlaceholderDiv from '@/components/BackgroundImagePlaceholderDiv';
import { listItemSize } from '@/components/Collection/constants';
import Button from '@/components/Input/Button';
import { useSeriesTagsQuery } from '@/core/react-query/series/queries';
import { useSettingsQuery } from '@/core/react-query/settings/queries';
import { setFilterTag } from '@/core/slices/collection';
import { setGroupId } from '@/core/slices/modals/editGroup';
import { setSeriesId } from '@/core/slices/modals/editSeries';
import { dayjs, formatThousand } from '@/core/util';
import { addFilterCriteriaToStore } from '@/core/utilities/filter';
import useEventCallback from '@/hooks/useEventCallback';
import useMainPoster from '@/hooks/useMainPoster';

Expand All @@ -45,17 +48,30 @@ const renderFileSources = (sources: SeriesSizesFileSourcesType): string => {
return output.join(' | ');
};

const SeriesTag = ({ text, type }: { text: string, type: 'AniDB' | 'User' }) => (
<div
className={cx(
'text-xs font-semibold flex gap-x-2 items-center border-2 border-panel-tags rounded-lg p-2 whitespace-nowrap capitalize',
type === 'User' ? 'text-panel-text-important' : 'text-panel-text-primary',
)}
>
<Icon path={mdiTagTextOutline} size="1rem" />
<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(() => {
navigate('/webui/collection', { state: { isTagLink: true } });
addFilterCriteriaToStore('HasTag').then(() => {
dispatch(setFilterTag({ HasTag: [{ Name: text, isExcluded: false }] }));
}).catch(console.error);
natyusha marked this conversation as resolved.
Show resolved Hide resolved
});

return (
<Button
className={cx(
'pointer-events-auto text-xs font-semibold flex gap-x-2 items-center border-2 border-panel-tags rounded-lg p-2 whitespace-nowrap capitalize cursor-pointer',
type === 'User' ? 'text-panel-text-important' : 'text-panel-text-primary',
)}
onClick={handleClick}
tooltip="Filter Tag"
natyusha marked this conversation as resolved.
Show resolved Hide resolved
>
<Icon path={mdiTagTextOutline} size="1rem" />
<span className="text-panel-text">{text}</span>
</Button>
);
});

type Props = {
item: CollectionGroupType | SeriesType;
Expand Down Expand Up @@ -152,21 +168,18 @@ const ListViewItem = ({ groupExtras, isSeries, isSidebarOpen, item }: Props) =>
<Link to={viewRouteLink()}>
<BackgroundImagePlaceholderDiv
image={poster}
className="group h-[12.5625rem] w-[8.625rem] shrink-0 rounded-lg drop-shadow-md"
className="group h-[13.438rem] w-[9.25rem] shrink-0 rounded-lg drop-shadow-md"
hidePlaceholderOnHover
zoomOnHover
>
<div className="pointer-events-none z-10 flex h-full bg-panel-background-poster-overlay p-3 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
<div
<Button
className="pointer-events-auto h-fit"
onClick={(isSeries || item.Size === 1) ? editSeriesModalCallback : editGroupModalCallback}
tooltip="Edit Series"
>
<Icon
path={mdiPencilCircleOutline}
size="2rem"
className="text-panel-icon"
/>
</div>
<Icon path={mdiPencilCircleOutline} size="2rem" />
</Button>
</div>
{showGroupIndicator && groupCount > 1 && (
<div className="absolute bottom-0 left-0 flex w-full justify-center rounded-bl-md bg-panel-background-overlay py-1.5 text-sm font-semibold opacity-100 transition-opacity group-hover:opacity-0">
Expand Down
12 changes: 5 additions & 7 deletions src/components/Collection/PosterViewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Icon } from '@mdi/react';
import { reduce } from 'lodash';

import BackgroundImagePlaceholderDiv from '@/components/BackgroundImagePlaceholderDiv';
import Button from '@/components/Input/Button';
import { useSettingsQuery } from '@/core/react-query/settings/queries';
import { setGroupId } from '@/core/slices/modals/editGroup';
import { setSeriesId } from '@/core/slices/modals/editSeries';
Expand Down Expand Up @@ -84,16 +85,13 @@ const PosterViewItem = ({ isSeries = false, item }: Props) => {
</div>
)}
<div className="pointer-events-none z-10 flex h-full bg-panel-background-poster-overlay p-3 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
<div
<Button
className="pointer-events-auto h-fit"
onClick={(isSeries || item.Size === 1) ? editSeriesModalCallback : editGroupModalCallback}
tooltip="Edit Series"
>
<Icon
path={mdiPencilCircleOutline}
size="2rem"
className="text-panel-icon"
/>
</div>
<Icon path={mdiPencilCircleOutline} size="2rem" />
</Button>
</div>
{showGroupIndicator && !isSeries && groupCount > 1 && (
<div className="absolute bottom-4 left-3 flex w-[90%] justify-center rounded-lg bg-panel-background-overlay py-2 text-sm font-semibold text-panel-text opacity-100 transition-opacity group-hover:opacity-0">
Expand Down
11 changes: 6 additions & 5 deletions src/components/Collection/SeriesTopPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import BackgroundImagePlaceholderDiv from '@/components/BackgroundImagePlacehold
import CleanDescription from '@/components/Collection/CleanDescription';
import SeriesInfo from '@/components/Collection/SeriesInfo';
import SeriesUserStats from '@/components/Collection/SeriesUserStats';
import Button from '@/components/Input/Button';
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 { setFilterTag } from '@/core/slices/collection';
import { addFilterCriteriaToStore } from '@/core/utilities/filter';
import useEventCallback from '@/hooks/useEventCallback';

Expand All @@ -25,24 +26,24 @@ const SeriesTag = React.memo(({ text, type }: { text: string, type: 'User' | 'An
const dispatch = useDispatch();
const navigate = useNavigate();
const handleClick = useEventCallback(() => {
dispatch(resetFilter());
natyusha marked this conversation as resolved.
Show resolved Hide resolved
addFilterCriteriaToStore('HasTag').then(() => {
dispatch(setFilterTag({ HasTag: [{ Name: text, isExcluded: false }] }));
navigate('/webui/collection');
navigate('/webui/collection', { state: { isTagLink: true } });
natyusha marked this conversation as resolved.
Show resolved Hide resolved
}).catch(console.error);
});

return (
<div
<Button
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}
tooltip="Filter Tag"
natyusha marked this conversation as resolved.
Show resolved Hide resolved
>
<Icon path={mdiTagTextOutline} size="1.25rem" />
<span className="text-panel-text">{text}</span>
</div>
</Button>
);
});

Expand Down
34 changes: 18 additions & 16 deletions src/components/Collection/TimelineSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mdiLoading } from '@mdi/js';
import { Icon } from '@mdi/react';

import BackgroundImagePlaceholderDiv from '@/components/BackgroundImagePlaceholderDiv';
import ShokoPanel from '@/components/Panels/ShokoPanel';
import { SeriesTypeEnum } from '@/core/types/api/series';
import { dayjs } from '@/core/util';
import useMainPoster from '@/hooks/useMainPoster';
Expand Down Expand Up @@ -37,22 +38,23 @@ const TimelineItem = ({ series }: { series: SeriesType }) => {
};

const TimelineSidebar = ({ isFetching, series }: { isFetching: boolean, series: SeriesType[] }) => (
<div className="flex min-h-full overflow-hidden transition-all">
<div className="ml-8 flex w-[26.125rem] grow flex-col gap-y-6 rounded-lg border border-panel-border bg-panel-background p-6">
<div className="text-xl font-semibold">Timeline</div>
{isFetching
? (
<div className="flex grow items-center justify-center text-panel-text-primary">
<Icon path={mdiLoading} size={3} spin />
</div>
)
: (
<div className="flex flex-col gap-y-3">
{series.map(item => <TimelineItem series={item} key={item.IDs.ID} />)}
</div>
)}
</div>
</div>
<ShokoPanel
title="Timeline"
className="sticky top-24 ml-6 !h-[calc(100vh-18rem)] w-[26.5rem]"
contentClassName="gap-y-6"
>
{isFetching
? (
<div className="flex grow items-center justify-center text-panel-text-primary">
<Icon path={mdiLoading} size={3} spin />
</div>
)
: (
<div className="flex flex-col gap-y-3">
{series.map(item => <TimelineItem series={item} key={item.IDs.ID} />)}
</div>
)}
</ShokoPanel>
natyusha marked this conversation as resolved.
Show resolved Hide resolved
);

export default TimelineSidebar;
2 changes: 1 addition & 1 deletion src/components/Layout/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function TopNav() {
layoutEditMode && 'opacity-65 pointer-events-none',
)}
>
<div className="mx-auto flex w-full max-w-[120rem] items-center justify-between p-6">
<div className="mx-auto flex w-full max-w-[120rem] items-center justify-between px-6 py-2">
<Link to="/webui/dashboard" className="flex items-center gap-x-3">
<ShokoIcon className="w-20" />
<span className="mt-1 text-2xl font-semibold text-header-text">Shoko</span>
Expand Down
7 changes: 6 additions & 1 deletion src/components/Utilities/Unrecognized/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { mdiChevronRight } from '@mdi/js';
import { Icon } from '@mdi/react';

const TabButton = ({ id, name }: { id: string, name: string }) => (
<NavLink to={`../unrecognized/${id}`} className={({ isActive }) => (isActive ? 'text-panel-text-primary' : '')}>
<NavLink
to={`../unrecognized/${id}`}
className={(
{ isActive },
) => (isActive ? 'text-panel-text-primary' : 'hover:text-panel-text-primary transition-colors')}
>
{name}
</NavLink>
);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/collection/Collection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function Collection() {

return (
<div className="flex grow flex-col gap-y-6">
<div className="flex items-center justify-between rounded-lg border border-panel-border bg-panel-background p-6">
<div className="sticky -top-6 z-10 flex items-center justify-between rounded-lg border border-panel-border bg-panel-background p-6">
<CollectionTitle
// eslint-disable-next-line no-nested-ternary
count={(total === 0 && isFetching) ? -1 : (isSeries ? total : groupsTotal)}
Expand Down Expand Up @@ -232,7 +232,7 @@ function Collection() {
className={cx(
'flex items-start transition-all',
(!isSeries && showFilterSidebar)
? 'w-[28.84rem] opacity-100 overflow-auto '
? 'w-[28rem] opacity-100 '
natyusha marked this conversation as resolved.
Show resolved Hide resolved
: 'w-0 opacity-0 overflow-hidden ',
)}
>
Expand Down
62 changes: 50 additions & 12 deletions src/pages/dashboard/panels/CollectionStats.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import prettyBytes from 'pretty-bytes';

import ShokoPanel from '@/components/Panels/ShokoPanel';
import { useDashbordStatsQuery } from '@/core/react-query/dashboard/queries';
import { resetFilter } from '@/core/slices/collection';
import { addFilterCriteriaToStore } from '@/core/utilities/filter';
import useEventCallback from '@/hooks/useEventCallback';

import type { RootState } from '@/core/store';

const Item = (
{ link, title, value = 0 }: { title: string, value?: string | number, link?: string },
) => (
<div className="flex">
<div className="grow">
{title}
{ filter, link, title, value = 0 }: { title: string, value?: string | number, link?: string, filter?: string },
) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const handleMissingFilter = useEventCallback((filterName: string) => {
dispatch(resetFilter());
addFilterCriteriaToStore(filterName).then(() => {
navigate('/webui/collection');
}).catch(console.error);
});

return (
/* eslint-disable no-nested-ternary */
<div className="flex">
<div className="grow">
{title}
</div>
{link
? <Link to={link} className="font-semibold text-panel-text-primary">{value}</Link>
natyusha marked this conversation as resolved.
Show resolved Hide resolved
: filter
? (
<div
className="cursor-pointer font-semibold text-panel-text-primary"
onClick={() => handleMissingFilter(filter)}
>
{value}
</div>
)
: <div>{value}</div>}
</div>
{link ? <Link to={link} className="font-semibold text-panel-text-primary">{value}</Link> : <div>{value}</div>}
</div>
);
/* eslint-enable no-nested-ternary */
natyusha marked this conversation as resolved.
Show resolved Hide resolved
);
};

function CollectionStats() {
const layoutEditMode = useSelector((state: RootState) => state.mainpage.layoutEditMode);
Expand Down Expand Up @@ -53,13 +80,24 @@ function CollectionStats() {
];

const childrenThird = [
<Item key="missing-links" title="Missing TMDB Links" value={statsQuery.data?.SeriesWithMissingLinks} />,
<Item
key="missing-links"
title="Missing TMDB Links"
value={statsQuery.data?.SeriesWithMissingLinks}
filter="MissingTmdbLink"
/>,
<Item
key="missing-episodes-collecting"
title="Missing Episodes (Collecting)"
value={statsQuery.data?.MissingEpisodesCollecting}
filter="HasMissingEpisodesCollecting"
/>,
<Item
key="missing-episodes"
title="Missing Episodes (Total)"
value={statsQuery.data?.MissingEpisodes}
filter="HasMissingEpisodes"
/>,
<Item key="missing-episodes" title="Missing Episodes (Total)" value={statsQuery.data?.MissingEpisodes} />,
];

return (
Expand Down