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 "Recommended Anime" panel and fix other dashboard panels #433

Merged
merged 16 commits into from
Nov 12, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 1 addition & 2 deletions css/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,5 @@ body {
}

.react-grid-item > .react-resizable-handle::after {
border-bottom: 2px solid theme('colors.highlight-1') !important;
border-right: 2px solid theme('colors.highlight-1') !important;
border: 0;
}
Binary file removed public/logo.png
Binary file not shown.
29 changes: 0 additions & 29 deletions public/logo.svg

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/Layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function Sidebar() {
<span className="text-lg">{text}</span>
</Link>
{(key === 'dashboard') && (
<div onClick={() => dispatch(setLayoutEditMode(true))} className={cx('cursor-pointer transition-opacity', layoutEditMode && 'text-highlight-1', pathname !== '/webui/dashboard' && 'opacity-50 pointer-events-none')}>
<div onClick={() => dispatch(setLayoutEditMode(true))} className={cx('cursor-pointer transition-opacity', (pathname !== '/webui/dashboard' || layoutEditMode) && 'opacity-0 pointer-events-none')}>
<Icon path={mdiCircleEditOutline} size={1} />
</div>
)}
Expand Down
8 changes: 4 additions & 4 deletions src/components/Panels/ShokoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ type Props = {
options?: any;
className?: string;
isFetching?: boolean;
disableClick?: boolean;
editMode?: boolean;
};

const ShokoPanel = ({ className, title, options, children, isFetching, disableClick }: Props) => (
<div className={cx(['flex', 'flex-col', 'overflow-hidden', 'h-full'], disableClick && 'pointer-events-none', className)}>
const ShokoPanel = ({ className, title, options, children, isFetching, editMode }: Props) => (
<div className={cx('flex flex-col overflow-hidden h-full transition-colors border border-dashed', editMode ? 'pointer-events-none border-highlight-1' : 'border-transparent', className)}>
<div className="flex justify-between items-center mr-2">
<span className="flex font-semibold text-base">{title}</span>
<div
Expand All @@ -25,7 +25,7 @@ const ShokoPanel = ({ className, title, options, children, isFetching, disableCl
</div>
</div>
<span className="bg-background-border my-4 h-0.5 flex-shrink-0" />
<div className="flex grow flex-col mr-2 font-open-sans overflow-y-auto">
<div className="flex grow flex-col mr-2 font-open-sans overflow-y-auto shoko-scrollbar">
{isFetching ? <div className="flex grow justify-center items-center"><Icon path={mdiLoading} spin size={1} /></div> : children}
</div>
</div>
Expand Down
9 changes: 8 additions & 1 deletion src/core/rtkQuery/seriesApi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { splitV3Api } from './splitV3Api';

import type { SeriesAniDBSearchResult, SeriesType } from '../types/api/series';
import type { SeriesAniDBSearchResult, SeriesType, SeriesRecommendedType } from '../types/api/series';
import type { ListResultType, PaginationType } from '../types/api';
import { EpisodeType } from '../types/api/episode';

Expand Down Expand Up @@ -41,6 +41,12 @@ const seriesApi = splitV3Api.injectEndpoints({
query: params => ({ url: `Series/AniDB/${params.anidbID}` }),
transformResponse: (response: any) => response.List,
}),

// Gets anidb recommendation for the user
getAniDBRecommendedAnime: build.query<Array<SeriesRecommendedType>, PaginationType>({
query: params => ({ url: 'Series/AniDB/RecommendedForYou', params: { ...params, showAll: true } }),
transformResponse: (response: any) => response.List,
}),
}),
});

Expand All @@ -51,4 +57,5 @@ export const {
useLazyGetSeriesEpisodesQuery,
useRefreshAnidbSeriesMutation,
useLazyGetSeriesAniDBQuery,
useGetAniDBRecommendedAnimeQuery,
} = seriesApi;
10 changes: 8 additions & 2 deletions src/core/rtkQuery/settingsApi.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import jsonpatch from 'fast-json-patch';
import semver from 'semver';

import { splitV3Api } from './splitV3Api';

import type { SettingsServerType, SettingsType } from '../types/api/settings';
import jsonpatch from 'fast-json-patch';
import { SettingsAnidbLoginType } from '../types/api/settings';
import { initialSettings } from '../../pages/settings/SettingsPage';
import { uiVersion } from '../util';

const settingsApi = splitV3Api.injectEndpoints({
endpoints: build => ({
Expand All @@ -13,7 +16,10 @@ const settingsApi = splitV3Api.injectEndpoints({
transformResponse: (response: SettingsServerType) => {
let webuiSettings = JSON.parse(response.WebUI_Settings === '' ? '{}' : response.WebUI_Settings);
const settingsRevision = webuiSettings.settingsRevision ?? 0;
if (settingsRevision !== 2) webuiSettings = { ...initialSettings.WebUI_Settings, settingsRevision: 2 }; // TO-DO: Move the settings revision number somewhere else
const newSettingsRevision = 3;
if (settingsRevision !== newSettingsRevision) webuiSettings = { ...initialSettings.WebUI_Settings, settingsRevision: newSettingsRevision }; // TO-DO: Move the settings revision number somewhere else
else webuiSettings = Object.assign({}, initialSettings.WebUI_Settings, webuiSettings);
if (semver.prerelease(uiVersion()) !== null) webuiSettings.updateChannel = 'unstable';
return { ...response, WebUI_Settings: webuiSettings };
},
providesTags: ['Settings'],
Expand Down
18 changes: 17 additions & 1 deletion src/core/slices/mainpage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,23 @@ const mainpageSlice = createSlice({
name: 'mainpage',
initialState: {
fetched: {},
queueStatus: {} as QueueStatusType,
queueStatus: {
HasherQueueState: {
state: 17,
description: '',
},
GeneralQueueState: {
state: 17,
description: '',
},
ImageQueueState: {
state: 17,
description: '',
},
HasherQueueCount: 0,
GeneralQueueCount: 0,
ImageQueueCount: 0,
},
banStatus: {
http: {
updateType: 2,
Expand Down
5 changes: 5 additions & 0 deletions src/core/types/api/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,8 @@ export type SeriesSizesEpisodeCountsType = {
Parodies: number;
Others: number;
};

export type SeriesRecommendedType = {
Anime: SeriesAniDBType,
SimilarTo: number,
};
13 changes: 13 additions & 0 deletions src/pages/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Responsive, WidthProvider } from 'react-grid-layout';
import { Icon } from '@mdi/react';
import { mdiMenuDown } from '@mdi/js';

import { RootState } from '../../core/store';
import toast from '../../components/Toast';
Expand All @@ -14,6 +16,7 @@ import ImportFolders from './panels/ImportFolders';
import ContinueWatching from './panels/ContinueWatching';
import NextUp from './panels/NextUp';
import UpcomingAnime from './panels/UpcomingAnime';
import RecommendedAnime from './panels/RecommendedAnime';
import Button from '../../components/Input/Button';

import { setLayoutEditMode } from '../../core/slices/mainpage';
Expand Down Expand Up @@ -84,6 +87,12 @@ function DashboardPage() {
}
}, [layoutEditMode, currentLayout]);

const renderResizeHandle = () => (
<div className="react-resizable-handle right-0 bottom-0 cursor-nwse-resize">
<Icon path={mdiMenuDown} size={1.5} className="text-highlight-1" rotate={-45}/>
</div>
);

return (
<ResponsiveGridLayout
layouts={currentLayout}
Expand All @@ -97,6 +106,7 @@ function DashboardPage() {
onLayoutChange={(_layout, layouts) => setCurrentLayout(layouts)}
isDraggable={layoutEditMode}
isResizable={layoutEditMode}
resizeHandle={renderResizeHandle()}
>
<div key="collectionBreakdown">
<CollectionBreakdown />
Expand Down Expand Up @@ -128,6 +138,9 @@ function DashboardPage() {
<div key="upcomingAnime">
<UpcomingAnime />
</div>
<div key="recommendedAnime">
<RecommendedAnime />
</div>
</ResponsiveGridLayout>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard/panels/CollectionBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function CollectionBreakdown() {
];

return (
<ShokoPanel title="Collection Breakdown" isFetching={stats.isLoading} disableClick={layoutEditMode}>
<ShokoPanel title="Collection Breakdown" isFetching={stats.isLoading} editMode={layoutEditMode}>
<div className="flex flex-col leading-5">
{childrenFirst}
</div>
Expand Down
9 changes: 7 additions & 2 deletions src/pages/dashboard/panels/ContinueWatching.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ const ContinueWatching = () => {
const items = useGetDashboardContinueWatchingEpisodesQuery({ pageSize: 20 });

return (
<ShokoPanel title="Continue Watching" isFetching={items.isLoading} disableClick={layoutEditMode}>
<div className="flex flex-nowrap overflow-x-auto shoko-scrollbar h-90">{items.data?.map(item => <EpisodeDetails episode={item} key={item.IDs.ID} />)}</div>
<ShokoPanel title="Continue Watching" isFetching={items.isLoading} editMode={layoutEditMode}>
<div className="flex shoko-scrollbar">
{(items.data?.length ?? 0) > 0
? items.data?.map(item => <EpisodeDetails episode={item} key={item.IDs.ID} />)
: <div className="flex justify-center font-semibold mt-4 w-full">No episodes in-progress to continue watching!</div>
}
</div>
</ShokoPanel>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard/panels/ImportBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function ImportBreakdown() {
const items = useGetFileUnrecognizedQuery({ pageSize: 0 });

return (
<ShokoPanel title="Unrecognized" isFetching={items.isLoading} disableClick={layoutEditMode}>
<ShokoPanel title="Unrecognized" isFetching={items.isLoading} editMode={layoutEditMode}>
<UnrecognizedTab />
</ShokoPanel>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ function UnrecognizedAvdumpedItem(props: Props) {

return (<div className="flex flex-col grow">
<div>
<span className={cx({ 'font-semibold': activeTab === 'date', 'text-highlight-1': activeTab === 'date' })} onClick={() => { setActiveTab('date');}}>{moment(item.Created).format('yyyy-MM-DD')} / {moment(item.Created).format('hh:mm A')}</span>
<span className={cx({ 'font-semibold': activeTab === 'date', 'text-highlight-1': activeTab === 'date' }, 'cursor-pointer')} onClick={() => { setActiveTab('date');}}>{moment(item.Created).format('yyyy-MM-DD')} / {moment(item.Created).format('hh:mm A')}</span>
<span className="mx-2">|</span>
<span className={cx({ 'font-semibold': activeTab === 'ed2k', 'text-highlight-1': activeTab === 'ed2k' })} onClick={() => { setActiveTab('ed2k');}}>ED2KHash</span>
<span className={cx({ 'font-semibold': activeTab === 'ed2k', 'text-highlight-1': activeTab === 'ed2k' }, 'cursor-pointer')} onClick={() => { setActiveTab('ed2k');}}>ED2KHash</span>
</div>
{activeTab === 'date' && <span className="flex break-words">{item.Locations?.[0].RelativePath ?? ''}</span>}
{activeTab === 'ed2k' && <span className="flex break-words">{hash}</span>}
Expand Down
57 changes: 23 additions & 34 deletions src/pages/dashboard/panels/ImportBreakdownTabs/UnrecognizedTab.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,54 @@
import React, { useState } from 'react';
// import { useDispatch, useSelector } from 'react-redux';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { forEach, orderBy } from 'lodash';
import moment from 'moment';
import CopyToClipboard from 'react-copy-to-clipboard';
import { Icon } from '@mdi/react';
import { mdiHelpCircleOutline, mdiClipboardTextMultipleOutline } from '@mdi/js';

import toast from '../../../../components/Toast';
// import { RootState } from '../../../../core/store';
import { RootState } from '../../../../core/store';
import Button from '../../../../components/Input/Button';
import UnrecognizedAvdumpedItem from './UnrecognizedAvdumpedItem';

import { setItem as setAvdumpItem } from '../../../../core/slices/utilities/avdump';
import toast from '../../../../components/Toast';
import type { FileType } from '../../../../core/types/api/file';
import { mdiHelpCircleOutline, mdiClipboardTextMultipleOutline } from '@mdi/js';
import { Icon } from '@mdi/react';
// import Checkbox from '../../../../components/Input/Checkbox';
// import { markUnrecognizedFile } from '../../../../core/slices/mainpage';
import UnrecognizedAvdumpedItem from './UnrecognizedAvdumpedItem';

import { useGetFileUnrecognizedQuery, useLazyPostFileAVDumpQuery } from '../../../../core/rtkQuery/fileApi';

function UnrecognizedTab() {
// const dispatch = useDispatch();

const [avdumpList, setAvdumpList] = useState([] as Array<string>);
const dispatch = useDispatch();

const items = useGetFileUnrecognizedQuery({ pageSize: 20 });
// const itemsMarked = useSelector((state: RootState) => state.mainpage.unrecognizedMark);
const [avdumpTrigger, avdumpResult] = useLazyPostFileAVDumpQuery();
const [avdumpTrigger] = useLazyPostFileAVDumpQuery();

const runAvdump = async (fileId: number) => {
const avdumpList = useSelector((state: RootState) => state.utilities.avdump);

const avdumpFile = async (fileId: number) => {
dispatch(setAvdumpItem({ id: fileId, hash: '', fetching: true }));
const result = await avdumpTrigger(fileId);
if (result.data?.Ed2k) {
const tempAvdumpList = [...avdumpList];
tempAvdumpList[fileId] = result.data.Ed2k;
setAvdumpList(tempAvdumpList);
}
dispatch(setAvdumpItem({ id: fileId, hash: result.data?.Ed2k ?? 'x', fetching: false }));
};

// const markFile = (id: string) => {
// const state = itemsMarked.indexOf(id) === -1;
// dispatch(markUnrecognizedFile({ id, state }));
// };

const renderItem = (item: FileType) => (
<div key={item.ID} className="flex mt-3 first:mt-0 items-center">
{/*<Checkbox id={`${item.ID}`} isChecked={itemsMarked.indexOf(`${item.ID}`) !== -1} onChange={() => {markFile(`${item.ID}`);}} className="mr-4" />*/}
{avdumpList[item.ID] && <UnrecognizedAvdumpedItem item={item} hash={avdumpList[item.ID]} />}
{avdumpList[item.ID] && <UnrecognizedAvdumpedItem item={item} hash={avdumpList[item.ID].hash} />}
{avdumpList[item.ID] === undefined && (
<div className="flex flex-col grow">
<span className="font-semibold">{moment(item.Created).format('yyyy-MM-DD')} / {moment(item.Created).format('hh:mm A')}</span>
<span className="flex break-words">{item.Locations[0].RelativePath}</span>
</div>
)}
<div className="flex my-2 justify-between">
{avdumpList[item.ID] === undefined && (
<Button onClick={() => runAvdump(item.ID)} className="py-1 px-2" loading={avdumpResult.isFetching}>
<Icon className="text-highlight-1" path={mdiHelpCircleOutline} size={1} horizontal vertical rotate={180}/>
{(avdumpList[item.ID] === undefined || avdumpList[item.ID].fetching) && (
<Button onClick={() => avdumpFile(item.ID)} className="py-1 px-2" loading={avdumpList[item.ID]?.fetching ?? false}>
<Icon className="text-highlight-1" path={mdiHelpCircleOutline} size={1} />
</Button>
)}
{avdumpList[item.ID] && (
{(avdumpList[item.ID] && !avdumpList[item.ID].fetching) && (
<div className="py-1 px-2 cursor-pointer text-highlight-2">
<CopyToClipboard text={avdumpList[item.ID] || ''} onCopy={() => toast.success('Copied to clipboard!')}>
<Icon path={mdiClipboardTextMultipleOutline} size={1} horizontal vertical rotate={180} />
<CopyToClipboard text={avdumpList[item.ID].hash || ''} onCopy={() => toast.success('Copied to clipboard!')}>
<Icon path={mdiClipboardTextMultipleOutline} size={1} />
</CopyToClipboard>
</div>
)}
Expand All @@ -77,7 +66,7 @@ function UnrecognizedTab() {
});

if (files.length === 0) {
files.push(<div className="flex justify-center font-semibold mt-4">No Unrecognized Files, Good Job!</div>);
files.push(<div className="flex justify-center font-semibold mt-4" key="no-files">No Unrecognized Files, Good Job!</div>);
}

return (<React.Fragment>{files}</React.Fragment>);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/dashboard/panels/ImportFolders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ function ImportFolders() {
);

return (
<ShokoPanel title="Import Folders" options={renderOptions()} isFetching={importFolderQuery.isFetching} disableClick={layoutEditMode}>
<ShokoPanel title="Import Folders" options={renderOptions()} isFetching={importFolderQuery.isFetching} editMode={layoutEditMode}>
{importFolders.length === 0
? (<div className="flex justify-center font-bold mt-4" key="no-folders">No import folders added!</div>)
? (<div className="flex justify-center font-semibold mt-4" key="no-folders">No import folders added!</div>)
: importFolders.map(importFolder => renderFolder(importFolder))}
</ShokoPanel>
);
Expand Down
9 changes: 7 additions & 2 deletions src/pages/dashboard/panels/NextUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ const NextUp = () => {
const items = useGetDashboardNextUpEpisodesQuery({ pageSize: 20 });

return (
<ShokoPanel title="Next Up" isFetching={items.isLoading} disableClick={layoutEditMode}>
<div className="flex flex-nowrap overflow-x-auto shoko-scrollbar h-90">{items.data?.map(item => <EpisodeDetails episode={item} key={item.IDs.ID} />)}</div>
<ShokoPanel title="Next Up" isFetching={items.isLoading} editMode={layoutEditMode}>
<div className="flex shoko-scrollbar">
{(items.data?.length ?? 0) > 0
? items.data?.map(item => <EpisodeDetails episode={item} key={item.IDs.ID} />)
: <div className="flex justify-center font-semibold mt-4 w-full">No next up episodes!</div>
}
</div>
</ShokoPanel>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/pages/dashboard/panels/QueueProcessor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function QueueProcessor() {
}

return (
<ShokoPanel title="Queue Processor" options={renderOptions()} isFetching={!hasFetched} disableClick={layoutEditMode}>
<ShokoPanel title="Queue Processor" options={renderOptions()} isFetching={!hasFetched} editMode={layoutEditMode}>
<div className="flex flex-col justify-between h-full">
{commands}
</div>
Expand Down
Loading