Started Layout - Working on Queue

This commit is contained in:
Izuco 2022-03-20 13:47:17 +01:00
parent 84da65120f
commit 9c5095f7a8
No known key found for this signature in database
GPG key ID: E9CBE9E4EF3A1BFA
16 changed files with 131 additions and 159 deletions

View file

@ -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
}

View file

@ -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){

View file

@ -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({

View file

@ -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;

View file

@ -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,
};

View file

@ -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>;

View 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;

View file

@ -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>
};

View file

@ -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 = () => {

View file

@ -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 ?

View file

@ -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;

View file

@ -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;

View file

@ -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>
}

View file

@ -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;

View 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;