forked from Suwayomi/Suwayomi-WebUI
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathChapterList.tsx
150 lines (135 loc) · 5.42 KB
/
ChapterList.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import React, {
useState, useEffect, useCallback, useMemo, useRef,
} from 'react';
import { Box, styled } from '@mui/system';
import { Virtuoso } from 'react-virtuoso';
import Typography from '@mui/material/Typography';
import { CircularProgress, Stack } from '@mui/material';
import makeToast from 'components/util/Toast';
import ChapterOptions from 'components/chapter/ChapterOptions';
import ChapterCard from 'components/chapter/ChapterCard';
import { useReducerLocalStorage } from 'util/useLocalStorage';
import {
chapterOptionsReducer, defaultChapterOptions, findFirstUnreadChapter,
filterAndSortChapters,
} from 'components/chapter/util';
import ResumeFab from 'components/chapter/ResumeFAB';
import useSubscription from 'components/library/useSubscription';
const CustomVirtuoso = styled(Virtuoso)(({ theme }) => ({
listStyle: 'none',
padding: 0,
minHeight: '200px',
[theme.breakpoints.up('md')]: {
width: '50vw',
// 64px for the Appbar, 48px for the ChapterCount Header
height: 'calc(100vh - 64px - 48px)',
margin: 0,
},
}));
interface IProps {
id: string
chaptersData: IChapter[] | undefined
onRefresh: () => void;
}
export default function ChapterList({ id, chaptersData, onRefresh }: IProps) {
const noChaptersFound = chaptersData?.length === 0;
const chapters = useMemo(() => chaptersData ?? [], [chaptersData]);
const [firstUnreadChapter, setFirstUnreadChapter] = useState<IChapter>();
const [filteredChapters, setFilteredChapters] = useState<IChapter[]>([]);
// eslint-disable-next-line max-len
const [options, optionsDispatch] = useReducerLocalStorage<ChapterListOptions, ChapterOptionsReducerAction>(
chapterOptionsReducer, `${id}filterOptions`, defaultChapterOptions,
);
const prevQueueRef = useRef<IDownloadChapter[]>();
const queue = useSubscription<IQueue>('/api/v1/downloads').data?.queue;
const downloadStatusStringFor = useCallback((chapter: IChapter) => {
let rtn = '';
if (chapter.downloaded) {
rtn = ' • Downloaded';
}
queue?.forEach((q) => {
if (chapter.index === q.chapterIndex && chapter.mangaId === q.mangaId) {
rtn = ` • Downloading (${(q.progress * 100).toFixed(2)}%)`;
}
});
return rtn;
}, [queue]);
useEffect(() => {
if (prevQueueRef.current && queue) {
const prevQueue = prevQueueRef.current;
const changedDownloads = queue.filter((cd) => {
const prevChapterDownload = prevQueue
.find((pcd) => cd.chapterIndex === pcd.chapterIndex
&& cd.mangaId === pcd.mangaId);
if (!prevChapterDownload) return true;
return cd.state !== prevChapterDownload.state;
});
if (changedDownloads.length > 0) {
onRefresh();
}
}
prevQueueRef.current = queue;
}, [queue]);
useEffect(() => {
const filtered = filterAndSortChapters(chapters, options);
setFilteredChapters(filtered);
setFirstUnreadChapter(findFirstUnreadChapter(filtered));
}, [options, chapters]);
useEffect(() => {
if (noChaptersFound) {
makeToast('No chapters found', 'warning');
}
}, [noChaptersFound]);
if (chapters.length === 0 || noChaptersFound) {
return (
<div style={{
margin: '10px auto',
display: 'flex',
justifyContent: 'center',
}}
>
<CircularProgress thickness={5} />
</div>
);
}
return (
<>
<Stack direction="column">
<Box sx={{
display: 'flex', justifyContent: 'space-between', px: 1.5, mt: 1,
}}
>
<Typography variant="h5">
{`${filteredChapters.length} Chapters`}
</Typography>
<ChapterOptions options={options} optionsDispatch={optionsDispatch} />
</Box>
<CustomVirtuoso
style={{ // override Virtuoso default values and set them with class
height: 'undefined',
// 900 is the md breakpoint in MUI
overflowY: window.innerWidth < 900 ? 'visible' : 'auto',
}}
totalCount={filteredChapters.length}
itemContent={(index:number) => (
<ChapterCard
showChapterNumber={options.showChapterNumber}
chapter={filteredChapters[index]}
downloadStatusString={downloadStatusStringFor(filteredChapters[index])}
triggerChaptersUpdate={onRefresh}
/>
)}
useWindowScroll={window.innerWidth < 900}
overscan={window.innerHeight * 0.5}
/>
</Stack>
{firstUnreadChapter && <ResumeFab chapter={firstUnreadChapter} mangaId={id} />}
</>
);
}