Search bar
This commit is contained in:
parent
75888c23a5
commit
71f7eb2a69
10 changed files with 134 additions and 25 deletions
14
@types/messageHandler.d.ts
vendored
14
@types/messageHandler.d.ts
vendored
|
|
@ -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
22
funi.ts
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
|||
4
gui/react/src/components/MainFrame/MainFrame.css
Normal file
4
gui/react/src/components/MainFrame/MainFrame.css
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.divider-width::before, .divider-width::after {
|
||||
border-top: 3px solid white !important;
|
||||
transform: translateY(40%) !important;
|
||||
}
|
||||
13
gui/react/src/components/MainFrame/MainFrame.tsx
Normal file
13
gui/react/src/components/MainFrame/MainFrame.tsx
Normal 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;
|
||||
8
gui/react/src/components/MainFrame/SearchBox.css
Normal file
8
gui/react/src/components/MainFrame/SearchBox.css
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.listitem-hover:hover {
|
||||
-webkit-filter: brightness(70%);
|
||||
filter: brightness(70%);
|
||||
}
|
||||
|
||||
.listitem-hover {
|
||||
transition: filter 0.1s ease-in;
|
||||
}
|
||||
73
gui/react/src/components/MainFrame/SearchBox.tsx
Normal file
73
gui/react/src/components/MainFrame/SearchBox.tsx
Normal 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;
|
||||
|
|
@ -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}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue