/* * 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, { useContext, useEffect, useState, useRef, } from 'react'; import { useHistory } from 'react-router-dom'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import IconButton from '@mui/material/IconButton'; import DownloadIcon from '@mui/icons-material/Download'; import Avatar from '@mui/material/Avatar'; import Typography from '@mui/material/Typography'; import NavbarContext from 'components/context/NavbarContext'; import client from 'util/client'; import useLocalStorage from 'util/useLocalStorage'; import EmptyView from 'components/util/EmptyView'; import LoadingPlaceholder from 'components/util/LoadingPlaceholder'; import { Box } from '@mui/system'; function epochToDate(epoch: number) { const date = new Date(0); // The 0 there is the key, which sets the date to the epoch date.setUTCSeconds(epoch); return date; } function isTheSameDay(first:Date, second:Date) { return first.getDate() === second.getDate() && first.getMonth() === second.getMonth() && first.getFullYear() === second.getFullYear(); } function getDateString(date: Date) { const today = new Date(); if (isTheSameDay(today, date)) return 'TODAY'; // calculate yesterday const yesterday = new Date(); yesterday.setDate(today.getDate() - 1); if (isTheSameDay(yesterday, date)) return 'YESTERDAY'; return date.toLocaleDateString(); } function groupByDate(updates: IMangaChapter[]): [string, { item: IMangaChapter, globalIdx: number }[] ][] { if (updates.length === 0) return []; const groups = {}; updates.forEach((item, globalIdx) => { const key = getDateString(epochToDate(item.chapter.fetchedAt)); // @ts-ignore if (groups[key] === undefined) groups[key] = []; // @ts-ignore groups[key].push({ item, globalIdx }); }); // @ts-ignore return Object.keys(groups).map((key) => [key, groups[key]]); } const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws'); const initialQueue = { status: 'Stopped', queue: [], } as IQueue; export default function Updates() { const history = useHistory(); const { setTitle, setAction } = useContext(NavbarContext); const [updateEntries, setUpdateEntries] = useState([]); const [hasNextPage, setHasNextPage] = useState(true); const [fetched, setFetched] = useState(false); const [lastPageNum, setLastPageNum] = useState(0); const [serverAddress] = useLocalStorage('serverBaseURL', ''); const [useCache] = useLocalStorage('useCache', true); const [, setWsClient] = useState(); const [{ queue }, setQueueState] = useState(initialQueue); useEffect(() => { const wsc = new WebSocket(`${baseWebsocketUrl}/api/v1/downloads`); wsc.onmessage = (e) => { const data = JSON.parse(e.data) as IQueue; setQueueState(data); }; setWsClient(wsc); return () => wsc.close(); }, []); useEffect(() => { setTitle('Updates'); setAction(<>); }, []); useEffect(() => { if (hasNextPage) { client.get(`/api/v1/update/recentChapters/${lastPageNum}`) .then((response) => response.data) .then(({ hasNextPage: fetchedHasNextPage, page }: PaginatedList) => { setUpdateEntries([ ...updateEntries, ...page, ]); setHasNextPage(fetchedHasNextPage); setFetched(true); }); } }, [lastPageNum]); const lastEntry = useRef(null); const scrollHandler = () => { if (lastEntry.current) { const rect = lastEntry.current.getBoundingClientRect(); if (((rect.y + rect.height) / window.innerHeight < 2) && hasNextPage) { setLastPageNum(lastPageNum + 1); } } }; useEffect(() => { window.addEventListener('scroll', scrollHandler, true); return () => { window.removeEventListener('scroll', scrollHandler, true); }; }, [hasNextPage, updateEntries]); if (!fetched) { return ; } if (fetched && updateEntries.length === 0) { return ; } const downloadStatusStringFor = (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; }; const downloadChapter = (chapter: IChapter) => { client.get(`/api/v1/download/${chapter.mangaId}/chapter/${chapter.index}`); }; return ( <> {groupByDate(updateEntries).map((dateGroup) => (
{dateGroup[0]} {dateGroup[1].map(({ item: { chapter, manga }, globalIdx }) => ( history.push({ pathname: `/manga/${chapter.mangaId}/chapter/${chapter.index}`, state: history.location.state })} > {manga.title} {chapter.name} {downloadStatusStringFor(chapter)} {downloadStatusStringFor(chapter) === '' && ( { downloadChapter(chapter); // prevent parent tags from getting the event e.stopPropagation(); }} size="large" > )} ))}
))} ); }