mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-12-26 16:51:50 +01:00
Implemented Dowloads front-end
This commit is contained in:
parent
224c24ee9f
commit
5023e96301
@ -83,6 +83,7 @@ object DownloadManager {
|
||||
)
|
||||
)
|
||||
)
|
||||
start()
|
||||
}
|
||||
notifyAllClients()
|
||||
}
|
||||
@ -93,10 +94,14 @@ object DownloadManager {
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (downloader != null && !downloader?.isAlive!!) // doesn't exist or is dead
|
||||
downloader = null
|
||||
|
||||
if (downloader == null) {
|
||||
downloader = Downloader(downloadQueue) { notifyAllClients() }
|
||||
downloader!!.start()
|
||||
}
|
||||
|
||||
notifyAllClients()
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,10 @@ class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter
|
||||
|
||||
override fun run() {
|
||||
do {
|
||||
val download = downloadQueue.firstOrNull { it.state == Queued } ?: break
|
||||
val download = downloadQueue.firstOrNull {
|
||||
it.state == Queued ||
|
||||
(it.state == Error && it.tries < 3) // 3 re-tries per download
|
||||
} ?: break
|
||||
|
||||
try {
|
||||
download.state = Downloading
|
||||
@ -44,7 +47,7 @@ class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter
|
||||
download.chapter = runBlocking { getChapter(download.chapterIndex, download.mangaId) }
|
||||
step()
|
||||
|
||||
val pageCount = download.chapter!!.pageCount!!
|
||||
val pageCount = download.chapter!!.pageCount
|
||||
for (pageNum in 0 until pageCount) {
|
||||
runBlocking { getPageImage(download.mangaId, download.chapterIndex, pageNum) }
|
||||
// TODO: retry on error with 2,4,8 seconds of wait
|
||||
@ -60,12 +63,15 @@ class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter
|
||||
}
|
||||
}
|
||||
step()
|
||||
|
||||
downloadQueue.removeIf { it.mangaId == download.mangaId && it.chapterIndex == download.chapterIndex }
|
||||
step()
|
||||
} catch (e: DownloadShouldStopException) {
|
||||
println("Downloader was stopped")
|
||||
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Queued }
|
||||
} catch (e: Exception) {
|
||||
println("Downloader faced an exception")
|
||||
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Error }
|
||||
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Error; it.tries++ }
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
notifier()
|
||||
|
@ -14,5 +14,6 @@ class DownloadChapter(
|
||||
val mangaId: Int,
|
||||
var state: DownloadState = DownloadState.Queued,
|
||||
var progress: Float = 0f,
|
||||
var tries: Int = 0,
|
||||
var chapter: ChapterDataClass? = null,
|
||||
)
|
||||
|
@ -38,6 +38,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@types/react-lazyload": "^3.1.0",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
|
@ -34,6 +34,7 @@ import SourceAnimes from 'screens/anime/SourceAnimes';
|
||||
import Reader from 'screens/manga/Reader';
|
||||
import Player from 'screens/anime/Player';
|
||||
import AnimeExtensions from 'screens/anime/AnimeExtensions';
|
||||
import DownloadQueue from 'screens/manga/DownloadQueue';
|
||||
|
||||
export default function App() {
|
||||
const [title, setTitle] = useState<string>('Tachidesk');
|
||||
@ -125,6 +126,9 @@ export default function App() {
|
||||
<Route path="/manga/sources">
|
||||
<MangaSources />
|
||||
</Route>
|
||||
<Route path="/manga/downloads">
|
||||
<DownloadQueue />
|
||||
</Route>
|
||||
<Route path="/manga/:mangaId/chapter/:chapterNum">
|
||||
<></>
|
||||
</Route>
|
||||
|
@ -14,6 +14,7 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import CollectionsBookmarkIcon from '@material-ui/icons/CollectionsBookmark';
|
||||
import ExploreIcon from '@material-ui/icons/Explore';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
import GetAppIcon from '@material-ui/icons/GetApp';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import { Link } from 'react-router-dom';
|
||||
@ -87,6 +88,14 @@ export default function TemporaryDrawer({ drawerOpen, setDrawerOpen }: IProps) {
|
||||
<ListItemText primary="Anime Sources" />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link to="/manga/downloads" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||
<ListItem button key="Manga Download Queue">
|
||||
<ListItemIcon>
|
||||
<GetAppIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Manga Download Queue" />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link to="/settings" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||
<ListItem button key="settings">
|
||||
<ListItemIcon>
|
||||
|
@ -47,12 +47,13 @@ const useStyles = makeStyles((theme) => ({
|
||||
interface IProps{
|
||||
chapter: IChapter
|
||||
triggerChaptersUpdate: () => void
|
||||
downloadingString: string
|
||||
}
|
||||
|
||||
export default function ChapterCard(props: IProps) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const { chapter, triggerChaptersUpdate } = props;
|
||||
const { chapter, triggerChaptersUpdate, downloadingString } = props;
|
||||
|
||||
const dateStr = chapter.uploadDate && new Date(chapter.uploadDate).toISOString().slice(0, 10);
|
||||
|
||||
@ -75,6 +76,11 @@ export default function ChapterCard(props: IProps) {
|
||||
.then(() => triggerChaptersUpdate());
|
||||
};
|
||||
|
||||
const downloadChapter = () => {
|
||||
client.get(`/api/v1/download/${chapter.mangaId}/chapter/${chapter.index}`);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const readChapterColor = theme.palette.type === 'dark' ? '#acacac' : '#b0b0b0';
|
||||
return (
|
||||
<>
|
||||
@ -101,6 +107,7 @@ export default function ChapterCard(props: IProps) {
|
||||
{chapter.scanlator}
|
||||
{chapter.scanlator && ' '}
|
||||
{dateStr}
|
||||
{downloadingString}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
@ -115,7 +122,8 @@ export default function ChapterCard(props: IProps) {
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{/* <MenuItem onClick={handleClose}>Download</MenuItem> */}
|
||||
{downloadingString.length === 0
|
||||
&& <MenuItem onClick={downloadChapter}>Download</MenuItem> }
|
||||
<MenuItem onClick={() => sendChange('bookmarked', !chapter.bookmarked)}>
|
||||
{chapter.bookmarked && 'Remove bookmark'}
|
||||
{!chapter.bookmarked && 'Bookmark'}
|
||||
|
@ -198,7 +198,7 @@ export default function MangaDetails(props: IProps) {
|
||||
<div className={classes.top}>
|
||||
<div className={classes.leftRight}>
|
||||
<div className={classes.leftSide}>
|
||||
<img src={`${serverAddress}${manga.thumbnailUrl}?x=${Math.random()}`} alt="Manga Thumbnail" />
|
||||
<img src={`${serverAddress}${manga.thumbnailUrl}`} alt="Manga Thumbnail" />
|
||||
</div>
|
||||
<div className={classes.rightSide}>
|
||||
<h1>
|
||||
|
153
webUI/react/src/screens/manga/DownloadQueue.tsx
Normal file
153
webUI/react/src/screens/manga/DownloadQueue.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
/*
|
||||
* 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 NavbarContext from 'context/NavbarContext';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
|
||||
import PauseIcon from '@material-ui/icons/Pause';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import client from 'util/client';
|
||||
import {
|
||||
DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDraggingStyle,
|
||||
} from 'react-beautiful-dnd';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
import { Palette } from '@material-ui/core/styles/createPalette';
|
||||
import List from '@material-ui/core/List';
|
||||
import DragHandleIcon from '@material-ui/icons/DragHandle';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import { ListItemIcon } from '@material-ui/core';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
|
||||
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
|
||||
|
||||
const getItemStyle = (isDragging: boolean,
|
||||
draggableStyle: DraggingStyle | NotDraggingStyle | undefined, palette: Palette) => ({
|
||||
// styles we need to apply on draggables
|
||||
...draggableStyle,
|
||||
|
||||
...(isDragging && {
|
||||
background: palette.type === 'dark' ? '#424242' : 'rgb(235,235,235)',
|
||||
}),
|
||||
});
|
||||
|
||||
const initialQueue = {
|
||||
status: 'Stopped',
|
||||
queue: [],
|
||||
} as IQueue;
|
||||
|
||||
export default function DownloadQueue() {
|
||||
const [, setWsClient] = useState<WebSocket>();
|
||||
const [queueState, setQueueState] = useState<IQueue>(initialQueue);
|
||||
const { queue, status } = queueState;
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const { setTitle, setAction } = useContext(NavbarContext);
|
||||
|
||||
const toggleQueueStatus = () => {
|
||||
if (status === 'Stopped') {
|
||||
client.get('/api/v1/downloads/start');
|
||||
} else {
|
||||
client.get('/api/v1/downloads/stop');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTitle('Download Queue');
|
||||
|
||||
setAction(() => {
|
||||
if (status === 'Stopped') {
|
||||
return (
|
||||
<IconButton onClick={toggleQueueStatus}>
|
||||
<PlayArrowIcon />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<IconButton onClick={toggleQueueStatus}>
|
||||
<PauseIcon />
|
||||
</IconButton>
|
||||
);
|
||||
});
|
||||
}, [status]);
|
||||
|
||||
useEffect(() => {
|
||||
const wsc = new WebSocket(`${baseWebsocketUrl}/api/v1/downloads`);
|
||||
wsc.onmessage = (e) => {
|
||||
setQueueState(JSON.parse(e.data));
|
||||
};
|
||||
|
||||
setWsClient(wsc);
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable">
|
||||
{(provided) => (
|
||||
<List ref={provided.innerRef}>
|
||||
{queue.map((item, index) => (
|
||||
<Draggable
|
||||
key={`${item.mangaId}-${item.chapterIndex}`}
|
||||
draggableId={`${item.mangaId}-${item.chapterIndex}`}
|
||||
index={index}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ListItem
|
||||
ContainerProps={{ ref: provided.innerRef } as any}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={getItemStyle(
|
||||
snapshot.isDragging,
|
||||
provided.draggableProps.style,
|
||||
theme.palette,
|
||||
)}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<DragHandleIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
`${item.chapter.name} | `
|
||||
+ ` (${item.progress * 100}%)`
|
||||
+ ` => state: ${item.state}`
|
||||
}
|
||||
/>
|
||||
{/* <IconButton
|
||||
onClick={() => {
|
||||
handleEditDialogOpen(index);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
deleteCategory(index);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton> */}
|
||||
</ListItem>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</List>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</>
|
||||
);
|
||||
}
|
@ -41,6 +41,12 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const baseWebsocketUrl = JSON.parse(window.localStorage.getItem('serverBaseURL')!).replace('http', 'ws');
|
||||
const initialQueue = {
|
||||
status: 'Stopped',
|
||||
queue: [],
|
||||
} as IQueue;
|
||||
|
||||
export default function Manga() {
|
||||
const classes = useStyles();
|
||||
|
||||
@ -55,10 +61,48 @@ export default function Manga() {
|
||||
const [noChaptersFound, setNoChaptersFound] = useState(false);
|
||||
const [chapterUpdateTriggerer, setChapterUpdateTriggerer] = useState(0);
|
||||
|
||||
const [, setWsClient] = useState<WebSocket>();
|
||||
const [{ queue }, setQueueState] = useState<IQueue>(initialQueue);
|
||||
|
||||
function triggerChaptersUpdate() {
|
||||
setChapterUpdateTriggerer(chapterUpdateTriggerer + 1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const wsc = new WebSocket(`${baseWebsocketUrl}/api/v1/downloads`);
|
||||
wsc.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data) as IQueue;
|
||||
setQueueState(data);
|
||||
|
||||
let shouldUpdate = false;
|
||||
data.queue.forEach((q) => {
|
||||
if (q.mangaId === manga?.id && q.state === 'Finished') {
|
||||
shouldUpdate = true;
|
||||
}
|
||||
});
|
||||
if (shouldUpdate) {
|
||||
triggerChaptersUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
setWsClient(wsc);
|
||||
|
||||
return () => wsc.close();
|
||||
}, [queue.length]);
|
||||
|
||||
const downloadingStringFor = (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}%)`;
|
||||
}
|
||||
});
|
||||
return rtn;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (manga === undefined || !manga.freshData) {
|
||||
client.get(`/api/v1/manga/${id}/?onlineFetch=${manga !== undefined}`)
|
||||
@ -105,6 +149,7 @@ export default function Manga() {
|
||||
itemContent={(index:number) => (
|
||||
<ChapterCard
|
||||
chapter={chapters[index]}
|
||||
downloadingString={downloadingStringFor(chapters[index])}
|
||||
triggerChaptersUpdate={triggerChaptersUpdate}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,3 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
@ -5,9 +8,6 @@
|
||||
* 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/. */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
/* eslint-disable react/destructuring-assignment */
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import {
|
||||
List,
|
||||
@ -16,7 +16,9 @@ import {
|
||||
ListItemIcon,
|
||||
IconButton,
|
||||
} from '@material-ui/core';
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import {
|
||||
DragDropContext, Droppable, Draggable, DropResult, DraggingStyle, NotDraggingStyle,
|
||||
} from 'react-beautiful-dnd';
|
||||
import DragHandleIcon from '@material-ui/icons/DragHandle';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
@ -31,10 +33,12 @@ import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import NavbarContext from '../../context/NavbarContext';
|
||||
import client from '../../util/client';
|
||||
import NavbarContext from 'context/NavbarContext';
|
||||
import client from 'util/client';
|
||||
import { Palette } from '@material-ui/core/styles/createPalette';
|
||||
|
||||
const getItemStyle = (isDragging, draggableStyle, palette) => ({
|
||||
const getItemStyle = (isDragging: boolean,
|
||||
draggableStyle: DraggingStyle | NotDraggingStyle | undefined, palette: Palette) => ({
|
||||
// styles we need to apply on draggables
|
||||
...draggableStyle,
|
||||
|
||||
@ -47,14 +51,14 @@ export default function Categories() {
|
||||
const { setTitle, setAction } = useContext(NavbarContext);
|
||||
useEffect(() => { setTitle('Categories'); setAction(<></>); }, []);
|
||||
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [categoryToEdit, setCategoryToEdit] = useState(-1); // -1 means new category
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [dialogName, setDialogName] = useState('');
|
||||
const [dialogDefault, setDialogDefault] = useState(false);
|
||||
const [categories, setCategories] = useState<ICategory[]>([]);
|
||||
const [categoryToEdit, setCategoryToEdit] = useState<number>(-1); // -1 means new category
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
const [dialogName, setDialogName] = useState<string>('');
|
||||
const [dialogDefault, setDialogDefault] = useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
|
||||
const [updateTriggerHolder, setUpdateTriggerHolder] = useState(0); // just a hack
|
||||
const [updateTriggerHolder, setUpdateTriggerHolder] = useState<number>(0); // just a hack
|
||||
const triggerUpdate = () => setUpdateTriggerHolder(updateTriggerHolder + 1); // just a hack
|
||||
|
||||
useEffect(() => {
|
||||
@ -65,12 +69,12 @@ export default function Categories() {
|
||||
}
|
||||
}, [updateTriggerHolder]);
|
||||
|
||||
const categoryReorder = (list, from, to) => {
|
||||
const categoryReorder = (list: ICategory[], from: number, to: number) => {
|
||||
const category = list[from];
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('from', from + 1);
|
||||
formData.append('to', to + 1);
|
||||
formData.append('from', `${from + 1}`);
|
||||
formData.append('to', `${to + 1}`);
|
||||
client.post(`/api/v1/category/${category.id}/reorder`, formData)
|
||||
.finally(() => triggerUpdate());
|
||||
|
||||
@ -81,7 +85,7 @@ export default function Categories() {
|
||||
return result;
|
||||
};
|
||||
|
||||
const onDragEnd = (result) => {
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
// dropped outside the list?
|
||||
if (!result.destination) {
|
||||
return;
|
||||
@ -105,7 +109,7 @@ export default function Categories() {
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleEditDialogOpen = (index) => {
|
||||
const handleEditDialogOpen = (index:number) => {
|
||||
setDialogName(categories[index].name);
|
||||
setDialogDefault(categories[index].default);
|
||||
setCategoryToEdit(index);
|
||||
@ -121,7 +125,7 @@ export default function Categories() {
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', dialogName);
|
||||
formData.append('default', dialogDefault);
|
||||
formData.append('default', dialogDefault.toString());
|
||||
|
||||
if (categoryToEdit === -1) {
|
||||
client.post('/api/v1/category/', formData)
|
||||
@ -133,7 +137,7 @@ export default function Categories() {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteCategory = (index) => {
|
||||
const deleteCategory = (index:number) => {
|
||||
const category = categories[index];
|
||||
client.delete(`/api/v1/category/${category.id}`)
|
||||
.finally(() => triggerUpdate());
|
||||
@ -153,8 +157,7 @@ export default function Categories() {
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<ListItem
|
||||
ContainerComponent="li"
|
||||
ContainerProps={{ ref: provided.innerRef }}
|
||||
ContainerProps={{ ref: provided.innerRef } as any}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={getItemStyle(
|
16
webUI/react/src/typings.d.ts
vendored
16
webUI/react/src/typings.d.ts
vendored
@ -67,6 +67,7 @@ interface IChapter {
|
||||
index: number
|
||||
chapterCount: number
|
||||
pageCount: number
|
||||
downloaded: boolean
|
||||
}
|
||||
|
||||
interface IEpisode {
|
||||
@ -99,7 +100,7 @@ interface IPartialEpisode {
|
||||
interface ICategory {
|
||||
id: number
|
||||
order: number
|
||||
name: String
|
||||
name: string
|
||||
default: boolean
|
||||
}
|
||||
|
||||
@ -153,3 +154,16 @@ interface IAbout {
|
||||
github: string
|
||||
discord: string
|
||||
}
|
||||
|
||||
interface IDownloadChapter{
|
||||
chapterIndex: number
|
||||
mangaId: number
|
||||
state: 'Queued' | 'Downloading' | 'Finished' | 'Error'
|
||||
progress: number
|
||||
chapter: IChapter
|
||||
}
|
||||
|
||||
interface IQueue {
|
||||
status: 'Stopped' | 'Started'
|
||||
queue: IDownloadChapter[]
|
||||
}
|
||||
|
@ -1891,6 +1891,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
|
||||
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
|
||||
|
||||
"@types/react-beautiful-dnd@^13.0.0":
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4"
|
||||
integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@^17.0.2":
|
||||
version "17.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.5.tgz#df44eed5b8d9e0b13bb0cd38e0ea6572a1231227"
|
||||
|
Loading…
Reference in New Issue
Block a user