mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-04-21 00:12:05 +00:00
Started Layout - Working on Queue
This commit is contained in:
parent
84da65120f
commit
9c5095f7a8
16 changed files with 131 additions and 159 deletions
6
@types/messageHandler.d.ts
vendored
6
@types/messageHandler.d.ts
vendored
|
|
@ -1,6 +1,7 @@
|
|||
import { HLSCallback } from 'hls-download';
|
||||
import type { FunimationSearch } from './funiSearch';
|
||||
import type { AvailableMuxer } from '../modules/module.args';
|
||||
import { LanguageItem } from '../modules/module.langsData';
|
||||
|
||||
export interface MessageHandler {
|
||||
auth: (data: AuthData) => Promise<AuthResponse>;
|
||||
|
|
@ -31,6 +32,7 @@ export type QueueItem = {
|
|||
},
|
||||
q: number,
|
||||
dubLang: string[],
|
||||
image: string
|
||||
}
|
||||
|
||||
export type ResolveItemsData = {
|
||||
|
|
@ -80,7 +82,8 @@ export type FuniEpisodeData = {
|
|||
episode: string,
|
||||
show: string,
|
||||
season: string
|
||||
}
|
||||
},
|
||||
image: string
|
||||
};
|
||||
|
||||
export type AuthData = { username: string, password: string };
|
||||
|
|
@ -124,6 +127,7 @@ export type DownloadInfo = {
|
|||
title: string
|
||||
},
|
||||
title: string,
|
||||
language: LanguageItem,
|
||||
fileName: string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -247,11 +247,13 @@ export default class Crunchy implements ServiceClass {
|
|||
return true;
|
||||
}
|
||||
|
||||
public async refreshToken(){
|
||||
public async refreshToken( ifNeeded = false ){
|
||||
if(!this.token.access_token && !this.token.refresh_token || this.token.access_token && !this.token.refresh_token){
|
||||
await this.doAnonymousAuth();
|
||||
}
|
||||
else{
|
||||
if (ifNeeded)
|
||||
return;
|
||||
if(Date.now() > new Date(this.token.expires).getTime()){
|
||||
//console.log('[WARN] The token has expired compleatly. I will try to refresh the token anyway, but you might have to reauth.');
|
||||
}
|
||||
|
|
@ -1147,7 +1149,8 @@ export default class Crunchy implements ServiceClass {
|
|||
parent: {
|
||||
title: medias.seasonTitle
|
||||
},
|
||||
title: medias.episodeTitle
|
||||
title: medias.episodeTitle,
|
||||
language: lang
|
||||
}) : undefined
|
||||
}).download();
|
||||
if(!dlStreamByPl.ok){
|
||||
|
|
|
|||
9
funi.ts
9
funi.ts
|
|
@ -286,7 +286,8 @@ export default class Funi implements ServiceClass {
|
|||
episode: eps[e].ids.externalEpisodeId,
|
||||
season: eps[e].ids.externalSeasonId,
|
||||
show: eps[e].ids.externalShowId
|
||||
}
|
||||
},
|
||||
image: eps[e].item.poster
|
||||
});
|
||||
epSelEpsTxt.push(epStrId);
|
||||
is_selected = true;
|
||||
|
|
@ -694,7 +695,8 @@ export default class Funi implements ServiceClass {
|
|||
title: epsiode.showTitle
|
||||
},
|
||||
title: epsiode.title,
|
||||
image: epsiode.image
|
||||
image: epsiode.image,
|
||||
language: streamPath.lang,
|
||||
}) : undefined);
|
||||
if (!dlFailed) {
|
||||
if (plAud) {
|
||||
|
|
@ -734,7 +736,8 @@ export default class Funi implements ServiceClass {
|
|||
title: epsiode.showTitle
|
||||
},
|
||||
title: epsiode.title,
|
||||
image: epsiode.image
|
||||
image: epsiode.image,
|
||||
language: plAud.language
|
||||
}) : undefined);
|
||||
if (!dlFailedA)
|
||||
puraudio.push({
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
constructor(window: BrowserWindow) {
|
||||
super(window);
|
||||
this.crunchy = new Crunchy();
|
||||
this.crunchy.refreshToken();
|
||||
}
|
||||
|
||||
public async listEpisodes (id: string): Promise<EpisodeListResponse> {
|
||||
await this.crunchy.refreshToken(true);
|
||||
return { isOk: true, value: (await this.crunchy.listSeriesID(id)).list };
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
}
|
||||
|
||||
public async resolveItems(data: ResolveItemsData): Promise<ResponseBase<QueueItem[]>> {
|
||||
await this.crunchy.refreshToken(true);
|
||||
console.log(`[DEBUG] Got resolve options: ${JSON.stringify(data)}`);
|
||||
const res = await this.crunchy.downloadFromSeriesID(data.id, data);
|
||||
if (!res.isOk)
|
||||
|
|
@ -50,12 +51,14 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
season: a.season.toString()
|
||||
},
|
||||
e: a.e,
|
||||
image: a.image,
|
||||
episode: a.episodeNumber
|
||||
};
|
||||
}) };
|
||||
}
|
||||
|
||||
public async search(data: SearchData): Promise<SearchResponse> {
|
||||
await this.crunchy.refreshToken(true);
|
||||
console.log(`[DEBUG] Got search options: ${JSON.stringify(data)}`);
|
||||
const crunchySearch = await this.crunchy.doSearch(data);
|
||||
if (!crunchySearch.isOk)
|
||||
|
|
@ -76,6 +79,7 @@ class CrunchyHandler extends Base implements MessageHandler {
|
|||
}
|
||||
|
||||
public async downloadItem(data: DownloadData) {
|
||||
await this.crunchy.refreshToken(true);
|
||||
console.log(`[DEBUG] Got download options: ${JSON.stringify(data)}`);
|
||||
this.setDownloading(true);
|
||||
const _default = buildDefault() as ArgvType;
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class FunimationHandler extends Base implements MessageHandler {
|
|||
title: a.seasonTitle,
|
||||
season: a.seasonNumber
|
||||
},
|
||||
image: a.image,
|
||||
e: a.episodeID,
|
||||
episode: a.epsiodeNumber,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
import React from "react";
|
||||
import AuthButton from "./components/AuthButton";
|
||||
import { Box } from "@mui/material";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import MainFrame from "./components/MainFrame/MainFrame";
|
||||
import LogoutButton from "./components/LogoutButton";
|
||||
import AddToQueue from "./components/AddToQueue/AddToQueue";
|
||||
import { messageChannelContext } from './provider/MessageChannel';
|
||||
import { Folder } from "@mui/icons-material";
|
||||
|
||||
const Layout: React.FC = () => {
|
||||
|
||||
const messageHandler = React.useContext(messageChannelContext);
|
||||
|
||||
return <Box>
|
||||
<Box sx={{ height: 50, mb: 4, display: 'flex', gap: 1 }}>
|
||||
<LogoutButton />
|
||||
<AuthButton />
|
||||
<Box>
|
||||
<Button variant="contained" startIcon={<Folder />} onClick={() => messageHandler?.openFolder('content')}>Open Output Directory</Button>
|
||||
</Box>
|
||||
<AddToQueue />
|
||||
</Box>
|
||||
<MainFrame />
|
||||
</Box>;
|
||||
|
|
|
|||
26
gui/react/src/components/AddToQueue/AddToQueue.tsx
Normal file
26
gui/react/src/components/AddToQueue/AddToQueue.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Add } from "@mui/icons-material";
|
||||
import { Box, Button, Container, Dialog, Divider } from "@mui/material";
|
||||
import React from "react";
|
||||
import DownloadSelector from "./DownloadSelector/DownloadSelector";
|
||||
import EpisodeListing from "./DownloadSelector/Listing/EpisodeListing";
|
||||
import SearchBox from "./SearchBox/SearchBox";
|
||||
|
||||
const AddToQueue: React.FC = () => {
|
||||
const [isOpen, setOpen] = React.useState(false);
|
||||
|
||||
return <Box>
|
||||
<EpisodeListing />
|
||||
<Dialog open={isOpen} onClose={() => setOpen(false)} maxWidth='md'>
|
||||
<Box sx={{ border: '2px solid white', p: 2 }}>
|
||||
<SearchBox />
|
||||
<Divider variant='middle' className="divider-width" light sx={{ color: 'text.primary', fontSize: '1.2rem' }}>Options</Divider>
|
||||
<DownloadSelector onFinish={() => setOpen(false)} />
|
||||
</Box>
|
||||
</Dialog>
|
||||
<Button variant='contained' onClick={() => setOpen(true)}>
|
||||
<Add />
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
|
||||
export default AddToQueue;
|
||||
|
|
@ -7,7 +7,11 @@ import LoadingButton from '@mui/lab/LoadingButton';
|
|||
import { useSnackbar } from "notistack";
|
||||
import { Folder } from "@mui/icons-material";
|
||||
|
||||
const DownloadSelector: React.FC = () => {
|
||||
type DownloadSelectorProps = {
|
||||
onFinish?: () => unknown
|
||||
}
|
||||
|
||||
const DownloadSelector: React.FC<DownloadSelectorProps> = ({ onFinish }) => {
|
||||
const messageHandler = React.useContext(messageChannelContext);
|
||||
const [store, dispatch] = useStore();
|
||||
const [availableDubs, setAvailableDubs] = React.useState<string[]>([]);
|
||||
|
|
@ -55,6 +59,8 @@ const DownloadSelector: React.FC = () => {
|
|||
});
|
||||
}
|
||||
setLoading(false);
|
||||
if (onFinish)
|
||||
onFinish();
|
||||
}
|
||||
|
||||
const listEpisodes = async () => {
|
||||
|
|
@ -138,7 +144,6 @@ const DownloadSelector: React.FC = () => {
|
|||
<Box sx={{ gap: 2, flex: 0, m: 1, mb: 3, display: 'flex', justifyContent: 'center' }}>
|
||||
<LoadingButton loading={loading} onClick={listEpisodes} variant='contained'>List episodes</LoadingButton>
|
||||
<LoadingButton loading={loading} onClick={addToQueue} variant='contained'>Add to Queue</LoadingButton>
|
||||
<Button variant="contained" startIcon={<Folder />} onClick={() => messageHandler?.openFolder('content')}>Open Output Directory</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Box, List, ListItem, Typography, Divider, Dialog, Select, MenuItem, FormControl, InputLabel, Checkbox } from "@mui/material";
|
||||
import { CheckBox, CheckBoxOutlineBlank } from '@mui/icons-material'
|
||||
import React from "react";
|
||||
import useStore from "../../../hooks/useStore";
|
||||
import useStore from "../../../../hooks/useStore";
|
||||
|
||||
|
||||
const EpisodeListing: React.FC = () => {
|
||||
|
|
@ -42,12 +42,11 @@ const SearchBox: React.FC = () => {
|
|||
}, [search]);
|
||||
|
||||
const anchorBounding = anchor.current?.getBoundingClientRect();
|
||||
|
||||
return <ClickAwayListener onClickAway={() => setFocus(false)}>
|
||||
<Box sx={{ m: 2 }}>
|
||||
<TextField ref={anchor} value={search} onClick={() => setFocus(true)} onChange={e => setSearch(e.target.value)} variant='outlined' label='Search' fullWidth />
|
||||
{searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 && focus &&
|
||||
<Paper sx={{ position: 'absolute', maxHeight: '50%', width: `${anchorBounding?.width}px`,
|
||||
<Paper sx={{ position: 'fixed', maxHeight: '50%', width: `${anchorBounding?.width}px`,
|
||||
left: anchorBounding?.x, top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0), zIndex: 99, overflowY: 'scroll'}}>
|
||||
<List>
|
||||
{searchResult && searchResult.isOk ?
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import Queue from "./Queue/Queue";
|
||||
import { Box } from "@mui/material";
|
||||
import React from "react";
|
||||
import EpisodeListing from "../Listing/EpisodeListing";
|
||||
|
||||
const Bottom: React.FC = () => {
|
||||
return <Box sx={{ display: 'grid', gridTemplateColumns: '1fr' }}>
|
||||
<Queue />
|
||||
</Box>
|
||||
}
|
||||
|
||||
export default Bottom;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { Box, Divider, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import useStore from "../../../../hooks/useStore";
|
||||
|
||||
const Queue: React.FC = () => {
|
||||
const [ { queue } ] = useStore();
|
||||
|
||||
return <Box>
|
||||
{queue.map((item, index) => {
|
||||
return <Box key={`QueueItem_${index}`} sx={{ gap: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography color='text.primary'>
|
||||
{`[${index}] S${item.parent.season}E${item.episode} - ${item.title} (${item.dubLang.join(', ')})`}
|
||||
</Typography>
|
||||
<Divider />
|
||||
</Box>
|
||||
})}
|
||||
</Box>
|
||||
}
|
||||
|
||||
export default Queue;
|
||||
|
|
@ -1,25 +1,11 @@
|
|||
import { Box, Divider } from "@mui/material";
|
||||
import { Box } from "@mui/material";
|
||||
import React from "react";
|
||||
import Bottom from "./Bottom/Bottom";
|
||||
import DownloadSelector from "./DownloadSelector/DownloadSelector";
|
||||
import EpisodeListing from "./Listing/EpisodeListing";
|
||||
import './MainFrame.css';
|
||||
import Progress from "./Progress/Progress";
|
||||
import SearchBox from "./SearchBox/SearchBox";
|
||||
import Queue from "./Queue/Queue";
|
||||
|
||||
const MainFrame: React.FC = () => {
|
||||
return <Box sx={{ display: 'grid', gridTemplateColumns: '3fr 1fr', borderCollapse: 'collapse' }}>
|
||||
<Box sx={{ border: '2px solid white' }}>
|
||||
<SearchBox />
|
||||
<Divider variant='middle' className="divider-width" light sx={{ color: 'text.primary', fontSize: '1.2rem' }}>Options</Divider>
|
||||
<DownloadSelector />
|
||||
<Divider variant='middle' className="divider-width" light />
|
||||
<Bottom />
|
||||
</Box>
|
||||
<Box sx={{ marginLeft: 1 }}>
|
||||
<Progress />
|
||||
</Box>
|
||||
<EpisodeListing />
|
||||
return <Box sx={{ marginLeft: 1 }}>
|
||||
<Queue />
|
||||
</Box>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
import { Close } from "@mui/icons-material";
|
||||
import { Box, Skeleton, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import { messageChannelContext } from "../../../provider/MessageChannel";
|
||||
import LinearProgressWithLabel from "../../reusable/LinearProgressWithLabel";
|
||||
|
||||
import useDownloadManager from "../DownloadManager/DownloadManager";
|
||||
|
||||
const Progress: React.FC = () => {
|
||||
const data = useDownloadManager();
|
||||
|
||||
return data ? <Box sx={{ display: 'grid', gridTemplateRows: '1fr 2fr', height: 'auto' }}>
|
||||
<img style={{ maxWidth: '100%', maxHeight: '100%', border: '2px solid white', padding: 8 }} src={data.downloadInfo.image}></img>
|
||||
<Box sx={{ display: 'grid', gridTemplateRows: '1ft fit-content', gap: 1 }}>
|
||||
<table>
|
||||
<tbody style={{ verticalAlign: 'text-top' }}>
|
||||
<tr>
|
||||
<td>
|
||||
<Typography color='text.primary'>
|
||||
Title:
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>
|
||||
{data.downloadInfo.title}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Typography color='text.primary'>
|
||||
Season:
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>
|
||||
{data.downloadInfo.parent.title}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Typography color='text.primary' sx={{ verticalAlign: 'text-top' }}>
|
||||
Filename:
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>
|
||||
{data.downloadInfo.fileName}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Typography color='text.primary' sx={{ verticalAlign: 'text-top' }}>
|
||||
Progress:
|
||||
</Typography>
|
||||
</td>
|
||||
<td>
|
||||
<Typography color='text.primary'>
|
||||
{`${data.progress.cur} / ${data.progress.total} - ${data.progress.percent}%`}
|
||||
</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Box>
|
||||
<Typography color='text.primary'>
|
||||
{`ETA ${formatTime(data.progress.time)}`}
|
||||
</Typography>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<LinearProgressWithLabel sx={{ height: '10px' }} value={typeof data.progress.percent === 'string' ? parseInt(data.progress.percent) : data.progress.percent} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box> : <Box sx={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Skeleton animation={false} variant="rectangular" sx={{ width: '100%', height: '60%', maxHeight: 200 }}/>
|
||||
<Skeleton animation={false} variant="text"/>
|
||||
<Skeleton animation={false} variant="text" />
|
||||
<Skeleton animation={false} variant="text" />
|
||||
</Box>
|
||||
}
|
||||
|
||||
const formatTime = (time: number) => {
|
||||
let seconds = Math.ceil(time / 1000);
|
||||
let minutes = 0;
|
||||
if (seconds >= 60) {
|
||||
minutes = Math.floor(seconds / 60);
|
||||
seconds = seconds % 60;
|
||||
}
|
||||
|
||||
return `${minutes != 0 ? `${minutes} Minutes ` : ''}${seconds !== 0 ? `${seconds} Seconds` : ''}`
|
||||
|
||||
}
|
||||
|
||||
export default Progress;
|
||||
59
gui/react/src/components/MainFrame/Queue/Queue.tsx
Normal file
59
gui/react/src/components/MainFrame/Queue/Queue.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { Box, Skeleton, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import useStore from "../../../hooks/useStore";
|
||||
|
||||
import useDownloadManager from "../DownloadManager/DownloadManager";
|
||||
|
||||
const Queue: React.FC = () => {
|
||||
const data = useDownloadManager();
|
||||
|
||||
const [{ queue }] = useStore();
|
||||
return data || queue.length > 0 ? <>
|
||||
{data && <Box sx={{ mb: 1, height: 200, display: 'grid', gridTemplateColumns: '20% 1fr', gap: 1 }}>
|
||||
<img src={data.downloadInfo.image} height='200px' width='100%' />
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr fit-content' }}>
|
||||
<Typography variant='h5' color='text.primary'>
|
||||
{data.downloadInfo.title}
|
||||
</Typography>
|
||||
<Typography variant='h5' color='text.primary'>
|
||||
{data.downloadInfo.language.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant='h6' color='text.primary'>
|
||||
{data.downloadInfo.parent.title}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>}
|
||||
{queue.map((queueItem, index) => {
|
||||
return <Box key={`queue_item_${index}`} sx={{ height: 200, display: 'grid', gridTemplateColumns: '20% 1fr', gap: 1, mb: 1, mt: 1 }}>
|
||||
<img src={queueItem.image} height='200px' width='100%' />
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr fit-content' }}>
|
||||
<Typography variant='h5' color='text.primary'>
|
||||
{queueItem.title}
|
||||
</Typography>
|
||||
<Typography variant='h5' color='text.primary'>
|
||||
{queueItem.dubLang.join(', ')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant='h6' color='text.primary'>
|
||||
{queueItem.parent.title}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>;
|
||||
})}
|
||||
</> : <Box sx={{ height: 200, display: 'grid', gridTemplateColumns: '20% 1fr', gap: 1 }}>
|
||||
<Skeleton variant='rectangular' height={'100%'}/>
|
||||
<Box sx={{ display: 'grid', gridTemplateRows: '33% 1fr', gap: 1 }}>
|
||||
<Skeleton variant='text' height={'100%'} />
|
||||
<Skeleton variant='text' height={'100%'} />
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
|
||||
export default Queue;
|
||||
Loading…
Reference in a new issue