JSX component for api requests

This commit is contained in:
lukas 2021-08-01 22:56:24 +02:00
parent 032b90a93d
commit 546f9c34d2
4 changed files with 237 additions and 243 deletions

View File

@ -0,0 +1,48 @@
import React from 'react';
import {APINode, callAPI} from '../utils/Api';
interface Props<T> {
onLoad?: JSX.Element;
render: (data: T, actions: {refresh: () => void}) => JSX.Element;
node: APINode;
action: string;
params?: {[_: string]: string | number | boolean | object};
}
interface State {
loaded: boolean;
}
export default class APIComponent<T> extends React.Component<Props<T>, State> {
state = {
loaded: false
};
data: T | undefined;
render(): JSX.Element {
if (this.state.loaded && this.data !== undefined) {
return this.props.render(this.data, {refresh: () => this.loadData()});
} else {
return this.props.onLoad ? this.props.onLoad : <></>;
}
}
componentDidMount(): void {
this.loadData();
}
componentDidUpdate(prevProps: Readonly<Props<T>>): void {
if (prevProps.params !== this.props.params) {
this.loadData();
}
}
private loadData(): void {
this.setState({loaded: false});
callAPI(this.props.node, {action: this.props.action, ...this.props.params}, (result: T) => {
this.data = result;
this.setState({loaded: true});
});
}
}

View File

@ -0,0 +1,32 @@
import React from 'react';
interface Props {
listenKey: string;
onKey: () => void;
}
export default class KeyComponent extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.handler = this.handler.bind(this);
}
render(): JSX.Element {
return <>{this.props.children}</>;
}
componentDidMount(): void {
document.addEventListener('keyup', this.handler);
}
componentWillUnmount(): void {
document.removeEventListener('keyup', this.handler);
}
private handler(e: KeyboardEvent): void {
if (e.key === this.props.listenKey) {
this.props.onKey();
}
}
}

View File

@ -5,7 +5,7 @@ import VideoContainer from '../../elements/VideoContainer/VideoContainer';
import style from './HomePage.module.css'; import style from './HomePage.module.css';
import PageTitle, {Line} from '../../elements/PageTitle/PageTitle'; import PageTitle, {Line} from '../../elements/PageTitle/PageTitle';
import {APINode, callAPI} from '../../utils/Api'; import {APINode} from '../../utils/Api';
import {Route, Switch, withRouter} from 'react-router-dom'; import {Route, Switch, withRouter} from 'react-router-dom';
import {RouteComponentProps} from 'react-router'; import {RouteComponentProps} from 'react-router';
import SearchHandling from './SearchHandling'; import SearchHandling from './SearchHandling';
@ -13,6 +13,8 @@ import {VideoTypes} from '../../types/ApiTypes';
import {DefaultTags} from '../../types/GeneralTypes'; import {DefaultTags} from '../../types/GeneralTypes';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faSortDown} from '@fortawesome/free-solid-svg-icons'; import {faSortDown} from '@fortawesome/free-solid-svg-icons';
import APIComponent from '../../elements/APIComponent';
import {TagType} from '../../types/VideoTypes';
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
export enum SortBy { export enum SortBy {
@ -26,11 +28,10 @@ export enum SortBy {
interface Props extends RouteComponentProps {} interface Props extends RouteComponentProps {}
interface state { interface state {
sideinfo: VideoTypes.startDataType;
subtitle: string; subtitle: string;
data: VideoTypes.VideoUnloadedType[];
selectionnr: number;
sortby: string; sortby: string;
sortState: SortBy;
tagState: TagType;
} }
/** /**
@ -40,166 +41,130 @@ export class HomePage extends React.Component<Props, state> {
/** keyword variable needed temporary store search keyword */ /** keyword variable needed temporary store search keyword */
keyword = ''; keyword = '';
constructor(props: Props) { state = {
super(props); subtitle: 'All Videos',
sortby: 'Date Added',
this.state = { sortState: SortBy.date,
sideinfo: { tagState: DefaultTags.all
VideoNr: 0, };
FullHdNr: 0,
HDNr: 0,
SDNr: 0,
DifferentTags: 0,
Tagged: 0
},
subtitle: 'All Videos',
data: [],
selectionnr: 0,
sortby: 'Date Added'
};
}
sortState = SortBy.date;
tagState = DefaultTags.all;
componentDidMount(): void {
// initial get of all videos
this.fetchVideoData();
this.fetchStartData();
}
/**
* fetch available videos for specified tag
* this function clears all preview elements an reloads gravity with tag
*
* @param tag tag to fetch videos
*/
fetchVideoData(): void {
callAPI(
APINode.Video,
{action: 'getMovies', Tag: this.tagState.TagId, Sort: this.sortState},
(result: {Videos: VideoTypes.VideoUnloadedType[]; TagName: string}) => {
this.setState({
data: result.Videos,
selectionnr: result.Videos.length
});
}
);
}
/**
* fetch the necessary data for left info box
*/
fetchStartData(): void {
callAPI(APINode.Video, {action: 'getStartData'}, (result: VideoTypes.startDataType) => {
this.setState({sideinfo: result});
});
}
render(): JSX.Element { render(): JSX.Element {
return ( return (
<> <Switch>
<Switch> <Route path='/search/:name'>
<Route path='/search/:name'> <SearchHandling />
<SearchHandling /> </Route>
</Route> <Route path='/'>
<Route path='/'> <APIComponent
<PageTitle title='Home Page' subtitle={this.state.subtitle + ' - ' + this.state.selectionnr}> render={(data: {Videos: VideoTypes.VideoUnloadedType[]; TagName: string}): JSX.Element => (
<form <>
className={'form-inline ' + style.searchform} <PageTitle title='Home Page' subtitle={this.state.subtitle + ' - ' + data.Videos.length}>
onSubmit={(e): void => { <form
e.preventDefault(); className={'form-inline ' + style.searchform}
this.props.history.push('/search/' + this.keyword); onSubmit={(e): void => {
}}> e.preventDefault();
<input this.props.history.push('/search/' + this.keyword);
data-testid='searchtextfield' }}>
className='form-control mr-sm-2' <input
type='text' data-testid='searchtextfield'
placeholder='Search' className='form-control mr-sm-2'
onChange={(e): void => { type='text'
this.keyword = e.target.value; placeholder='Search'
}} onChange={(e): void => {
/> this.keyword = e.target.value;
<button data-testid='searchbtnsubmit' className='btn btn-success' type='submit'> }}
Search />
</button> <button data-testid='searchbtnsubmit' className='btn btn-success' type='submit'>
</form> Search
</PageTitle> </button>
<SideBar> </form>
<SideBarTitle>Infos:</SideBarTitle> </PageTitle>
<Line /> <SideBar>
<SideBarItem> <APIComponent
<b>{this.state.sideinfo.VideoNr}</b> Videos Total! render={(sidebardata: VideoTypes.startDataType): JSX.Element => (
</SideBarItem> <>
<SideBarItem> <SideBarTitle>Infos:</SideBarTitle>
<b>{this.state.sideinfo.FullHdNr}</b> FULL-HD Videos! <Line />
</SideBarItem> <SideBarItem>
<SideBarItem> <b>{sidebardata.VideoNr}</b> Videos Total!
<b>{this.state.sideinfo.HDNr}</b> HD Videos! </SideBarItem>
</SideBarItem> <SideBarItem>
<SideBarItem> <b>{sidebardata.FullHdNr}</b> FULL-HD Videos!
<b>{this.state.sideinfo.SDNr}</b> SD Videos! </SideBarItem>
</SideBarItem> <SideBarItem>
<SideBarItem> <b>{sidebardata.HDNr}</b> HD Videos!
<b>{this.state.sideinfo.DifferentTags}</b> different Tags! </SideBarItem>
</SideBarItem> <SideBarItem>
<Line /> <b>{sidebardata.SDNr}</b> SD Videos!
<SideBarTitle>Default Tags:</SideBarTitle> </SideBarItem>
<Tag <SideBarItem>
tagInfo={{TagName: 'All', TagId: DefaultTags.all.TagId}} <b>{sidebardata.DifferentTags}</b> different Tags!
onclick={(): void => { </SideBarItem>
this.tagState = DefaultTags.all; <Line />
this.fetchVideoData(); <SideBarTitle>Default Tags:</SideBarTitle>
this.setState({subtitle: 'All Videos'}); <Tag
}} tagInfo={{TagName: 'All', TagId: DefaultTags.all.TagId}}
/> onclick={(): void => {
<Tag this.setState({tagState: DefaultTags.all, subtitle: 'All Videos'});
tagInfo={{TagName: 'Full Hd', TagId: DefaultTags.fullhd.TagId}} }}
onclick={(): void => { />
this.tagState = DefaultTags.fullhd; <Tag
this.fetchVideoData(); tagInfo={{TagName: 'Full Hd', TagId: DefaultTags.fullhd.TagId}}
this.setState({subtitle: 'Full Hd Videos'}); onclick={(): void => {
}} this.setState({tagState: DefaultTags.fullhd, subtitle: 'Full Hd Videos'});
/> }}
<Tag />
tagInfo={{TagName: 'Low Quality', TagId: DefaultTags.lowq.TagId}} <Tag
onclick={(): void => { tagInfo={{TagName: 'Low Quality', TagId: DefaultTags.lowq.TagId}}
this.tagState = DefaultTags.lowq; onclick={(): void => {
this.fetchVideoData(); this.setState({
this.setState({subtitle: 'Low Quality Videos'}); tagState: DefaultTags.lowq,
}} subtitle: 'Low Quality Videos'
/> });
<Tag }}
tagInfo={{TagName: 'HD', TagId: DefaultTags.hd.TagId}} />
onclick={(): void => { <Tag
this.tagState = DefaultTags.hd; tagInfo={{TagName: 'HD', TagId: DefaultTags.hd.TagId}}
this.fetchVideoData(); onclick={(): void => {
this.setState({subtitle: 'HD Videos'}); this.setState({tagState: DefaultTags.hd, subtitle: 'HD Videos'});
}} }}
/> />
</SideBar> </>
<div> )}
<span className={style.sortbyLabel}>Sort By: </span> node={APINode.Video}
<div className={style.dropdown}> action='getStartData'
<span className={style.dropbtn}> />
<span>{this.state.sortby}</span> </SideBar>
<FontAwesomeIcon style={{marginLeft: 3, paddingBottom: 3}} icon={faSortDown} size='1x' /> <div>
</span> <span className={style.sortbyLabel}>Sort By: </span>
<div className={style.dropdownContent}> <div className={style.dropdown}>
<span onClick={(): void => this.onDropDownItemClick(SortBy.date, 'Date Added')}>Date Added</span> <span className={style.dropbtn}>
<span onClick={(): void => this.onDropDownItemClick(SortBy.likes, 'Most likes')}>Most likes</span> <span>{this.state.sortby}</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.random, 'Random')}>Random</span> <FontAwesomeIcon style={{marginLeft: 3, paddingBottom: 3}} icon={faSortDown} size='1x' />
<span onClick={(): void => this.onDropDownItemClick(SortBy.name, 'Name')}>Name</span> </span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.length, 'Length')}>Length</span> <div className={style.dropdownContent}>
<span onClick={(): void => this.onDropDownItemClick(SortBy.date, 'Date Added')}>
Date Added
</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.likes, 'Most likes')}>
Most likes
</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.random, 'Random')}>Random</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.name, 'Name')}>Name</span>
<span onClick={(): void => this.onDropDownItemClick(SortBy.length, 'Length')}>Length</span>
</div>
</div>
</div> </div>
</div> <VideoContainer data={data.Videos} />
</div> <div className={style.rightinfo} />
</>
<VideoContainer data={this.state.data} /> )}
<div className={style.rightinfo} /> node={APINode.Video}
</Route> action='getMovies'
</Switch> params={{Tag: this.state.tagState.TagId, Sort: this.state.sortState}}
</> />
</Route>
</Switch>
); );
} }
@ -209,9 +174,7 @@ export class HomePage extends React.Component<Props, state> {
* @param name new header title * @param name new header title
*/ */
onDropDownItemClick(type: SortBy, name: string): void { onDropDownItemClick(type: SortBy, name: string): void {
this.sortState = type; this.setState({sortby: name, sortState: type});
this.setState({sortby: name});
this.fetchVideoData();
} }
} }

View File

@ -4,15 +4,11 @@ import SideBar, {SideBarTitle} from '../../elements/SideBar/SideBar';
import Tag from '../../elements/Tag/Tag'; import Tag from '../../elements/Tag/Tag';
import PageTitle from '../../elements/PageTitle/PageTitle'; import PageTitle from '../../elements/PageTitle/PageTitle';
import VideoContainer from '../../elements/VideoContainer/VideoContainer'; import VideoContainer from '../../elements/VideoContainer/VideoContainer';
import {APINode, callAPI} from '../../utils/Api'; import {APINode} from '../../utils/Api';
import {TagType} from '../../types/VideoTypes'; import {TagType} from '../../types/VideoTypes';
import {VideoTypes} from '../../types/ApiTypes'; import {VideoTypes} from '../../types/ApiTypes';
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler'; import APIComponent from '../../elements/APIComponent';
import KeyComponent from '../../elements/KeyComponent';
interface state {
videos: VideoTypes.VideoUnloadedType[];
tags: TagType[];
}
interface GetRandomMoviesType { interface GetRandomMoviesType {
Videos: VideoTypes.VideoUnloadedType[]; Videos: VideoTypes.VideoUnloadedType[];
@ -22,88 +18,43 @@ interface GetRandomMoviesType {
/** /**
* Randompage shuffles random viedeopreviews and provides a shuffle btn * Randompage shuffles random viedeopreviews and provides a shuffle btn
*/ */
class RandomPage extends React.Component<{}, state> { class RandomPage extends React.Component {
readonly LoadNR = 3; readonly LoadNR = 3;
constructor(props: {}) {
super(props);
this.state = {
videos: [],
tags: []
};
this.keypress = this.keypress.bind(this);
}
componentDidMount(): void {
addKeyHandler(this.keypress);
this.loadShuffledvideos(this.LoadNR);
}
componentWillUnmount(): void {
removeKeyHandler(this.keypress);
}
render(): JSX.Element { render(): JSX.Element {
return ( return (
<div> <div>
<PageTitle title='Random Videos' subtitle='4pc' /> <PageTitle title='Random Videos' subtitle={this.LoadNR + 'pcs'} />
<APIComponent
render={(data: GetRandomMoviesType, actions): JSX.Element => (
<KeyComponent listenKey='s' onKey={actions.refresh}>
<SideBar>
<SideBarTitle>Visible Tags:</SideBarTitle>
{data.Tags.map((m) => (
<Tag key={m.TagId} tagInfo={m} />
))}
</SideBar>
<SideBar> {data.Videos.length !== 0 ? (
<SideBarTitle>Visible Tags:</SideBarTitle> <VideoContainer data={data.Videos}>
{this.state.tags.map((m) => ( <div className={style.Shufflebutton}>
<Tag key={m.TagId} tagInfo={m} /> <button onClick={actions.refresh} className={style.btnshuffle}>
))} Shuffle
</SideBar> </button>
</div>
{this.state.videos.length !== 0 ? ( </VideoContainer>
<VideoContainer data={this.state.videos}> ) : (
<div className={style.Shufflebutton}> <div>No Data found!</div>
<button onClick={(): void => this.shuffleclick()} className={style.btnshuffle}> )}
Shuffle </KeyComponent>
</button> )}
</div> node={APINode.Video}
</VideoContainer> action='getRandomMovies'
) : ( params={{Number: this.LoadNR}}
<div>No Data found!</div> />
)}
</div> </div>
); );
} }
/**
* click handler for shuffle btn
*/
shuffleclick(): void {
this.loadShuffledvideos(this.LoadNR);
}
/**
* load random videos from backend
* @param nr number of videos to load
*/
loadShuffledvideos(nr: number): void {
callAPI<GetRandomMoviesType>(APINode.Video, {action: 'getRandomMovies', Number: nr}, (result) => {
this.setState({videos: []}); // needed to trigger rerender of main videoview
this.setState({
videos: result.Videos,
tags: result.Tags
});
});
}
/**
* key event handling
* @param event keyevent
*/
private keypress(event: KeyboardEvent): void {
// bind s to shuffle
if (event.key === 's') {
this.loadShuffledvideos(4);
}
}
} }
export default RandomPage; export default RandomPage;