implement full load of videos and startdata

modify api where necessary
This commit is contained in:
2021-02-23 16:01:29 +00:00
parent 2967aee16d
commit f2b5fb6587
69 changed files with 14016 additions and 21001 deletions

View File

@ -45,14 +45,16 @@ class App extends React.Component<{}, state> {
// set theme
GlobalInfos.enableDarkTheme(result.DarkMode);
GlobalInfos.setVideoPath(result.VideoPath);
this.setState({
generalSettingsLoaded: true,
passwordsupport: result.passwordEnabled,
mediacentername: result.mediacenter_name,
passwordsupport: result.Password,
mediacentername: result.Mediacenter_name,
onapierror: false
});
// set tab title to received mediacenter name
document.title = result.mediacenter_name;
document.title = result.Mediacenter_name;
}, error => {
this.setState({onapierror: true});
});

View File

@ -4,13 +4,13 @@ import ActorTile from './ActorTile';
describe('<ActorTile/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<ActorTile actor={{thumbnail: '-1', name: 'testname', id: 3}}/>);
const wrapper = shallow(<ActorTile actor={{Thumbnail: '-1', Name: 'testname', id: 3}}/>);
wrapper.unmount();
});
it('simulate click with custom handler', function () {
const func = jest.fn((_) => {});
const wrapper = shallow(<ActorTile actor={{thumbnail: '-1', name: 'testname', id: 3}} onClick={() => func()}/>);
const wrapper = shallow(<ActorTile actor={{Thumbnail: '-1', Name: 'testname', id: 3}} onClick={() => func()}/>);
const func1 = jest.fn();
prepareViewBinding(func1);

View File

@ -22,7 +22,7 @@ class ActorTile extends React.Component<props> {
return this.renderActorTile(this.props.onClick);
} else {
return (
<Link to={{pathname: '/actors/' + this.props.actor.actor_id}}>
<Link to={{pathname: '/actors/' + this.props.actor.ActorId}}>
{this.renderActorTile(() => {
})}
</Link>
@ -34,11 +34,11 @@ class ActorTile extends React.Component<props> {
return (
<div className={style.actortile} onClick={(): void => customclickhandler(this.props.actor)}>
<div className={style.actortile_thumbnail}>
{this.props.actor.thumbnail === null ? <FontAwesomeIcon style={{
{this.props.actor.Thumbnail === '' ? <FontAwesomeIcon style={{
lineHeight: '130px'
}} icon={faUser} size='5x'/> : 'dfdf' /* todo render picture provided here! */}
</div>
<div className={style.actortile_name}>{this.props.actor.name}</div>
<div className={style.actortile_name}>{this.props.actor.Name}</div>
</div>
);
}

View File

@ -31,7 +31,7 @@ describe('<AddActorPopup/>', function () {
});
it('test api call and insertion of actor tiles', function () {
global.callAPIMock([{id: 1, name: 'test'}, {id: 2, name: 'test2'}]);
global.callAPIMock([{Id: 1, Name: 'test'}, {Id: 2, Name: 'test2'}]);
const wrapper = shallow(<AddActorPopup/>);
@ -44,7 +44,7 @@ describe('<AddActorPopup/>', function () {
global.callAPIMock({result: 'success'});
wrapper.setState({actors: [{actor_id: 1, name: 'test'}]}, () => {
wrapper.setState({actors: [{ActorId: 1, Name: 'test'}]}, () => {
wrapper.find('ActorTile').dive().simulate('click');
expect(callAPI).toHaveBeenCalledTimes(1);
@ -59,7 +59,7 @@ describe('<AddActorPopup/>', function () {
global.callAPIMock({result: 'nosuccess'});
wrapper.setState({actors: [{actor_id: 1, name: 'test'}]}, () => {
wrapper.setState({actors: [{ActorId: 1, Name: 'test'}]}, () => {
wrapper.find('ActorTile').dive().simulate('click');
expect(callAPI).toHaveBeenCalledTimes(1);
@ -80,7 +80,7 @@ describe('<AddActorPopup/>', function () {
callAPIMock({});
wrapper.setState({actors: [{name: 'test', actor_id: 1}]});
wrapper.setState({actors: [{Name: 'test', ActorId: 1}]});
wrapper.find('PopupBase').props().ParentSubmit();

View File

@ -134,8 +134,8 @@ class AddActorPopup extends React.Component<props, state> {
// fetch the available actors
callAPI<GeneralSuccess>(APINode.Actor, {
action: 'addActorToVideo',
actorid: actor.actor_id,
videoid: this.props.movie_id
ActorId: actor.ActorId,
MovieId: this.props.movie_id
}, result => {
if (result.result === 'success') {
// return back to player page
@ -170,7 +170,7 @@ class AddActorPopup extends React.Component<props, state> {
* @param actor
*/
private filterSearch(actor: ActorType): boolean {
return actor.name.toLowerCase().includes(this.state.filter.toLowerCase());
return actor.Name.toLowerCase().includes(this.state.filter.toLowerCase());
}
/**

View File

@ -14,7 +14,7 @@ describe('<AddTagPopup/>', function () {
it('test tag insertion', function () {
const wrapper = shallow(<AddTagPopup/>);
wrapper.setState({
items: [{tag_id: 1, tag_name: 'test'}, {tag_id: 2, tag_name: 'ee'}]
items: [{TagId: 1, TagName: 'test'}, {TagId: 2, TagName: 'ee'}]
}, () => {
expect(wrapper.find('Tag')).toHaveLength(2);
expect(wrapper.find('Tag').first().dive().text()).toBe('test');

View File

@ -39,7 +39,7 @@ class AddTagPopup extends React.Component<props, state> {
this.state.items.map((i) => (
<Tag tagInfo={i}
onclick={(): void => {
this.props.submit(i.tag_id, i.tag_name);
this.props.submit(i.TagId, i.TagName);
this.props.onHide();
}}/>
)) : null}

View File

@ -29,7 +29,7 @@ class NewTagPopup extends React.Component<props> {
* store the filled in form to the backend
*/
storeselection(): void {
callAPI(APINode.Tags, {action: 'createTag', tagname: this.value}, (result: GeneralSuccess) => {
callAPI(APINode.Tags, {action: 'createTag', TagName: this.value}, (result: GeneralSuccess) => {
if (result.result !== 'success') {
console.log('error occured while writing to db -- todo error handling');
console.log(result.result);

View File

@ -6,12 +6,12 @@ import {shallow} from 'enzyme';
describe('<Tag/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<Tag tagInfo={{tag_name: 'testname', tag_id: 1}}/>);
const wrapper = shallow(<Tag tagInfo={{TagName: 'testname', TagId: 1}}/>);
wrapper.unmount();
});
it('renders childs correctly', function () {
const wrapper = shallow(<Tag tagInfo={{tag_name: 'test', tag_id: 1}}/>);
const wrapper = shallow(<Tag tagInfo={{TagName: 'test', TagId: 1}}/>);
expect(wrapper.children().text()).toBe('test');
});
@ -19,7 +19,7 @@ describe('<Tag/>', function () {
const func = jest.fn();
const wrapper = shallow(<Tag
tagInfo={{tag_name: 'test', tag_id: 1}}
tagInfo={{TagName: 'test', TagId: 1}}
onclick={() => {func();}}>test</Tag>);
expect(func).toBeCalledTimes(0);

View File

@ -18,7 +18,7 @@ class Tag extends React.Component<props> {
return this.renderButton();
} else {
return (
<Link to={'/categories/' + this.props.tagInfo.tag_id}>
<Link to={'/categories/' + this.props.tagInfo.TagId}>
{this.renderButton()}
</Link>
);
@ -28,7 +28,7 @@ class Tag extends React.Component<props> {
renderButton(): JSX.Element {
return (
<button className={styles.tagbtn} onClick={(): void => this.TagClick()}
data-testid='Test-Tag'>{this.props.tagInfo.tag_name}</button>
data-testid='Test-Tag'>{this.props.tagInfo.TagName}</button>
);
}
@ -38,7 +38,7 @@ class Tag extends React.Component<props> {
TagClick(): void {
if (this.props.onclick) {
// call custom onclick handling
this.props.onclick(this.props.tagInfo.tag_name); // todo check if param is neccessary
this.props.onclick(this.props.tagInfo.TagName); // todo check if param is neccessary
return;
}
}

View File

@ -40,9 +40,9 @@ class VideoContainer extends React.Component<props, state> {
<div className={style.maincontent}>
{this.state.loadeditems.map(elem => (
<Preview
key={elem.movie_id}
name={elem.movie_name}
movie_id={elem.movie_id}/>
key={elem.MovieId}
name={elem.MovieName}
movie_id={elem.MovieId}/>
))}
{/*todo css for no items to show*/}
{this.state.loadeditems.length === 0 ?

View File

@ -38,7 +38,7 @@ class ActorOverviewPage extends React.Component<props, state> {
<Button title='Add Actor' onClick={(): void => this.setState({NActorPopupVisible: true})}/>
</SideBar>
<div className={style.container}>
{this.state.actors.map((el) => (<ActorTile key={el.actor_id} actor={el}/>))}
{this.state.actors.map((el) => (<ActorTile key={el.ActorId} actor={el}/>))}
</div>
{this.state.NActorPopupVisible ?
<NewActorPopup onHide={(): void => {

View File

@ -10,13 +10,13 @@ describe('<ActorPage/>', function () {
it('fetch infos', function () {
callAPIMock({
videos: [{
movie_id: 0,
movie_name: 'test'
}], info: {
thumbnail: '',
name: '',
actor_id: 0
Videos: [{
MovieId: 0,
MovieName: 'test'
}], Info: {
Thumbnail: '',
Name: '',
ActorId: 0
}
});

View File

@ -31,13 +31,13 @@ export class ActorPage extends React.Component<props, state> {
constructor(props: props) {
super(props);
this.state = {data: [], actor: {actor_id: 0, name: '', thumbnail: ''}};
this.state = {data: [], actor: {ActorId: 0, Name: '', Thumbnail: ''}};
}
render(): JSX.Element {
return (
<>
<PageTitle title={this.state.actor.name} subtitle={this.state.data ? this.state.data.length + ' videos' : null}>
<PageTitle title={this.state.actor.Name} subtitle={this.state.data ? this.state.data.length + ' videos' : null}>
<span className={style.overviewbutton}>
<Link to='/actors'>
<Button onClick={(): void => {}} title='Go to Actor overview'/>
@ -68,11 +68,11 @@ export class ActorPage extends React.Component<props, state> {
getActorInfo(): void {
callAPI(APINode.Actor, {
action: 'getActorInfo',
actorid: this.props.match.params.id
ActorId: parseInt(this.props.match.params.id)
}, (result: ActorTypes.videofetchresult) => {
this.setState({
data: result.videos ? result.videos : [],
actor: result.info
data: result.Videos ? result.Videos : [],
actor: result.Info
});
});
}

View File

@ -103,8 +103,8 @@ export class CategoryView extends React.Component<CategoryViewProps, CategoryVie
private deleteTag(force: boolean): void {
callAPI<GeneralSuccess>(APINode.Tags, {
action: 'deleteTag',
tagId: parseInt(this.props.match.params.id),
force: force
TagId: parseInt(this.props.match.params.id),
Force: force
}, result => {
console.log(result.result);
if (result.result === 'success') {

View File

@ -54,8 +54,8 @@ class TagView extends React.Component<props, TagViewState> {
<div className={videocontainerstyle.maincontent}>
{this.state.loadedtags ?
this.state.loadedtags.map((m) => (
<Link to={'/categories/' + m.tag_id} key={m.tag_id}>
<TagPreview name={m.tag_name}/></Link>
<Link to={'/categories/' + m.TagId} key={m.TagId}>
<TagPreview name={m.TagName}/></Link>
)) :
'loading'}
</div>

View File

@ -10,17 +10,12 @@ import {Route, Switch, withRouter} from 'react-router-dom';
import {RouteComponentProps} from 'react-router';
import SearchHandling from './SearchHandling';
import {VideoTypes} from '../../types/ApiTypes';
import {DefaultTags} from "../../types/GeneralTypes";
interface props extends RouteComponentProps {}
interface state {
sideinfo: {
videonr: number,
fullhdvideonr: number,
hdvideonr: number,
sdvideonr: number,
tagnr: number
},
sideinfo: VideoTypes.startDataType
subtitle: string,
data: VideoTypes.VideoUnloadedType[],
selectionnr: number
@ -38,11 +33,12 @@ export class HomePage extends React.Component<props, state> {
this.state = {
sideinfo: {
videonr: 0,
fullhdvideonr: 0,
hdvideonr: 0,
sdvideonr: 0,
tagnr: 0
VideoNr: 0,
FullHdNr: 0,
HDNr: 0,
SDNr: 0,
DifferentTags: 0,
Tagged: 0,
},
subtitle: 'All Videos',
data: [],
@ -52,7 +48,7 @@ export class HomePage extends React.Component<props, state> {
componentDidMount(): void {
// initial get of all videos
this.fetchVideoData('All');
this.fetchVideoData(DefaultTags.all.TagId);
this.fetchStartData();
}
@ -62,7 +58,7 @@ export class HomePage extends React.Component<props, state> {
*
* @param tag tag to fetch videos
*/
fetchVideoData(tag: string): void {
fetchVideoData(tag: number): void {
callAPI(APINode.Video, {action: 'getMovies', tag: tag}, (result: VideoTypes.VideoUnloadedType[]) => {
this.setState({
data: []
@ -70,7 +66,6 @@ export class HomePage extends React.Component<props, state> {
this.setState({
data: result,
selectionnr: result.length,
subtitle: `${tag} Videos`
});
});
}
@ -80,15 +75,7 @@ export class HomePage extends React.Component<props, state> {
*/
fetchStartData(): void {
callAPI(APINode.Video, {action: 'getStartData'}, (result: VideoTypes.startDataType) => {
this.setState({
sideinfo: {
videonr: result['total'],
fullhdvideonr: result['fullhd'],
hdvideonr: result['hd'],
sdvideonr: result['sd'],
tagnr: result['tags']
}
});
this.setState({sideinfo: result});
});
}
@ -119,17 +106,30 @@ export class HomePage extends React.Component<props, state> {
<SideBar>
<SideBarTitle>Infos:</SideBarTitle>
<Line/>
<SideBarItem><b>{this.state.sideinfo.videonr}</b> Videos Total!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.fullhdvideonr}</b> FULL-HD Videos!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.hdvideonr}</b> HD Videos!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.sdvideonr}</b> SD Videos!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.tagnr}</b> different Tags!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.VideoNr}</b> Videos Total!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.FullHdNr}</b> FULL-HD Videos!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.HDNr}</b> HD Videos!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.SDNr}</b> SD Videos!</SideBarItem>
<SideBarItem><b>{this.state.sideinfo.DifferentTags}</b> different Tags!</SideBarItem>
<Line/>
<SideBarTitle>Default Tags:</SideBarTitle>
<Tag tagInfo={{tag_name: 'All', tag_id: -1}} onclick={(): void => this.fetchVideoData('All')}/>
<Tag tagInfo={{tag_name: 'FullHd', tag_id: -1}} onclick={(): void => this.fetchVideoData('FullHd')}/>
<Tag tagInfo={{tag_name: 'LowQuality', tag_id: -1}} onclick={(): void => this.fetchVideoData('LowQuality')}/>
<Tag tagInfo={{tag_name: 'HD', tag_id: -1}} onclick={(): void => this.fetchVideoData('HD')}/>
<Tag tagInfo={{TagName: 'All', TagId: DefaultTags.all.TagId}} onclick={(): void => {
this.fetchVideoData(DefaultTags.all.TagId);
this.setState({subtitle: `All Videos`});
}}/>
<Tag tagInfo={{TagName: 'Full Hd', TagId: DefaultTags.fullhd.TagId}} onclick={(): void => {
this.fetchVideoData(DefaultTags.fullhd.TagId);
this.setState({subtitle: `Full Hd Videos`});
}}/>
<Tag tagInfo={{TagName: 'Low Quality', TagId: DefaultTags.lowq.TagId}}
onclick={(): void => {
this.fetchVideoData(DefaultTags.lowq.TagId);
this.setState({subtitle: `Low Quality Videos`});
}}/>
<Tag tagInfo={{TagName: 'HD', TagId: DefaultTags.hd.TagId}} onclick={(): void => {
this.fetchVideoData(DefaultTags.hd.TagId);
this.setState({subtitle: `HD Videos`});
}}/>
</SideBar>
{this.state.data.length !== 0 ?
<VideoContainer

View File

@ -190,15 +190,15 @@ describe('<Player/>', function () {
const wrapper = instance();
global.callAPIMock({result: 'success'});
wrapper.setState({suggesttag: [{tag_name: 'test', tag_id: 1}]}, () => {
wrapper.setState({suggesttag: [{TagName: 'test', TagId: 1}]}, () => {
// mock funtion should have not been called
expect(callAPI).toBeCalledTimes(0);
wrapper.find('Tag').findWhere(p => p.props().tagInfo.tag_name === 'test').dive().simulate('click');
wrapper.find('Tag').findWhere(p => p.props().tagInfo.TagName === 'test').dive().simulate('click');
// mock function should have been called once
expect(callAPI).toBeCalledTimes(1);
// expect tag added to video tags
expect(wrapper.state().tags).toMatchObject([{tag_name: 'test'}]);
expect(wrapper.state().tags).toMatchObject([{TagName: 'test'}]);
// expect tag to be removed from tag suggestions
expect(wrapper.state().suggesttag).toHaveLength(0);
});

View File

@ -20,6 +20,7 @@ import {ActorType, TagType} from '../../types/VideoTypes';
import PlyrJS from 'plyr';
import {Button} from '../../elements/GPElements/Button';
import {VideoTypes} from '../../types/ApiTypes';
import GlobalInfos from "../../utils/GlobalInfos";
interface myprops extends RouteComponentProps<{ id: string }> {}
@ -131,16 +132,16 @@ export class Player extends React.Component<myprops, mystate> {
<Line/>
<SideBarTitle>Tags:</SideBarTitle>
{this.state.tags.map((m: TagType) => (
<Tag key={m.tag_id} tagInfo={m}/>
<Tag key={m.TagId} tagInfo={m}/>
))}
<Line/>
<SideBarTitle>Tag Quickadd:</SideBarTitle>
{this.state.suggesttag.map((m: TagType) => (
<Tag
tagInfo={m}
key={m.tag_name}
key={m.TagName}
onclick={(): void => {
this.quickAddTag(m.tag_id, m.tag_name);
this.quickAddTag(m.TagId, m.TagName);
}}/>
))}
</SideBar>
@ -155,7 +156,7 @@ export class Player extends React.Component<myprops, mystate> {
<div className={style.actorcontainer}>
{this.state.actors ?
this.state.actors.map((actr: ActorType) => (
<ActorTile key={actr.actor_id} actor={actr}/>
<ActorTile key={actr.ActorId} actor={actr}/>
)) : <></>
}
<div className={style.actorAddTile} onClick={(): void => {
@ -207,8 +208,8 @@ export class Player extends React.Component<myprops, mystate> {
quickAddTag(tagId: number, tagName: string): void {
callAPI(APINode.Tags, {
action: 'addTag',
id: tagId,
movieid: this.props.match.params.id
TagId: tagId,
MovieId: parseInt(this.props.match.params.id)
}, (result: GeneralSuccess) => {
if (result.result !== 'success') {
console.error('error occured while writing to db -- todo error handling');
@ -216,7 +217,7 @@ export class Player extends React.Component<myprops, mystate> {
} else {
// check if tag has already been added
const tagIndex = this.state.tags.map(function (e: TagType) {
return e.tag_name;
return e.TagName;
}).indexOf(tagName);
// only add tag if it isn't already there
@ -224,7 +225,7 @@ export class Player extends React.Component<myprops, mystate> {
// update tags if successful
let array = [...this.state.suggesttag]; // make a separate copy of the array (because of setState)
const quickaddindex = this.state.suggesttag.map(function (e: TagType) {
return e.tag_id;
return e.TagId;
}).indexOf(tagId);
// check if tag is available in quickadds
@ -232,12 +233,12 @@ export class Player extends React.Component<myprops, mystate> {
array.splice(quickaddindex, 1);
this.setState({
tags: [...this.state.tags, {tag_name: tagName, tag_id: tagId}],
tags: [...this.state.tags, {TagName: tagName, TagId: tagId}],
suggesttag: array
});
} else {
this.setState({
tags: [...this.state.tags, {tag_name: tagName, tag_id: tagId}]
tags: [...this.state.tags, {TagName: tagName, TagId: tagId}]
});
}
}
@ -249,27 +250,28 @@ export class Player extends React.Component<myprops, mystate> {
* fetch all the required infos of a video from backend
*/
fetchMovieData(): void {
callAPI(APINode.Video, {action: 'loadVideo', movieid: this.props.match.params.id}, (result: VideoTypes.loadVideoType) => {
callAPI(APINode.Video, {action: 'loadVideo', MovieId: parseInt(this.props.match.params.id)}, (result: VideoTypes.loadVideoType) => {
console.log(result)
this.setState({
sources: {
type: 'video',
sources: [
{
src: getBackendDomain() + result.movie_url,
src: getBackendDomain() + GlobalInfos.getVideoPath() + result.MovieUrl,
type: 'video/mp4',
size: 1080
}
],
poster: result.thumbnail
poster: result.Poster
},
movie_id: result.movie_id,
movie_name: result.movie_name,
likes: result.likes,
quality: result.quality,
length: result.length,
tags: result.tags,
suggesttag: result.suggesttag,
actors: result.actors
movie_id: result.MovieId,
movie_name: result.MovieName,
likes: result.Likes,
quality: result.Quality,
length: result.Length,
tags: result.Tags,
suggesttag: result.SuggestedTag,
actors: result.Actors
});
});
}
@ -279,7 +281,7 @@ export class Player extends React.Component<myprops, mystate> {
* click handler for the like btn
*/
likebtn(): void {
callAPI(APINode.Video, {action: 'addLike', movieid: this.props.match.params.id}, (result: GeneralSuccess) => {
callAPI(APINode.Video, {action: 'addLike', MovieId: parseInt(this.props.match.params.id)}, (result: GeneralSuccess) => {
if (result.result === 'success') {
// likes +1 --> avoid reload of all data
this.setState({likes: this.state.likes + 1});
@ -302,7 +304,7 @@ export class Player extends React.Component<myprops, mystate> {
* delete the current video and return to last page
*/
deleteVideo(): void {
callAPI(APINode.Video, {action: 'deleteVideo', movieid: this.props.match.params.id}, (result: GeneralSuccess) => {
callAPI(APINode.Video, {action: 'deleteVideo', MovieId: parseInt(this.props.match.params.id)}, (result: GeneralSuccess) => {
if (result.result === 'success') {
// return to last element if successful
this.props.history.goBack();

View File

@ -55,7 +55,7 @@ describe('<RandomPage/>', function () {
shallow(<RandomPage/>);
callAPIMock({rows: [], tags: []});
callAPIMock({Videos: [], Tags: []});
// trigger the keypress event
events.keyup({key: 's'});

View File

@ -15,8 +15,8 @@ interface state {
}
interface GetRandomMoviesType {
rows: VideoTypes.VideoUnloadedType[];
tags: TagType[];
Videos: VideoTypes.VideoUnloadedType[];
Tags: TagType[];
}
/**
@ -53,7 +53,7 @@ class RandomPage extends React.Component<{}, state> {
<SideBar>
<SideBarTitle>Visible Tags:</SideBarTitle>
{this.state.tags.map((m) => (
<Tag key={m.tag_id} tagInfo={m}/>
<Tag key={m.TagId} tagInfo={m}/>
))}
</SideBar>
@ -84,10 +84,11 @@ class RandomPage extends React.Component<{}, state> {
*/
loadShuffledvideos(nr: number): void {
callAPI<GetRandomMoviesType>(APINode.Video, {action: 'getRandomMovies', number: nr}, result => {
console.log(result)
this.setState({videos: []}); // needed to trigger rerender of main videoview
this.setState({
videos: result.rows,
tags: result.tags
videos: result.Videos,
tags: result.Tags
});
});
}

View File

@ -80,48 +80,48 @@ describe('<GeneralSettings/>', function () {
it('test videopath change event', function () {
const wrapper = shallow(<GeneralSettings/>);
expect(wrapper.state().videopath).not.toBe('test');
expect(wrapper.state().generalSettings.VideoPath).not.toBe('test');
const event = {target: {name: 'pollName', value: 'test'}};
wrapper.find('[data-testid=\'videpathform\']').find('FormControl').simulate('change', event);
expect(wrapper.state().videopath).toBe('test');
expect(wrapper.state().generalSettings.VideoPath).toBe('test');
});
it('test tvshowpath change event', function () {
const wrapper = shallow(<GeneralSettings/>);
const event = {target: {name: 'pollName', value: 'test'}};
expect(wrapper.state().tvshowpath).not.toBe('test');
expect(wrapper.state().generalSettings.EpisodePath).not.toBe('test');
wrapper.find('[data-testid=\'tvshowpath\']').find('FormControl').simulate('change', event);
expect(wrapper.state().tvshowpath).toBe('test');
expect(wrapper.state().generalSettings.EpisodePath).toBe('test');
});
it('test mediacentername-form change event', function () {
const wrapper = shallow(<GeneralSettings/>);
const event = {target: {name: 'pollName', value: 'test'}};
expect(wrapper.state().mediacentername).not.toBe('test');
expect(wrapper.state().generalSettings.MediacenterName).not.toBe('test');
wrapper.find('[data-testid=\'nameform\']').find('FormControl').simulate('change', event);
expect(wrapper.state().mediacentername).toBe('test');
expect(wrapper.state().generalSettings.MediacenterName).toBe('test');
});
it('test password-form change event', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.setState({passwordsupport: true});
wrapper.setState({generalSettings : {PasswordEnabled: true}});
const event = {target: {name: 'pollName', value: 'test'}};
expect(wrapper.state().password).not.toBe('test');
expect(wrapper.state().generalSettings.Password).not.toBe('test');
wrapper.find('[data-testid=\'passwordfield\']').find('FormControl').simulate('change', event);
expect(wrapper.state().password).toBe('test');
expect(wrapper.state().generalSettings.Password).toBe('test');
});
it('test tmdbsupport change event', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.setState({tmdbsupport: true});
wrapper.setState({generalSettings : {TMDBGrabbing: true}});
expect(wrapper.state().tmdbsupport).toBe(true);
expect(wrapper.state().generalSettings.TMDBGrabbing).toBe(true);
wrapper.find('[data-testid=\'tmdb-switch\']').simulate('change');
expect(wrapper.state().tmdbsupport).toBe(false);
expect(wrapper.state().generalSettings.TMDBGrabbing).toBe(false);
});
it('test insertion of 4 infoheaderitems', function () {

View File

@ -11,23 +11,13 @@ import {SettingsTypes} from '../../types/ApiTypes';
import {GeneralSuccess} from '../../types/GeneralTypes';
interface state {
passwordsupport: boolean,
tmdbsupport: boolean,
customapi: boolean,
videopath: string,
tvshowpath: string,
mediacentername: string,
password: string,
apipath: string,
videonr: number,
dbsize: number,
difftagnr: number,
tagsadded: number
customapi: boolean
apipath: string
generalSettings: SettingsTypes.loadGeneralSettingsType
}
interface props {}
interface props {
}
/**
* Component for Generalsettings tag on Settingspage
@ -38,20 +28,21 @@ class GeneralSettings extends React.Component<props, state> {
super(props);
this.state = {
passwordsupport: false,
tmdbsupport: false,
customapi: false,
videopath: '',
tvshowpath: '',
mediacentername: '',
password: '',
apipath: '',
videonr: 0,
dbsize: 0,
difftagnr: 0,
tagsadded: 0
generalSettings: {
DarkMode: true,
DBSize: 0,
DifferentTags: 0,
EpisodePath: "",
MediacenterName: "",
Password: "",
PasswordEnabled: false,
TagsAdded: 0,
TMDBGrabbing: false,
VideoNr: 0,
VideoPath: ""
}
};
}
@ -65,19 +56,19 @@ class GeneralSettings extends React.Component<props, state> {
<>
<div className={style.infoheader}>
<InfoHeaderItem backColor='lightblue'
text={this.state.videonr}
text={this.state.generalSettings.VideoNr}
subtext='Videos in Gravity'
icon={faArchive}/>
<InfoHeaderItem backColor='yellow'
text={this.state.dbsize !== undefined ? this.state.dbsize + ' MB' : ''}
text={this.state.generalSettings.DBSize + ' MB'}
subtext='Database size'
icon={faRulerVertical}/>
<InfoHeaderItem backColor='green'
text={this.state.difftagnr}
text={this.state.generalSettings.DifferentTags}
subtext='different Tags'
icon={faAddressCard}/>
<InfoHeaderItem backColor='orange'
text={this.state.tagsadded}
text={this.state.generalSettings.TagsAdded}
subtext='tags added'
icon={faBalanceScaleLeft}/>
</div>
@ -89,15 +80,26 @@ class GeneralSettings extends React.Component<props, state> {
<Form.Row>
<Form.Group as={Col} data-testid='videpathform'>
<Form.Label>Video Path</Form.Label>
<Form.Control type='text' placeholder='/var/www/html/video' value={this.state.videopath}
onChange={(ee): void => this.setState({videopath: ee.target.value})}/>
<Form.Control type='text' placeholder='/var/www/html/video'
value={this.state.generalSettings.VideoPath}
onChange={(ee): void => this.setState({
generalSettings: {
...this.state.generalSettings,
VideoPath: ee.target.value
}
})}/>
</Form.Group>
<Form.Group as={Col} data-testid='tvshowpath'>
<Form.Label>TV Show Path</Form.Label>
<Form.Control type='text' placeholder='/var/www/html/tvshow'
value={this.state.tvshowpath}
onChange={(e): void => this.setState({tvshowpath: e.target.value})}/>
value={this.state.generalSettings.EpisodePath}
onChange={(e): void => this.setState({
generalSettings: {
...this.state.generalSettings,
EpisodePath: e.target.value
}
})}/>
</Form.Group>
</Form.Row>
@ -131,17 +133,28 @@ class GeneralSettings extends React.Component<props, state> {
id='custom-switch'
data-testid='passwordswitch'
label='Enable Password support'
checked={this.state.passwordsupport}
checked={this.state.generalSettings.PasswordEnabled}
onChange={(): void => {
this.setState({passwordsupport: !this.state.passwordsupport});
this.setState({
generalSettings: {
...this.state.generalSettings,
PasswordEnabled: !this.state.generalSettings.PasswordEnabled
}
});
}}
/>
{this.state.passwordsupport ?
{this.state.generalSettings.PasswordEnabled ?
<Form.Group data-testid='passwordfield'>
<Form.Label>Password</Form.Label>
<Form.Control type='password' placeholder='**********' value={this.state.password}
onChange={(e): void => this.setState({password: e.target.value})}/>
<Form.Control type='password' placeholder='**********'
value={this.state.generalSettings.Password}
onChange={(e): void => this.setState({
generalSettings: {
...this.state.generalSettings,
Password: e.target.value
}
})}/>
</Form.Group> : null
}
@ -150,9 +163,14 @@ class GeneralSettings extends React.Component<props, state> {
id='custom-switch-2'
data-testid='tmdb-switch'
label='Enable TMDB video grabbing support'
checked={this.state.tmdbsupport}
checked={this.state.generalSettings.TMDBGrabbing}
onChange={(): void => {
this.setState({tmdbsupport: !this.state.tmdbsupport});
this.setState({
generalSettings: {
...this.state.generalSettings,
TMDBGrabbing: !this.state.generalSettings.TMDBGrabbing
}
});
}}
/>
@ -171,8 +189,14 @@ class GeneralSettings extends React.Component<props, state> {
<Form.Group className={style.mediacenternameform} data-testid='nameform'>
<Form.Label>The name of the Mediacenter</Form.Label>
<Form.Control type='text' placeholder='Mediacentername' value={this.state.mediacentername}
onChange={(e): void => this.setState({mediacentername: e.target.value})}/>
<Form.Control type='text' placeholder='Mediacentername'
value={this.state.generalSettings.MediacenterName}
onChange={(e): void => this.setState({
generalSettings: {
...this.state.generalSettings,
MediacenterName: e.target.value
}
})}/>
</Form.Group>
<Button variant='primary' type='submit'>
@ -192,19 +216,7 @@ class GeneralSettings extends React.Component<props, state> {
*/
loadSettings(): void {
callAPI(APINode.Settings, {action: 'loadGeneralSettings'}, (result: SettingsTypes.loadGeneralSettingsType) => {
this.setState({
videopath: result.video_path,
tvshowpath: result.episode_path,
mediacentername: result.mediacenter_name,
password: result.password,
passwordsupport: result.passwordEnabled,
tmdbsupport: result.TMDB_grabbing,
videonr: result.videonr,
dbsize: result.dbsize,
difftagnr: result.difftagnr,
tagsadded: result.tagsadded
});
this.setState({generalSettings: result});
});
}
@ -212,14 +224,15 @@ class GeneralSettings extends React.Component<props, state> {
* save the selected and typed settings to the backend
*/
saveSettings(): void {
let settings = this.state.generalSettings;
if(!this.state.generalSettings.PasswordEnabled){
settings.Password = '-1';
}
settings.DarkMode = GlobalInfos.isDarkTheme()
callAPI(APINode.Settings, {
action: 'saveGeneralSettings',
password: this.state.passwordsupport ? this.state.password : '-1',
videopath: this.state.videopath,
tvshowpath: this.state.tvshowpath,
mediacentername: this.state.mediacentername,
tmdbsupport: this.state.tmdbsupport,
darkmodeenabled: GlobalInfos.isDarkTheme().toString()
Settings: settings
}, (result: GeneralSuccess) => {
if (result.result) {
console.log('successfully saved settings');

View File

@ -52,10 +52,9 @@ describe('<MovieSettings/>', function () {
it('content available received and in state', () => {
const wrapper = shallow(<MovieSettings/>);
callAPIMock({
contentAvailable: true,
message: 'firstline\nsecondline'
ContentAvailable: true,
Messages: ['firstline', 'secondline']
})
wrapper.instance().updateStatus();
@ -70,7 +69,8 @@ describe('<MovieSettings/>', function () {
it('test reindex with no content available', () => {
callAPIMock({
contentAvailable: false
Messages: [],
ContentAvailable: false
})
global.clearInterval = jest.fn();
@ -99,12 +99,12 @@ describe('<MovieSettings/>', function () {
expect(wrapper.instance().setState).toBeCalledTimes(1);
});
it('test no textDiv insertion if string is empty', function () {
it('expect insertion before existing ones', function () {
const wrapper = shallow(<MovieSettings/>);
callAPIMock({
contentAvailable: true,
message: 'test'
ContentAvailable: true,
Messages: ['test']
})
wrapper.instance().updateStatus();
@ -115,14 +115,14 @@ describe('<MovieSettings/>', function () {
// expect an untouched state if we try to add an empty string...
callAPIMock({
contentAvailable: true,
message: ''
ContentAvailable: true,
Messages: ['']
})
wrapper.instance().updateStatus();
expect(wrapper.state()).toMatchObject({
text: ['test']
text: ['', 'test']
});
});
});

View File

@ -58,11 +58,7 @@ class MovieSettings extends React.Component<props, state> {
*/
startReindex(): void {
// clear output text before start
this.setState({text: []});
this.setState({startbtnDisabled: true});
console.log('starting');
this.setState({text: [], startbtnDisabled: true});
callAPI(APINode.Settings, {action: 'startReindex'}, (result: GeneralSuccess): void => {
console.log(result);
@ -85,18 +81,12 @@ class MovieSettings extends React.Component<props, state> {
*/
updateStatus = (): void => {
callAPI(APINode.Settings, {action: 'getStatusMessage'}, (result: SettingsTypes.getStatusMessageType) => {
if (result.contentAvailable) {
// ignore if message is empty
console.log(result);
if(result.message === '') return;
// todo 2020-07-4: scroll to bottom of div here
this.setState({
// insert a string for each line
text: [...result.message.split('\n'),
...this.state.text]
});
} else {
this.setState({
// insert a string for each line
text: [...result.Messages, ...this.state.text]
});
// todo 2020-07-4: scroll to bottom of div here
if (!result.ContentAvailable) {
// clear refresh interval if no content available
clearInterval(this.myinterval);

View File

@ -2,56 +2,59 @@ import {ActorType, TagType} from './VideoTypes';
export namespace VideoTypes {
export interface loadVideoType {
movie_url: string
thumbnail: string
movie_id: number
movie_name: string
likes: number
quality: number
length: number
tags: TagType[]
suggesttag: TagType[]
actors: ActorType[]
MovieUrl: string
Poster: string
MovieId: number
MovieName: string
Likes: number
Quality: number
Length: number
Tags: TagType[]
SuggestedTag: TagType[]
Actors: ActorType[]
}
export interface startDataType {
total: number;
fullhd: number;
hd: number;
sd: number;
tags: number;
VideoNr: number;
FullHdNr: number;
HDNr: number;
SDNr: number;
DifferentTags: number;
Tagged: number;
}
export interface VideoUnloadedType {
movie_id: number;
movie_name: string
MovieId: number;
MovieName: string
}
}
export namespace SettingsTypes {
export interface initialApiCallData {
DarkMode: boolean;
passwordEnabled: boolean;
mediacenter_name: string;
Password: boolean;
Mediacenter_name: string;
VideoPath: string;
}
export interface loadGeneralSettingsType {
video_path: string,
episode_path: string,
mediacenter_name: string,
password: string,
passwordEnabled: boolean,
TMDB_grabbing: boolean,
VideoPath: string,
EpisodePath: string,
MediacenterName: string,
Password: string,
PasswordEnabled: boolean,
TMDBGrabbing: boolean,
DarkMode: boolean,
videonr: number,
dbsize: number,
difftagnr: number,
tagsadded: number
VideoNr: number,
DBSize: number,
DifferentTags: number,
TagsAdded: number
}
export interface getStatusMessageType {
contentAvailable: boolean;
message: string;
ContentAvailable: boolean;
Messages: string[];
}
}
@ -60,7 +63,7 @@ export namespace ActorTypes {
* result of actor fetch
*/
export interface videofetchresult {
videos: VideoTypes.VideoUnloadedType[];
info: ActorType;
Videos: VideoTypes.VideoUnloadedType[];
Info: ActorType;
}
}

View File

@ -9,8 +9,8 @@ interface TagarrayType {
}
export const DefaultTags: TagarrayType = {
all: {tag_id: 1, tag_name: 'all'},
fullhd: {tag_id: 2, tag_name: 'fullhd'},
lowq: {tag_id: 3, tag_name: 'lowquality'},
hd: {tag_id: 4, tag_name: 'hd'}
all: {TagId: 1, TagName: 'all'},
fullhd: {TagId: 2, TagName: 'fullhd'},
lowq: {TagId: 3, TagName: 'lowquality'},
hd: {TagId: 4, TagName: 'hd'}
};

View File

@ -2,12 +2,12 @@
* type accepted by Tag component
*/
export interface TagType {
tag_name: string
tag_id: number
TagName: string
TagId: number
}
export interface ActorType {
thumbnail: string;
name: string;
actor_id: number;
Thumbnail: string;
Name: string;
ActorId: number;
}

View File

@ -40,20 +40,7 @@ function getAPIDomain(): string {
interface ApiBaseRequest {
action: string | number,
[_: string]: string | number | boolean
}
/**
* helper function to build a formdata for requesting post data correctly
* @param args api request object
*/
function buildFormData(args: ApiBaseRequest): FormData {
const req = new FormData();
for (const i in args) {
req.append(i, (args[i].toString()));
}
return req;
[_: string]: string | number | boolean | object
}
/**
@ -64,7 +51,7 @@ function buildFormData(args: ApiBaseRequest): FormData {
* @param errorcallback a optional callback if an error occured
*/
export function callAPI<T>(apinode: APINode, fd: ApiBaseRequest, callback: (_: T) => void, errorcallback: (_: string) => void = (_: string): void => {}): void {
fetch(getAPIDomain() + apinode, {method: 'POST', body: buildFormData(fd)})
fetch(getAPIDomain() + apinode, {method: 'POST', body: JSON.stringify(fd)})
.then((response) => response.json()
.then((result) => {
callback(result);
@ -78,7 +65,7 @@ export function callAPI<T>(apinode: APINode, fd: ApiBaseRequest, callback: (_: T
* @param callback the callback with PLAIN text reply from backend
*/
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
fetch(getAPIDomain() + apinode, {method: 'POST', body: buildFormData(fd)})
fetch(getAPIDomain() + apinode, {method: 'POST', body: JSON.stringify(fd)})
.then((response) => response.text()
.then((result) => {
callback(result);
@ -90,8 +77,8 @@ export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_:
* API nodes definitions
*/
export enum APINode {
Settings = 'settings.php',
Tags = 'tags.php',
Actor = 'actor.php',
Video = 'video.php'
Settings = 'settings',
Tags = 'tags',
Actor = 'actor',
Video = 'video'
}

View File

@ -6,31 +6,47 @@ import lighttheme from '../AppLightTheme.module.css';
* it contains general infos about app - like theme
*/
class StaticInfos {
#darktheme = true;
private darktheme: boolean = true;
private videopath: string = ""
/**
* check if the current theme is the dark theme
* @returns {boolean} is dark theme?
*/
isDarkTheme() {
return this.#darktheme;
isDarkTheme(): boolean {
return this.darktheme;
};
/**
* setter to enable or disable the dark or light theme
* @param enable enable the dark theme?
*/
enableDarkTheme(enable = true) {
this.#darktheme = enable;
enableDarkTheme(enable = true): void {
this.darktheme = enable;
}
/**
* get the currently selected theme stylesheet
* @returns {*} the style object of the current active theme
*/
getThemeStyle() {
getThemeStyle(): { [_: string]: string } {
return this.isDarkTheme() ? darktheme : lighttheme;
}
/**
* set the current videopath
* @param vidpath videopath with beginning and ending slash
*/
setVideoPath(vidpath: string): void {
this.videopath = vidpath;
}
/**
* return the current videopath
*/
getVideoPath(): string {
return this.videopath;
}
}
const GlobalInfos = new StaticInfos();