Search bar

This commit is contained in:
Izuco 2022-01-27 20:55:21 +01:00
parent 75888c23a5
commit 71f7eb2a69
No known key found for this signature in database
GPG key ID: E9CBE9E4EF3A1BFA
10 changed files with 134 additions and 25 deletions

View file

@ -1,12 +1,22 @@
import { HLSCallback } from 'hls-download';
import type { FunimationShow as FunimationSearch } from './funiSearch';
import type { FunimationSearch } from './funiSearch';
import type { AvailableMuxer } from '../modules/module.args';
export interface MessageHandler {
auth: (data: AuthData) => Promise<AuthResponse>;
checkToken: () => Promise<CheckTokenResponse>;
search: (data: SearchData) => Promise<SearchResponse>
}
export type SearchResponse = ResponseBase<{
image: string,
name: string,
desc?: string,
id: string,
lang?: string[],
rating: number
}[]>
export type FuniEpisodeData = {
title: string,
episode: string,
@ -14,7 +24,7 @@ export type FuniEpisodeData = {
};
export type AuthData = { username: string, password: string };
export type FuniSearchData = { search: string };
export type SearchData = { search: string };
export type FuniGetShowData = { id: number, e?: string, but: boolean, all: boolean };
export type FuniGetEpisodeData = { subs: FuniSubsData, fnSlug: FuniEpisodeData, callback?: HLSCallback, simul?: boolean; dubLang: string[], s: string }
export type FuniStreamData = { q: number, callback?: HLSCallback, x: number, fileName: string, numbers: number, novids?: boolean,

22
funi.ts
View file

@ -37,7 +37,7 @@ import { FunimationMediaDownload } from './@types/funiTypes';
import * as langsData from './modules/module.langsData';
import { TitleElement } from './@types/episode';
import { AvailableFilenameVars } from './modules/module.args';
import { AuthData, AuthResponse, CheckTokenResponse, FuniGetEpisodeData, FuniGetEpisodeResponse, FuniGetShowData, FuniSearchData, FuniSearchReponse, FuniShowResponse, FuniStreamData, FuniSubsData } from './@types/messageHandler';
import { AuthData, AuthResponse, CheckTokenResponse, FuniGetEpisodeData, FuniGetEpisodeResponse, FuniGetShowData, SearchData, FuniSearchReponse, FuniShowResponse, FuniStreamData, FuniSubsData } from './@types/messageHandler';
// check page
// fn variables
@ -104,23 +104,7 @@ export default class Funi {
for (const episodeData of data.value) {
if ((await this.getEpisode(true, { subs: { dlsubs: argv.dlsubs, nosubs: argv.nosubs, sub: false }, dubLang: argv.dubLang, fnSlug: episodeData, s: argv.s, simul: argv.simul }, {
ass: false,
fileName: argv.fileName,
fontSize: argv.fontSize,
fontName: argv.fontName,
forceMuxer: argv.forceMuxer,
fsRetryTime: argv.fsRetryTime,
mp4: argv.mp4,
noaudio: argv.noaudio,
nocleanup: argv.nocleanup,
novids: argv.novids,
numbers: argv.numbers,
partsize: argv.partsize,
q: argv.q,
simul: argv.simul,
skipSubMux: argv.skipSubMux,
skipmux: argv.skipmux,
timeout: argv.timeout,
x: argv.x
...argv
})).isOk !== true)
ok = false;
}
@ -159,7 +143,7 @@ export default class Funi {
return { isOk: false, reason: new Error('Login request failed') }
}
public async searchShow(log: boolean, data: FuniSearchData): Promise<FuniSearchReponse> {
public async searchShow(log: boolean, data: SearchData): Promise<FuniSearchReponse> {
const qs = {unique: true, limit: 100, q: data.search, offset: 0 };
const searchData = await getData({
baseUrl: api_host,

View file

@ -15,4 +15,5 @@ export default () => {
ipcMain.handle('auth', async (_, data) => handler?.auth(data));
ipcMain.handle('checkToken', async () => handler?.checkToken());
ipcMain.handle('search', async (_, data) => handler?.search(data));
}

View file

@ -1,5 +1,4 @@
import { AuthData, AuthResponse, CheckTokenResponse, MessageHandler } from "../../../../@types/messageHandler";
import { AuthData, CheckTokenResponse, MessageHandler, SearchData, SearchResponse } from "../../../../@types/messageHandler";
import Funimation from '../../../../funi';
class FunimationHandler implements MessageHandler {
@ -8,6 +7,20 @@ class FunimationHandler implements MessageHandler {
this.funi = new Funimation();
}
public async search(data: SearchData): Promise<SearchResponse> {
const funiSearch = await this.funi.searchShow(false, data);
if (!funiSearch.isOk)
return funiSearch;
return { isOk: true, value: funiSearch.value.items.hits.map(a => ({
image: a.image.showThumbnail,
name: a.title,
desc: a.description,
id: a.id,
lang: a.languages,
rating: a.starRating
})) }
}
public async checkToken(): Promise<CheckTokenResponse> {
return this.funi.checkToken();
}

View file

@ -1,12 +1,14 @@
import React from "react";
import AuthButton from "./components/AuthButton";
import { Box } from "@mui/material";
import MainFrame from "./components/MainFrame/MainFrame";
const Layout: React.FC = () => {
return <Box>
<Box sx={{ height: 50, mb: 4 }}>
<AuthButton />
</Box>
<MainFrame />
</Box>;
}

View file

@ -0,0 +1,4 @@
.divider-width::before, .divider-width::after {
border-top: 3px solid white !important;
transform: translateY(40%) !important;
}

View file

@ -0,0 +1,13 @@
import { Box, Divider } from "@mui/material";
import React from "react";
import './MainFrame.css';
import SearchBox from "./SearchBox";
const MainFrame: React.FC = () => {
return <Box sx={{ border: '2px solid white', width: '75%' }}>
<SearchBox />
<Divider className="divider-width" light sx={{ color: 'text.primary'}}>Text</Divider>
</Box>
}
export default MainFrame;

View file

@ -0,0 +1,8 @@
.listitem-hover:hover {
-webkit-filter: brightness(70%);
filter: brightness(70%);
}
.listitem-hover {
transition: filter 0.1s ease-in;
}

View file

@ -0,0 +1,73 @@
import { Image } from "@mui/icons-material";
import { Box, Button, Divider, List, ListItem, ListItemText, Paper, Popover, TextField, Typography } from "@mui/material";
import React from "react";
import { SearchResponse } from "../../../../../@types/messageHandler";
import { messageChannelContext } from "../../provider/MessageChannel";
import Require from "../Require";
import './SearchBox.css';
const SearchBox: React.FC = () => {
const messageHandler = React.useContext(messageChannelContext);
const [search, setSearch] = React.useState('');
const [searchResult, setSearchResult] = React.useState<undefined|SearchResponse>();
const anchor = React.useRef<HTMLDivElement>(null);
const selectItem = (id: string) => {
window.alert(id); //TODO change
};
React.useEffect(() => {
(async () => {
if (search.trim().length === 0)
return setSearchResult({ isOk: true, value: [] });
const s = await messageHandler?.search({search});
if (s && s.isOk)
s.value = s.value.slice(0, 10);
setSearchResult(s);
})();
}, [search]);
const anchorBounding = anchor.current?.getBoundingClientRect();
return <Box sx={{ mt: 2, mb: 2 }}>
<TextField ref={anchor} value={search} onChange={e => setSearch(e.target.value)} variant='outlined' label='Search' sx={{ width: 'calc(100% - 50px)', left: 25 }} />
{searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 &&
<Paper sx={{ position: 'absolute', maxHeight: '50%', width: `${anchorBounding?.width}px`,
left: anchorBounding?.x, top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0), zIndex: 99, overflowY: 'scroll'}}>
<List>
{searchResult && searchResult.isOk ?
searchResult.value.map((a, ind, arr) => {
return <>
<ListItem className='listitem-hover' key={a.id} onClick={() => selectItem(a.id)}>
<Box sx={{ display: 'flex' }}>
<Box sx={{ width: '20%', height: '100%', pr: 2 }}>
<img src={a.image} style={{ width: '100%', height: '100%' }}/>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', maxWidth: '70%' }}>
<Typography variant='h6' component='h6' color='text.primary' sx={{ }}>
{a.name}
</Typography>
{a.desc && <Typography variant='caption' component='p' color='text.primary' sx={{ pt: 1, pb: 1 }}>
{a.desc}
</Typography>}
{a.lang && <Typography variant='caption' component='p' color='text.primary' sx={{ }}>
Languages: {a.lang.join(', ')}
</Typography>}
<Typography variant='caption' component='p' color='text.primary' sx={{ }}>
ID: {a.id}
</Typography>
</Box>
</Box>
</ListItem>
{(ind < arr.length - 1) && <Divider key={`${a.id}Divider`} />}
</>
})
: <></>}
</List>
</Paper>}
</Box>
}
export default SearchBox;

View file

@ -1,5 +1,5 @@
import React from 'react';
import type { AuthData, AuthResponse, MessageHandler, ResponseBase } from '../../../../@types/messageHandler';
import type { AuthData, AuthResponse, MessageHandler, ResponseBase, SearchData } from '../../../../@types/messageHandler';
import { serviceContext } from './ServiceProvider';
import type { IpcRenderer } from "electron";
@ -17,7 +17,8 @@ const MessageChannelProvider: React.FC = ({ children }) => {
const messageHandler: MessageHandler = {
auth: async (data) => await ipcRenderer.invoke('auth', data),
checkToken: async () => await ipcRenderer.invoke('checkToken')
checkToken: async () => await ipcRenderer.invoke('checkToken'),
search: async (data) => await ipcRenderer.invoke('search', data)
}
return <messageChannelContext.Provider value={messageHandler}>