build electron app
implement new fetch api calls use typescript
This commit is contained in:
		
							
								
								
									
										50
									
								
								src/App.js
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/App.js
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import HomePage from './pages/HomePage/HomePage';
 | 
			
		||||
import RandomPage from './pages/RandomPage/RandomPage';
 | 
			
		||||
import GlobalInfos from './GlobalInfos';
 | 
			
		||||
import GlobalInfos from './utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
// include bootstraps css
 | 
			
		||||
import 'bootstrap/dist/css/bootstrap.min.css';
 | 
			
		||||
@@ -9,6 +9,8 @@ import style from './App.module.css';
 | 
			
		||||
 | 
			
		||||
import SettingsPage from './pages/SettingsPage/SettingsPage';
 | 
			
		||||
import CategoryPage from './pages/CategoryPage/CategoryPage';
 | 
			
		||||
import {callAPI} from './utils/Api';
 | 
			
		||||
import {NoBackendConnectionPopup} from './elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The main App handles the main tabs and which content to show
 | 
			
		||||
@@ -22,7 +24,8 @@ class App extends React.Component {
 | 
			
		||||
            page: 'default',
 | 
			
		||||
            generalSettingsLoaded: false,
 | 
			
		||||
            passwordsupport: null,
 | 
			
		||||
            mediacentername: 'OpenMediaCenter'
 | 
			
		||||
            mediacentername: 'OpenMediaCenter',
 | 
			
		||||
            onapierror: false
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // bind this to the method for being able to call methods such as this.setstate
 | 
			
		||||
@@ -33,24 +36,27 @@ class App extends React.Component {
 | 
			
		||||
        GlobalInfos.setViewBinding(this.constructViewBinding());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialAPICall(){
 | 
			
		||||
        // this is the first api call so if it fails we know there is no connection to backend
 | 
			
		||||
        callAPI('settings.php', {action: 'loadInitialData'}, (result) =>{
 | 
			
		||||
            // set theme
 | 
			
		||||
            GlobalInfos.enableDarkTheme(result.DarkMode);
 | 
			
		||||
 | 
			
		||||
            this.setState({
 | 
			
		||||
                generalSettingsLoaded: true,
 | 
			
		||||
                passwordsupport: result.passwordEnabled,
 | 
			
		||||
                mediacentername: result.mediacenter_name,
 | 
			
		||||
                onapierror: false
 | 
			
		||||
            });
 | 
			
		||||
            // set tab title to received mediacenter name
 | 
			
		||||
            document.title = result.mediacenter_name;
 | 
			
		||||
        }, error =>  {
 | 
			
		||||
            this.setState({onapierror: true});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'loadInitialData');
 | 
			
		||||
 | 
			
		||||
        fetch('/api/settings.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    // set theme
 | 
			
		||||
                    GlobalInfos.enableDarkTheme(result.DarkMode);
 | 
			
		||||
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        generalSettingsLoaded: true,
 | 
			
		||||
                        passwordsupport: result.passwordEnabled,
 | 
			
		||||
                        mediacentername: result.mediacenter_name
 | 
			
		||||
                    });
 | 
			
		||||
                    // set tab title to received mediacenter name
 | 
			
		||||
                    document.title = result.mediacenter_name;
 | 
			
		||||
                }));
 | 
			
		||||
        this.initialAPICall();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -119,6 +125,7 @@ class App extends React.Component {
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                {this.state.generalSettingsLoaded ? this.MainBody() : 'loading'}
 | 
			
		||||
                {this.state.onapierror ? this.ApiError() : null}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -142,6 +149,11 @@ class App extends React.Component {
 | 
			
		||||
            page: 'lastpage'
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ApiError() {
 | 
			
		||||
        // on api error show popup and retry and show again if failing..
 | 
			
		||||
        return (<NoBackendConnectionPopup onHide={() => this.initialAPICall()}/>);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default App;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import style from './ActorTile.module.css';
 | 
			
		||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
 | 
			
		||||
import {faUser} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
import ActorPage from '../../pages/ActorPage/ActorPage';
 | 
			
		||||
 | 
			
		||||
class ActorTile extends React.Component {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,4 +18,17 @@ describe('<ActorTile/>', function () {
 | 
			
		||||
 | 
			
		||||
        expect(func).toBeCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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 func1 = jest.fn();
 | 
			
		||||
        prepareViewBinding(func1);
 | 
			
		||||
 | 
			
		||||
        wrapper.simulate('click');
 | 
			
		||||
 | 
			
		||||
        expect(func1).toBeCalledTimes(0);
 | 
			
		||||
        expect(func).toBeCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import style from './PageTitle.module.css';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component for generating PageTitle with bottom Line
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
 | 
			
		||||
import PageTitle from './PageTitle';
 | 
			
		||||
import PageTitle, {Line} from './PageTitle';
 | 
			
		||||
 | 
			
		||||
describe('<Preview/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
@@ -29,3 +29,10 @@ describe('<Preview/>', function () {
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('<Line/>', () => {
 | 
			
		||||
    it('renders without crashing', function () {
 | 
			
		||||
        const wrapper = shallow(<Line/>);
 | 
			
		||||
        wrapper.unmount();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import React from 'react';
 | 
			
		||||
import ActorTile from '../../ActorTile/ActorTile';
 | 
			
		||||
import style from './AddActorPopup.module.css';
 | 
			
		||||
import {NewActorPopupContent} from '../NewActorPopup/NewActorPopup';
 | 
			
		||||
import {callAPI} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Popup for Adding a new Actor to a Video
 | 
			
		||||
@@ -69,33 +70,21 @@ class AddActorPopup extends React.Component {
 | 
			
		||||
     */
 | 
			
		||||
    tileClickHandler(actorid) {
 | 
			
		||||
        // fetch the available actors
 | 
			
		||||
        const req = new FormData();
 | 
			
		||||
        req.append('action', 'addActorToVideo');
 | 
			
		||||
        req.append('actorid', actorid);
 | 
			
		||||
        req.append('videoid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/actor.php', {method: 'POST', body: req})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.result === 'success') {
 | 
			
		||||
                        // return back to player page
 | 
			
		||||
                        this.props.onHide();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.error('an error occured while fetching actors');
 | 
			
		||||
                        console.error(result);
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('actor.php', {action: 'addActorToVideo', actorid: actorid, videoid: this.props.movie_id}, result => {
 | 
			
		||||
            if (result.result === 'success') {
 | 
			
		||||
                // return back to player page
 | 
			
		||||
                this.props.onHide();
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error('an error occured while fetching actors');
 | 
			
		||||
                console.error(result);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loadActors() {
 | 
			
		||||
        const req = new FormData();
 | 
			
		||||
        req.append('action', 'getAllActors');
 | 
			
		||||
 | 
			
		||||
        fetch('/api/actor.php', {method: 'POST', body: req})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({actors: result});
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('actor.php', {action: 'getAllActors'}, result => {
 | 
			
		||||
            this.setState({actors: result});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import AddActorPopup from './AddActorPopup';
 | 
			
		||||
import {callAPI} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
describe('<AddActorPopup/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
@@ -8,12 +9,63 @@ describe('<AddActorPopup/>', function () {
 | 
			
		||||
        wrapper.unmount();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // it('simulate change to other page', function () {
 | 
			
		||||
    //     const wrapper = shallow(<AddActorPopup/>);
 | 
			
		||||
    //
 | 
			
		||||
    //     console.log(wrapper.find('PopupBase').dive().debug());
 | 
			
		||||
    //
 | 
			
		||||
    //
 | 
			
		||||
    //     console.log(wrapper.debug());
 | 
			
		||||
    // });
 | 
			
		||||
    it('simulate change to other page', function () {
 | 
			
		||||
        const wrapper = shallow(<AddActorPopup/>);
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.find('NewActorPopupContent')).toHaveLength(0);
 | 
			
		||||
        wrapper.find('PopupBase').props().banner.props.onClick();
 | 
			
		||||
 | 
			
		||||
        // check if new content is showing
 | 
			
		||||
        expect(wrapper.find('NewActorPopupContent')).toHaveLength(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('hide new actor page', function () {
 | 
			
		||||
        const wrapper = shallow(<AddActorPopup/>);
 | 
			
		||||
        wrapper.find('PopupBase').props().banner.props.onClick();
 | 
			
		||||
 | 
			
		||||
        // call onhide event listener manually
 | 
			
		||||
        wrapper.find('NewActorPopupContent').props().onHide();
 | 
			
		||||
 | 
			
		||||
        // expect other page to be hidden again
 | 
			
		||||
        expect(wrapper.find('NewActorPopupContent')).toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test api call and insertion of actor tiles', function () {
 | 
			
		||||
        global.callAPIMock([{id: 1, actorname: 'test'}, {id: 2, actorname: 'test2'}]);
 | 
			
		||||
 | 
			
		||||
        const wrapper = shallow(<AddActorPopup/>);
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.find('ActorTile')).toHaveLength(2);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('simulate actortile click', function () {
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        const wrapper = shallow(<AddActorPopup onHide={() => {func()}}/>);
 | 
			
		||||
 | 
			
		||||
        global.callAPIMock({result: 'success'});
 | 
			
		||||
 | 
			
		||||
        wrapper.setState({actors: [{id: 1, actorname: 'test'}]}, () => {
 | 
			
		||||
            wrapper.find('ActorTile').props().onClick();
 | 
			
		||||
 | 
			
		||||
            expect(callAPI).toHaveBeenCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
            expect(func).toHaveBeenCalledTimes(1);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test failing actortile click', function () {
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        const wrapper = shallow(<AddActorPopup onHide={() => {func()}}/>);
 | 
			
		||||
 | 
			
		||||
        global.callAPIMock({result: 'nosuccess'});
 | 
			
		||||
 | 
			
		||||
        wrapper.setState({actors: [{id: 1, actorname: 'test'}]}, () => {
 | 
			
		||||
            wrapper.find('ActorTile').props().onClick();
 | 
			
		||||
 | 
			
		||||
            expect(callAPI).toHaveBeenCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
            // hide funtion should not have been called on error!
 | 
			
		||||
            expect(func).toHaveBeenCalledTimes(0);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import Tag from '../../Tag/Tag';
 | 
			
		||||
import PopupBase from '../PopupBase';
 | 
			
		||||
import {callAPI} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * component creates overlay to add a new tag to a video
 | 
			
		||||
@@ -13,16 +14,11 @@ class AddTagPopup extends React.Component {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getAllTags');
 | 
			
		||||
 | 
			
		||||
        fetch('/api/tags.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json())
 | 
			
		||||
            .then((result) => {
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    items: result
 | 
			
		||||
                });
 | 
			
		||||
        callAPI('tags.php', {action: 'getAllTags'}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                items: result
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
@@ -44,23 +40,15 @@ class AddTagPopup extends React.Component {
 | 
			
		||||
     * @param tagname tag name to add
 | 
			
		||||
     */
 | 
			
		||||
    addTag(tagid, tagname) {
 | 
			
		||||
        console.log(this.props);
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'addTag');
 | 
			
		||||
        updateRequest.append('id', tagid);
 | 
			
		||||
        updateRequest.append('movieid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/tags.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.result !== 'success') {
 | 
			
		||||
                        console.log('error occured while writing to db -- todo error handling');
 | 
			
		||||
                        console.log(result.result);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this.props.submit(tagid, tagname);
 | 
			
		||||
                    }
 | 
			
		||||
                    this.props.onHide();
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('tags.php', {action: 'addTag', id: tagid, movieid: this.props.movie_id}, result => {
 | 
			
		||||
            if (result.result !== 'success') {
 | 
			
		||||
                console.log('error occured while writing to db -- todo error handling');
 | 
			
		||||
                console.log(result.result);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.props.submit(tagid, tagname);
 | 
			
		||||
            }
 | 
			
		||||
            this.props.onHide();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PopupBase from '../PopupBase';
 | 
			
		||||
import style from './NewActorPopup.module.css';
 | 
			
		||||
import {callAPI} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * creates modal overlay to define a new Tag
 | 
			
		||||
@@ -41,19 +42,13 @@ export class NewActorPopupContent extends React.Component {
 | 
			
		||||
        // check if user typed in name
 | 
			
		||||
        if (this.value === '' || this.value === undefined) return;
 | 
			
		||||
 | 
			
		||||
        const req = new FormData();
 | 
			
		||||
        req.append('action', 'createActor');
 | 
			
		||||
        req.append('actorname', this.value);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/actor.php', {method: 'POST', body: req})
 | 
			
		||||
            .then((response) => response.json())
 | 
			
		||||
            .then((result) => {
 | 
			
		||||
                if (result.result !== 'success') {
 | 
			
		||||
                    console.log('error occured while writing to db -- todo error handling');
 | 
			
		||||
                    console.log(result.result);
 | 
			
		||||
                }
 | 
			
		||||
                this.props.onHide();
 | 
			
		||||
            });
 | 
			
		||||
        callAPI('actor.php', {action: 'createActor', actorname: this.value}, (result) => {
 | 
			
		||||
            if (result.result !== 'success') {
 | 
			
		||||
                console.log('error occured while writing to db -- todo error handling');
 | 
			
		||||
                console.log(result.result);
 | 
			
		||||
            }
 | 
			
		||||
            this.props.onHide();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import React from 'react';
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import '@testing-library/jest-dom';
 | 
			
		||||
import NewActorPopup, {NewActorPopupContent} from './NewActorPopup';
 | 
			
		||||
import {callAPI} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
describe('<NewActorPopup/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
@@ -18,18 +19,23 @@ describe('<NewActorPopupContent/>', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('simulate button click', function () {
 | 
			
		||||
        const wrapper = shallow(<NewActorPopupContent/>);
 | 
			
		||||
        global.callAPIMock({});
 | 
			
		||||
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        const wrapper = shallow(<NewActorPopupContent onHide={() => {func()}}/>);
 | 
			
		||||
 | 
			
		||||
        // manually set typed in actorname
 | 
			
		||||
        wrapper.instance().value = 'testactorname';
 | 
			
		||||
 | 
			
		||||
        global.fetch = prepareFetchApi({});
 | 
			
		||||
 | 
			
		||||
        expect(global.fetch).toBeCalledTimes(0);
 | 
			
		||||
        expect(callAPI).toBeCalledTimes(0);
 | 
			
		||||
        wrapper.find('button').simulate('click');
 | 
			
		||||
 | 
			
		||||
        // fetch should have been called once now
 | 
			
		||||
        expect(global.fetch).toBeCalledTimes(1);
 | 
			
		||||
        expect(callAPI).toBeCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
        expect(func).toHaveBeenCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test not allowing request if textfield is empty', function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PopupBase from '../PopupBase';
 | 
			
		||||
import style from './NewTagPopup.module.css';
 | 
			
		||||
import {callAPI} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * creates modal overlay to define a new Tag
 | 
			
		||||
@@ -27,19 +28,13 @@ class NewTagPopup extends React.Component {
 | 
			
		||||
     * store the filled in form to the backend
 | 
			
		||||
     */
 | 
			
		||||
    storeselection() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'createTag');
 | 
			
		||||
        updateRequest.append('tagname', this.value);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/tags.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json())
 | 
			
		||||
            .then((result) => {
 | 
			
		||||
                if (result.result !== 'success') {
 | 
			
		||||
                    console.log('error occured while writing to db -- todo error handling');
 | 
			
		||||
                    console.log(result.result);
 | 
			
		||||
                }
 | 
			
		||||
                this.props.onHide();
 | 
			
		||||
            });
 | 
			
		||||
        callAPI('tags.php', {action: 'createTag', tagname: this.value}, result => {
 | 
			
		||||
            if (result.result !== 'success') {
 | 
			
		||||
                console.log('error occured while writing to db -- todo error handling');
 | 
			
		||||
                console.log(result.result);
 | 
			
		||||
            }
 | 
			
		||||
            this.props.onHide();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ import React from 'react';
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import '@testing-library/jest-dom';
 | 
			
		||||
import NewTagPopup from './NewTagPopup';
 | 
			
		||||
import {NoBackendConnectionPopup} from '../NoBackendConnectionPopup/NoBackendConnectionPopup';
 | 
			
		||||
import {getBackendDomain} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
describe('<NewTagPopup/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
@@ -33,4 +35,12 @@ describe('<NewTagPopup/>', function () {
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('simulate textfield change', function () {
 | 
			
		||||
        const wrapper = shallow(<NewTagPopup/>);
 | 
			
		||||
 | 
			
		||||
        wrapper.find('input').simulate('change', {target: {value: 'testvalue'}});
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.instance().value).toBe('testvalue');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import {NoBackendConnectionPopup} from './NoBackendConnectionPopup';
 | 
			
		||||
import {getBackendDomain} from '../../../utils/Api';
 | 
			
		||||
 | 
			
		||||
describe('<NoBackendConnectionPopup/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
        const wrapper = shallow(<NoBackendConnectionPopup onHide={() => {}}/>);
 | 
			
		||||
        wrapper.unmount();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('hides on refresh click', function () {
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        const wrapper = shallow(<NoBackendConnectionPopup onHide={func}/>);
 | 
			
		||||
 | 
			
		||||
        expect(func).toBeCalledTimes(0);
 | 
			
		||||
        wrapper.find('button').simulate('click');
 | 
			
		||||
 | 
			
		||||
        expect(func).toBeCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('simulate change of textfield', function () {
 | 
			
		||||
        const wrapper = shallow(<NoBackendConnectionPopup onHide={() => {}}/>);
 | 
			
		||||
 | 
			
		||||
        wrapper.find('input').simulate('change', {target: {value: 'testvalue'}});
 | 
			
		||||
 | 
			
		||||
        expect(getBackendDomain()).toBe('testvalue');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import PopupBase from "../PopupBase";
 | 
			
		||||
import style from "../NewActorPopup/NewActorPopup.module.css";
 | 
			
		||||
import {setCustomBackendDomain} from "../../../utils/Api";
 | 
			
		||||
 | 
			
		||||
interface NBCProps {
 | 
			
		||||
    onHide: (_: void) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function NoBackendConnectionPopup(props: NBCProps): JSX.Element {
 | 
			
		||||
    return (
 | 
			
		||||
        <PopupBase title='No connection to backend API!' onHide={props.onHide} height='200px' width='600px'>
 | 
			
		||||
            <div>
 | 
			
		||||
                <input type='text' placeholder='http://192.168.0.2' onChange={(v) => {
 | 
			
		||||
                    setCustomBackendDomain(v.target.value);
 | 
			
		||||
                }}/></div>
 | 
			
		||||
            <button className={style.savebtn} onClick={() => props.onHide()}>Refresh</button>
 | 
			
		||||
        </PopupBase>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
import style from './PopupBase.module.css';
 | 
			
		||||
import {Line} from '../PageTitle/PageTitle';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
@@ -20,7 +20,8 @@ class PopupBase extends React.Component {
 | 
			
		||||
        // parse style props
 | 
			
		||||
        this.framedimensions = {
 | 
			
		||||
            width: (this.props.width ? this.props.width : undefined),
 | 
			
		||||
            height: (this.props.height ? this.props.height : undefined)
 | 
			
		||||
            height: (this.props.height ? this.props.height : undefined),
 | 
			
		||||
            minHeight: (this.props.height ? this.props.height : undefined)
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
.popup {
 | 
			
		||||
    border: 3px #3574fe solid;
 | 
			
		||||
    border-radius: 18px;
 | 
			
		||||
    height: 80%;
 | 
			
		||||
    min-height: 80%;
 | 
			
		||||
    height: fit-content;
 | 
			
		||||
    left: 20%;
 | 
			
		||||
    opacity: 0.95;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
@@ -40,4 +41,5 @@
 | 
			
		||||
    margin-right: 20px;
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,5 +8,19 @@ describe('<PopupBase/>', function () {
 | 
			
		||||
        wrapper.unmount();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('simulate keypress', function () {
 | 
			
		||||
        let events = [];
 | 
			
		||||
        document.addEventListener = jest.fn((event, cb) => {
 | 
			
		||||
            events[event] = cb;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        shallow(<PopupBase onHide={() => func()}/>);
 | 
			
		||||
 | 
			
		||||
        // trigger the keypress event
 | 
			
		||||
        events.keyup({key: 'Escape'});
 | 
			
		||||
 | 
			
		||||
        expect(func).toBeCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,8 @@ import React from 'react';
 | 
			
		||||
import style from './Preview.module.css';
 | 
			
		||||
import Player from '../../pages/Player/Player';
 | 
			
		||||
import {Spinner} from 'react-bootstrap';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
import {callAPIPlain} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component for single preview tile
 | 
			
		||||
@@ -19,22 +20,12 @@ class Preview extends React.Component {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            previewpicture: null,
 | 
			
		||||
            name: this.props.name
 | 
			
		||||
        callAPIPlain('video.php', {action: 'readThumbnail', movieid: this.props.movie_id}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                previewpicture: result,
 | 
			
		||||
                name: this.props.name
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'readThumbnail');
 | 
			
		||||
        updateRequest.append('movieid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.text()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        previewpicture: result
 | 
			
		||||
                    });
 | 
			
		||||
                }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,6 @@ describe('<Preview/>', function () {
 | 
			
		||||
        wrapper.unmount();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // check if preview title renders correctly
 | 
			
		||||
    it('renders title', () => {
 | 
			
		||||
        const wrapper = shallow(<Preview name='test'/>);
 | 
			
		||||
        expect(wrapper.find('.previewtitle').text()).toBe('test');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    it('click event triggered', () => {
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
@@ -36,7 +30,7 @@ describe('<Preview/>', function () {
 | 
			
		||||
        });
 | 
			
		||||
        global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
 | 
			
		||||
 | 
			
		||||
        const wrapper = shallow(<Preview/>);
 | 
			
		||||
        const wrapper = shallow(<Preview name='test'/>);
 | 
			
		||||
 | 
			
		||||
        // now called 1 times
 | 
			
		||||
        expect(global.fetch).toHaveBeenCalledTimes(1);
 | 
			
		||||
@@ -44,6 +38,8 @@ describe('<Preview/>', function () {
 | 
			
		||||
        process.nextTick(() => {
 | 
			
		||||
            // received picture should be rendered into wrapper
 | 
			
		||||
            expect(wrapper.find('.previewimage').props().src).not.toBeNull();
 | 
			
		||||
            // check if preview title renders correctly
 | 
			
		||||
            expect(wrapper.find('.previewtitle').text()).toBe('test');
 | 
			
		||||
 | 
			
		||||
            global.fetch.mockClear();
 | 
			
		||||
            done();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import style from './SideBar.module.css';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * component for sidebar-info
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import React from 'react';
 | 
			
		||||
 | 
			
		||||
import styles from './Tag.module.css';
 | 
			
		||||
import CategoryPage from '../../pages/CategoryPage/CategoryPage';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Component representing a single Category tag
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import React from 'react';
 | 
			
		||||
import ReactDOM from 'react-dom';
 | 
			
		||||
import App from './App';
 | 
			
		||||
 | 
			
		||||
// don't allow console logs within production env
 | 
			
		||||
// don't allow console logs within production env
 | 
			
		||||
global.console.log = process.env.NODE_ENV !== "development" ?  (s) => {} : global.console.log;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
 | 
			
		||||
import {faUser} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import style from './ActorPage.module.css';
 | 
			
		||||
import VideoContainer from '../../elements/VideoContainer/VideoContainer';
 | 
			
		||||
import {callAPI} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
class ActorPage extends React.Component {
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
@@ -40,17 +41,10 @@ class ActorPage extends React.Component {
 | 
			
		||||
     */
 | 
			
		||||
    getActorInfo() {
 | 
			
		||||
        // todo 2020-12-4: fetch to db
 | 
			
		||||
 | 
			
		||||
        const req = new FormData();
 | 
			
		||||
        req.append('action', 'getActorInfo');
 | 
			
		||||
        req.append('actorid', this.props.actor.actor_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/actor.php', {method: 'POST', body: req})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    console.log(result);
 | 
			
		||||
                    this.setState({data: result.videos ? result.videos : []});
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('actor.php', {action: 'getActorInfo', actorid: this.props.actor.actor_id}, result => {
 | 
			
		||||
            console.log(result);
 | 
			
		||||
            this.setState({data: result.videos ? result.videos : []});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import {TagPreview} from '../../elements/Preview/Preview';
 | 
			
		||||
import NewTagPopup from '../../elements/Popups/NewTagPopup/NewTagPopup';
 | 
			
		||||
import PageTitle, {Line} from '../../elements/PageTitle/PageTitle';
 | 
			
		||||
import VideoContainer from '../../elements/VideoContainer/VideoContainer';
 | 
			
		||||
import {callAPI} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component for Category Page
 | 
			
		||||
@@ -111,24 +112,11 @@ class CategoryPage extends React.Component {
 | 
			
		||||
     * @param tag tagname
 | 
			
		||||
     */
 | 
			
		||||
    fetchVideoData(tag) {
 | 
			
		||||
        console.log(tag);
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getMovies');
 | 
			
		||||
        updateRequest.append('tag', tag);
 | 
			
		||||
 | 
			
		||||
        console.log('fetching data');
 | 
			
		||||
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.videodata = result;
 | 
			
		||||
                    this.setState({selected: null}); // needed to trigger the state reload correctly
 | 
			
		||||
                    this.setState({selected: tag});
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
            });
 | 
			
		||||
        callAPI('video.php', {action: 'getMovies', tag: tag}, result => {
 | 
			
		||||
            this.videodata = result;
 | 
			
		||||
            this.setState({selected: null}); // needed to trigger the state reload correctly
 | 
			
		||||
            this.setState({selected: tag});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -143,18 +131,9 @@ class CategoryPage extends React.Component {
 | 
			
		||||
     * load all available tags from db.
 | 
			
		||||
     */
 | 
			
		||||
    loadTags() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getAllTags');
 | 
			
		||||
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/tags.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({loadedtags: result});
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
            });
 | 
			
		||||
        callAPI('tags.php', {action: 'getAllTags'}, result => {
 | 
			
		||||
            this.setState({loadedtags: result});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,27 +24,6 @@ describe('<CategoryPage/>', function () {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test errored fetch call', done => {
 | 
			
		||||
        global.fetch = global.prepareFetchApi({});
 | 
			
		||||
 | 
			
		||||
        let message;
 | 
			
		||||
        global.console.log = jest.fn((m) => {
 | 
			
		||||
            message = m;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        shallow(<CategoryPage/>);
 | 
			
		||||
 | 
			
		||||
        expect(global.fetch).toHaveBeenCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
        process.nextTick(() => {
 | 
			
		||||
            //callback to close window should have called
 | 
			
		||||
            expect(message).toBe('no connection to backend');
 | 
			
		||||
 | 
			
		||||
            global.fetch.mockClear();
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test new tag popup', function () {
 | 
			
		||||
        const wrapper = shallow(<CategoryPage/>);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import VideoContainer from '../../elements/VideoContainer/VideoContainer';
 | 
			
		||||
 | 
			
		||||
import style from './HomePage.module.css';
 | 
			
		||||
import PageTitle, {Line} from '../../elements/PageTitle/PageTitle';
 | 
			
		||||
import {callAPI} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The home page component showing on the initial pageload
 | 
			
		||||
@@ -43,54 +44,33 @@ class HomePage extends React.Component {
 | 
			
		||||
     * @param tag tag to fetch videos
 | 
			
		||||
     */
 | 
			
		||||
    fetchVideoData(tag) {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getMovies');
 | 
			
		||||
        updateRequest.append('tag', tag);
 | 
			
		||||
 | 
			
		||||
        console.log('fetching data from' + tag);
 | 
			
		||||
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        data: []
 | 
			
		||||
                    });
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        data: result,
 | 
			
		||||
                        selectionnr: result.length,
 | 
			
		||||
                        tag: tag + ' Videos'
 | 
			
		||||
                    });
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
        callAPI('video.php', {action: 'getMovies', tag: tag}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                data: []
 | 
			
		||||
            });
 | 
			
		||||
            this.setState({
 | 
			
		||||
                data: result,
 | 
			
		||||
                selectionnr: result.length,
 | 
			
		||||
                tag: tag + ' Videos'
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * fetch the necessary data for left info box
 | 
			
		||||
     */
 | 
			
		||||
    fetchStartData() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getStartData');
 | 
			
		||||
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        sideinfo: {
 | 
			
		||||
                            videonr: result['total'],
 | 
			
		||||
                            fullhdvideonr: result['fullhd'],
 | 
			
		||||
                            hdvideonr: result['hd'],
 | 
			
		||||
                            sdvideonr: result['sd'],
 | 
			
		||||
                            tagnr: result['tags']
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
        callAPI('video.php', {action: 'getStartData'}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                sideinfo: {
 | 
			
		||||
                    videonr: result['total'],
 | 
			
		||||
                    fullhdvideonr: result['fullhd'],
 | 
			
		||||
                    hdvideonr: result['hd'],
 | 
			
		||||
                    sdvideonr: result['sd'],
 | 
			
		||||
                    tagnr: result['tags']
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -101,26 +81,16 @@ class HomePage extends React.Component {
 | 
			
		||||
    searchVideos(keyword) {
 | 
			
		||||
        console.log('search called');
 | 
			
		||||
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getSearchKeyWord');
 | 
			
		||||
        updateRequest.append('keyword', keyword);
 | 
			
		||||
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        data: []
 | 
			
		||||
                    });
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        data: result,
 | 
			
		||||
                        selectionnr: result.length,
 | 
			
		||||
                        tag: 'Search result: ' + keyword
 | 
			
		||||
                    });
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
        callAPI('video.php', {action: 'getSearchKeyWord', keyword: keyword}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                data: []
 | 
			
		||||
            });
 | 
			
		||||
            this.setState({
 | 
			
		||||
                data: result,
 | 
			
		||||
                selectionnr: result.length,
 | 
			
		||||
                tag: 'Search result: ' + keyword
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import style from './Player.module.css';
 | 
			
		||||
import plyrstyle from 'plyr-react/dist/plyr.css'
 | 
			
		||||
import plyrstyle from 'plyr-react/dist/plyr.css';
 | 
			
		||||
 | 
			
		||||
import {Plyr} from 'plyr-react';
 | 
			
		||||
import SideBar, {SideBarItem, SideBarTitle} from '../../elements/SideBar/SideBar';
 | 
			
		||||
@@ -12,7 +12,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
 | 
			
		||||
import {faPlusCircle} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import AddActorPopup from '../../elements/Popups/AddActorPopup/AddActorPopup';
 | 
			
		||||
import ActorTile from '../../elements/ActorTile/ActorTile';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
import {callAPI, getBackendDomain} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -67,47 +68,40 @@ class Player extends React.Component {
 | 
			
		||||
     * @param tagName name of tag to add
 | 
			
		||||
     */
 | 
			
		||||
    quickAddTag(tagId, tagName) {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'addTag');
 | 
			
		||||
        updateRequest.append('id', tagId);
 | 
			
		||||
        updateRequest.append('movieid', this.props.movie_id);
 | 
			
		||||
        callAPI('tags.php', {action: 'addTag', id: tagId, movieid: this.props.movie_id}, (result) => {
 | 
			
		||||
            if (result.result !== 'success') {
 | 
			
		||||
                console.error('error occured while writing to db -- todo error handling');
 | 
			
		||||
                console.error(result.result);
 | 
			
		||||
            } else {
 | 
			
		||||
                // check if tag has already been added
 | 
			
		||||
                const tagIndex = this.state.tags.map(function (e) {
 | 
			
		||||
                    return e.tag_name;
 | 
			
		||||
                }).indexOf(tagName);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/tags.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.result !== 'success') {
 | 
			
		||||
                        console.error('error occured while writing to db -- todo error handling');
 | 
			
		||||
                        console.error(result.result);
 | 
			
		||||
                // only add tag if it isn't already there
 | 
			
		||||
                if (tagIndex === -1) {
 | 
			
		||||
                    // 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) {
 | 
			
		||||
                        return e.tag_id;
 | 
			
		||||
                    }).indexOf(tagId);
 | 
			
		||||
 | 
			
		||||
                    // check if tag is available in quickadds
 | 
			
		||||
                    if (quickaddindex !== -1) {
 | 
			
		||||
                        array.splice(quickaddindex, 1);
 | 
			
		||||
 | 
			
		||||
                        this.setState({
 | 
			
		||||
                            tags: [...this.state.tags, {tag_name: tagName}],
 | 
			
		||||
                            suggesttag: array
 | 
			
		||||
                        });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // check if tag has already been added
 | 
			
		||||
                        const tagIndex = this.state.tags.map(function (e) {
 | 
			
		||||
                            return e.tag_name;
 | 
			
		||||
                        }).indexOf(tagName);
 | 
			
		||||
 | 
			
		||||
                        // only add tag if it isn't already there
 | 
			
		||||
                        if (tagIndex === -1) {
 | 
			
		||||
                            // 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) {
 | 
			
		||||
                                return e.tag_id;
 | 
			
		||||
                            }).indexOf(tagId);
 | 
			
		||||
 | 
			
		||||
                            // check if tag is available in quickadds
 | 
			
		||||
                            if (quickaddindex !== -1) {
 | 
			
		||||
                                array.splice(quickaddindex, 1);
 | 
			
		||||
 | 
			
		||||
                                this.setState({
 | 
			
		||||
                                    tags: [...this.state.tags, {tag_name: tagName}],
 | 
			
		||||
                                    suggesttag: array
 | 
			
		||||
                                });
 | 
			
		||||
                            } else {
 | 
			
		||||
                                this.setState({
 | 
			
		||||
                                    tags: [...this.state.tags, {tag_name: tagName}]
 | 
			
		||||
                                });
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        this.setState({
 | 
			
		||||
                            tags: [...this.state.tags, {tag_name: tagName}]
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -179,7 +173,7 @@ class Player extends React.Component {
 | 
			
		||||
                <div className={style.videowrapper}>
 | 
			
		||||
                    {/* video component is added here */}
 | 
			
		||||
                    {this.state.sources ? <Plyr
 | 
			
		||||
                        style={plyrstyle}
 | 
			
		||||
                            style={plyrstyle}
 | 
			
		||||
                            source={this.state.sources}
 | 
			
		||||
                            options={this.options}/> :
 | 
			
		||||
                        <div>not loaded yet</div>}
 | 
			
		||||
@@ -227,36 +221,30 @@ class Player extends React.Component {
 | 
			
		||||
     * fetch all the required infos of a video from backend
 | 
			
		||||
     */
 | 
			
		||||
    fetchMovieData() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'loadVideo');
 | 
			
		||||
        updateRequest.append('movieid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json())
 | 
			
		||||
            .then((result) => {
 | 
			
		||||
                this.setState({
 | 
			
		||||
                    sources: {
 | 
			
		||||
                        type: 'video',
 | 
			
		||||
                        sources: [
 | 
			
		||||
                            {
 | 
			
		||||
                                src: result.movie_url,
 | 
			
		||||
                                type: 'video/mp4',
 | 
			
		||||
                                size: 1080
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        poster: result.thumbnail
 | 
			
		||||
                    },
 | 
			
		||||
                    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
 | 
			
		||||
                });
 | 
			
		||||
                console.log(this.state);
 | 
			
		||||
        callAPI('video.php', {action: 'loadVideo', movieid: this.props.movie_id}, result => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                sources: {
 | 
			
		||||
                    type: 'video',
 | 
			
		||||
                    sources: [
 | 
			
		||||
                        {
 | 
			
		||||
                            src: getBackendDomain() + result.movie_url,
 | 
			
		||||
                            type: 'video/mp4',
 | 
			
		||||
                            size: 1080
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    poster: result.thumbnail
 | 
			
		||||
                },
 | 
			
		||||
                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
 | 
			
		||||
            });
 | 
			
		||||
            console.log(this.state);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -264,21 +252,15 @@ class Player extends React.Component {
 | 
			
		||||
     * click handler for the like btn
 | 
			
		||||
     */
 | 
			
		||||
    likebtn() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'addLike');
 | 
			
		||||
        updateRequest.append('movieid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.result === 'success') {
 | 
			
		||||
                        // likes +1 --> avoid reload of all data
 | 
			
		||||
                        this.setState({likes: this.state.likes + 1});
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.error('an error occured while liking');
 | 
			
		||||
                        console.error(result);
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('video.php', {action: 'addLike', movieid: this.props.movie_id}, result => {
 | 
			
		||||
            if (result.result === 'success') {
 | 
			
		||||
                // likes +1 --> avoid reload of all data
 | 
			
		||||
                this.setState({likes: this.state.likes + 1});
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error('an error occured while liking');
 | 
			
		||||
                console.error(result);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -293,21 +275,15 @@ class Player extends React.Component {
 | 
			
		||||
     * delete the current video and return to last page
 | 
			
		||||
     */
 | 
			
		||||
    deleteVideo() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'deleteVideo');
 | 
			
		||||
        updateRequest.append('movieid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.result === 'success') {
 | 
			
		||||
                        // return to last element if successful
 | 
			
		||||
                        GlobalInfos.getViewBinding().returnToLastElement();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.error('an error occured while liking');
 | 
			
		||||
                        console.error(result);
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('video.php', {action: 'deleteVideo', movieid: this.props.movie_id}, result => {
 | 
			
		||||
            if (result.result === 'success') {
 | 
			
		||||
                // return to last element if successful
 | 
			
		||||
                GlobalInfos.getViewBinding().returnToLastElement();
 | 
			
		||||
            } else {
 | 
			
		||||
                console.error('an error occured while liking');
 | 
			
		||||
                console.error(result);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -318,17 +294,9 @@ class Player extends React.Component {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    refetchActors() {
 | 
			
		||||
        const req = new FormData();
 | 
			
		||||
        req.append('action', 'getActorsOfVideo');
 | 
			
		||||
        req.append('videoid', this.props.movie_id);
 | 
			
		||||
 | 
			
		||||
        console.log('refrething actors');
 | 
			
		||||
 | 
			
		||||
        fetch('/api/actor.php', {method: 'POST', body: req})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({actors: result});
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('actor.php', {action: 'getActorsOfVideo', videoid: this.props.movie_id}, result => {
 | 
			
		||||
            this.setState({actors: result});
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import Player from './Player';
 | 
			
		||||
import {callAPI} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
describe('<Player/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
@@ -81,7 +82,7 @@ describe('<Player/>', function () {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        prepareViewBinding(func)
 | 
			
		||||
        prepareViewBinding(func);
 | 
			
		||||
 | 
			
		||||
        global.fetch = prepareFetchApi({result: 'success'});
 | 
			
		||||
 | 
			
		||||
@@ -163,6 +164,58 @@ describe('<Player/>', function () {
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('showspopups correctly', function () {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
 | 
			
		||||
        wrapper.setState({popupvisible: true}, () => {
 | 
			
		||||
            // is the AddTagpopu rendered?
 | 
			
		||||
            expect(wrapper.find('AddTagPopup')).toHaveLength(1);
 | 
			
		||||
            wrapper.setState({popupvisible: false, actorpopupvisible: true}, () => {
 | 
			
		||||
                // actorpopup rendred and tagpopup hidden?
 | 
			
		||||
                expect(wrapper.find('AddTagPopup')).toHaveLength(0);
 | 
			
		||||
                expect(wrapper.find('AddActorPopup')).toHaveLength(1);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    it('quickadd tag correctly', function () {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
        global.callAPIMock({result: 'success'});
 | 
			
		||||
 | 
			
		||||
        wrapper.setState({suggesttag: [{tag_name: 'test', tag_id: 1}]}, () => {
 | 
			
		||||
            // mock funtion should have not been called
 | 
			
		||||
            expect(callAPI).toBeCalledTimes(0);
 | 
			
		||||
            wrapper.find('Tag').findWhere(p => p.text() === 'test').parent().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 tag to be removed from tag suggestions
 | 
			
		||||
            expect(wrapper.state().suggesttag).toHaveLength(0);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test adding of already existing tag', function () {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
        global.callAPIMock({result: 'success'});
 | 
			
		||||
 | 
			
		||||
        wrapper.setState({suggesttag: [{tag_name: 'test', tag_id: 1}], tags: [{tag_name: 'test', tag_id: 1}]}, () => {
 | 
			
		||||
            // mock funtion should have not been called
 | 
			
		||||
            expect(callAPI).toBeCalledTimes(0);
 | 
			
		||||
            wrapper.find('Tag').findWhere(p => p.text() === 'test').last().parent().dive().simulate('click');
 | 
			
		||||
            // mock function should have been called once
 | 
			
		||||
            expect(callAPI).toBeCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
            // there should not been added a duplicate of tag so object stays same...
 | 
			
		||||
            expect(wrapper.state().tags).toMatchObject([{tag_name: 'test'}]);
 | 
			
		||||
            // the suggestion tag shouldn't be removed (this can't actually happen in rl
 | 
			
		||||
            // because backennd doesn't give dupliacate suggestiontags)
 | 
			
		||||
            expect(wrapper.state().suggesttag).toHaveLength(1);
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function generatetag() {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
 | 
			
		||||
@@ -178,4 +231,26 @@ describe('<Player/>', function () {
 | 
			
		||||
 | 
			
		||||
        return wrapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it('test addactor popup showing', function () {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.find('AddActorPopup')).toHaveLength(0);
 | 
			
		||||
 | 
			
		||||
        wrapper.instance().addActor();
 | 
			
		||||
 | 
			
		||||
        // check if popup is visible
 | 
			
		||||
        expect(wrapper.find('AddActorPopup')).toHaveLength(1);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test hiding of addactor popup', function () {
 | 
			
		||||
        const wrapper = shallow(<Player/>);
 | 
			
		||||
        wrapper.instance().addActor();
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.find('AddActorPopup')).toHaveLength(1);
 | 
			
		||||
 | 
			
		||||
        wrapper.find('AddActorPopup').props().onHide();
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.find('AddActorPopup')).toHaveLength(0);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import SideBar, {SideBarTitle} from '../../elements/SideBar/SideBar';
 | 
			
		||||
import Tag from '../../elements/Tag/Tag';
 | 
			
		||||
import PageTitle from '../../elements/PageTitle/PageTitle';
 | 
			
		||||
import VideoContainer from '../../elements/VideoContainer/VideoContainer';
 | 
			
		||||
import {callAPI} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Randompage shuffles random viedeopreviews and provides a shuffle btn
 | 
			
		||||
@@ -61,25 +62,15 @@ class RandomPage extends React.Component {
 | 
			
		||||
     * @param nr number of videos to load
 | 
			
		||||
     */
 | 
			
		||||
    loadShuffledvideos(nr) {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'getRandomMovies');
 | 
			
		||||
        updateRequest.append('number', nr);
 | 
			
		||||
        callAPI('video.php', {action: 'getRandomMovies', number: nr}, result => {
 | 
			
		||||
            console.log(result);
 | 
			
		||||
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/video.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    console.log(result);
 | 
			
		||||
 | 
			
		||||
                    this.setState({videos: []}); // needed to trigger rerender of main videoview
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        videos: result.rows,
 | 
			
		||||
                        tags: result.tags
 | 
			
		||||
                    });
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
            this.setState({videos: []}); // needed to trigger rerender of main videoview
 | 
			
		||||
            this.setState({
 | 
			
		||||
                videos: result.rows,
 | 
			
		||||
                tags: result.tags
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import {Button, Col, Form} from 'react-bootstrap';
 | 
			
		||||
import style from './GeneralSettings.module.css';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
import InfoHeaderItem from '../../elements/InfoHeaderItem/InfoHeaderItem';
 | 
			
		||||
import {faArchive, faBalanceScaleLeft, faRulerVertical} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import {faAddressCard} from '@fortawesome/free-regular-svg-icons';
 | 
			
		||||
import {version} from '../../../package.json';
 | 
			
		||||
import {callAPI, setCustomBackendDomain} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component for Generalsettings tag on Settingspage
 | 
			
		||||
@@ -18,6 +19,7 @@ class GeneralSettings extends React.Component {
 | 
			
		||||
        this.state = {
 | 
			
		||||
            passwordsupport: false,
 | 
			
		||||
            tmdbsupport: null,
 | 
			
		||||
            customapi: false,
 | 
			
		||||
 | 
			
		||||
            videopath: '',
 | 
			
		||||
            tvshowpath: '',
 | 
			
		||||
@@ -77,6 +79,31 @@ class GeneralSettings extends React.Component {
 | 
			
		||||
                            </Form.Group>
 | 
			
		||||
                        </Form.Row>
 | 
			
		||||
 | 
			
		||||
                        <Form.Check
 | 
			
		||||
                            type='switch'
 | 
			
		||||
                            id='custom-switch-api'
 | 
			
		||||
                            label='Use custom API url'
 | 
			
		||||
                            checked={this.state.customapi}
 | 
			
		||||
                            onChange={() => {
 | 
			
		||||
                                if (this.state.customapi) {
 | 
			
		||||
                                    setCustomBackendDomain('');
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                this.setState({customapi: !this.state.customapi});
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                        {this.state.customapi ?
 | 
			
		||||
                            <Form.Group className={style.customapiform} data-testid='apipath'>
 | 
			
		||||
                                <Form.Label>API Backend url</Form.Label>
 | 
			
		||||
                                <Form.Control type='text' placeholder='https://127.0.0.1'
 | 
			
		||||
                                              value={this.state.apipath}
 | 
			
		||||
                                              onChange={(e) => {
 | 
			
		||||
                                                  this.setState({apipath: e.target.value});
 | 
			
		||||
                                                  setCustomBackendDomain(e.target.value);
 | 
			
		||||
                                              }}/>
 | 
			
		||||
                            </Form.Group> : null}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <Form.Check
 | 
			
		||||
                            type='switch'
 | 
			
		||||
                            id='custom-switch'
 | 
			
		||||
@@ -142,54 +169,44 @@ class GeneralSettings extends React.Component {
 | 
			
		||||
     * inital load of already specified settings from backend
 | 
			
		||||
     */
 | 
			
		||||
    loadSettings() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'loadGeneralSettings');
 | 
			
		||||
        callAPI('settings.php', {action: 'loadGeneralSettings'}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                videopath: result.video_path,
 | 
			
		||||
                tvshowpath: result.episode_path,
 | 
			
		||||
                mediacentername: result.mediacenter_name,
 | 
			
		||||
                password: result.password,
 | 
			
		||||
                passwordsupport: result.passwordEnabled,
 | 
			
		||||
                tmdbsupport: result.TMDB_grabbing,
 | 
			
		||||
 | 
			
		||||
        fetch('/api/settings.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    console.log(result);
 | 
			
		||||
                    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
 | 
			
		||||
                    });
 | 
			
		||||
                }));
 | 
			
		||||
                videonr: result.videonr,
 | 
			
		||||
                dbsize: result.dbsize,
 | 
			
		||||
                difftagnr: result.difftagnr,
 | 
			
		||||
                tagsadded: result.tagsadded
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * save the selected and typed settings to the backend
 | 
			
		||||
     */
 | 
			
		||||
    saveSettings() {
 | 
			
		||||
        const updateRequest = new FormData();
 | 
			
		||||
        updateRequest.append('action', 'saveGeneralSettings');
 | 
			
		||||
 | 
			
		||||
        updateRequest.append('password', this.state.passwordsupport ? this.state.password : '-1');
 | 
			
		||||
        updateRequest.append('videopath', this.state.videopath);
 | 
			
		||||
        updateRequest.append('tvshowpath', this.state.tvshowpath);
 | 
			
		||||
        updateRequest.append('mediacentername', this.state.mediacentername);
 | 
			
		||||
        updateRequest.append('tmdbsupport', this.state.tmdbsupport);
 | 
			
		||||
        updateRequest.append('darkmodeenabled', GlobalInfos.isDarkTheme().toString());
 | 
			
		||||
 | 
			
		||||
        fetch('/api/settings.php', {method: 'POST', body: updateRequest})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.success) {
 | 
			
		||||
                        console.log('successfully saved settings');
 | 
			
		||||
                        // todo 2020-07-10: popup success
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.log('failed to save settings');
 | 
			
		||||
                        // todo 2020-07-10: popup error
 | 
			
		||||
                    }
 | 
			
		||||
                }));
 | 
			
		||||
        callAPI('settings.php', {
 | 
			
		||||
            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()
 | 
			
		||||
        }, (result) => {
 | 
			
		||||
            if (result.success) {
 | 
			
		||||
                console.log('successfully saved settings');
 | 
			
		||||
                // todo 2020-07-10: popup success
 | 
			
		||||
            } else {
 | 
			
		||||
                console.log('failed to save settings');
 | 
			
		||||
                // todo 2020-07-10: popup error
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,11 @@
 | 
			
		||||
    width: 40%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.customapiform{
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
    width: 40%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.infoheader {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import GeneralSettings from './GeneralSettings';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
describe('<GeneralSettings/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import style from './MovieSettings.module.css';
 | 
			
		||||
import {callAPI} from '../../utils/Api';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component for MovieSettings on Settingspage
 | 
			
		||||
@@ -50,23 +51,17 @@ class MovieSettings extends React.Component {
 | 
			
		||||
        this.setState({startbtnDisabled: true});
 | 
			
		||||
 | 
			
		||||
        console.log('starting');
 | 
			
		||||
        const request = new FormData();
 | 
			
		||||
        request.append('action', 'startReindex');
 | 
			
		||||
        // fetch all videos available
 | 
			
		||||
        fetch('/api/settings.php', {method: 'POST', body: request})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    console.log(result);
 | 
			
		||||
                    if (result.success) {
 | 
			
		||||
                        console.log('started successfully');
 | 
			
		||||
                    } else {
 | 
			
		||||
                        console.log('error, reindex already running');
 | 
			
		||||
                        this.setState({startbtnDisabled: true});
 | 
			
		||||
                    }
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        callAPI('settings.php', {action: 'startReindex'}, (result) => {
 | 
			
		||||
            console.log(result);
 | 
			
		||||
            if (result.success) {
 | 
			
		||||
                console.log('started successfully');
 | 
			
		||||
            } else {
 | 
			
		||||
                console.log('error, reindex already running');
 | 
			
		||||
                this.setState({startbtnDisabled: true});
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (this.myinterval) {
 | 
			
		||||
            clearInterval(this.myinterval);
 | 
			
		||||
        }
 | 
			
		||||
@@ -77,49 +72,33 @@ class MovieSettings extends React.Component {
 | 
			
		||||
     * This interval function reloads the current status of reindexing from backend
 | 
			
		||||
     */
 | 
			
		||||
    updateStatus = () => {
 | 
			
		||||
        const request = new FormData();
 | 
			
		||||
        request.append('action', 'getStatusMessage');
 | 
			
		||||
        callAPI('settings.php', {action: 'getStatusMessage'}, (result) => {
 | 
			
		||||
            if (result.contentAvailable === true) {
 | 
			
		||||
                console.log(result);
 | 
			
		||||
                // 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 {
 | 
			
		||||
                // clear refresh interval if no content available
 | 
			
		||||
                clearInterval(this.myinterval);
 | 
			
		||||
 | 
			
		||||
        fetch('/api/settings.php', {method: 'POST', body: request})
 | 
			
		||||
            .then((response) => response.json()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    if (result.contentAvailable === true) {
 | 
			
		||||
                        console.log(result);
 | 
			
		||||
                        // 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 {
 | 
			
		||||
                        // clear refresh interval if no content available
 | 
			
		||||
                        clearInterval(this.myinterval);
 | 
			
		||||
 | 
			
		||||
                        this.setState({startbtnDisabled: false});
 | 
			
		||||
                    }
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
            });
 | 
			
		||||
                this.setState({startbtnDisabled: false});
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * send request to cleanup db gravity
 | 
			
		||||
     */
 | 
			
		||||
    cleanupGravity() {
 | 
			
		||||
        const request = new FormData();
 | 
			
		||||
        request.append('action', 'cleanupGravity');
 | 
			
		||||
 | 
			
		||||
        fetch('/api/settings.php', {method: 'POST', body: request})
 | 
			
		||||
            .then((response) => response.text()
 | 
			
		||||
                .then((result) => {
 | 
			
		||||
                    this.setState({
 | 
			
		||||
                        text: ['successfully cleaned up gravity!']
 | 
			
		||||
                    });
 | 
			
		||||
                }))
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
                console.log('no connection to backend');
 | 
			
		||||
        callAPI('settings.php', {action: 'cleanupGravity'}, (result) => {
 | 
			
		||||
            this.setState({
 | 
			
		||||
                text: ['successfully cleaned up gravity!']
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,18 @@ import React from 'react';
 | 
			
		||||
import MovieSettings from './MovieSettings';
 | 
			
		||||
import GeneralSettings from './GeneralSettings';
 | 
			
		||||
import style from './SettingsPage.module.css';
 | 
			
		||||
import GlobalInfos from '../../GlobalInfos';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
type SettingsPageState = {
 | 
			
		||||
    currentpage: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The Settingspage handles all kinds of settings for the mediacenter
 | 
			
		||||
 * and is basically a wrapper for child-tabs
 | 
			
		||||
 */
 | 
			
		||||
class SettingsPage extends React.Component {
 | 
			
		||||
    constructor(props, context) {
 | 
			
		||||
class SettingsPage extends React.Component<{}, SettingsPageState> {
 | 
			
		||||
    constructor(props: Readonly<{}> | {}, context?: any) {
 | 
			
		||||
        super(props, context);
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
@@ -21,7 +25,7 @@ class SettingsPage extends React.Component {
 | 
			
		||||
     * load the selected tab
 | 
			
		||||
     * @returns {JSX.Element|string} the jsx element of the selected tab
 | 
			
		||||
     */
 | 
			
		||||
    getContent() {
 | 
			
		||||
    getContent(): JSX.Element | string {
 | 
			
		||||
        switch (this.state.currentpage) {
 | 
			
		||||
            case 'general':
 | 
			
		||||
                return <GeneralSettings/>;
 | 
			
		||||
@@ -34,7 +38,7 @@ class SettingsPage extends React.Component {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
    render() : JSX.Element {
 | 
			
		||||
        const themestyle = GlobalInfos.getThemeStyle();
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
@@ -6,7 +6,7 @@ import '@testing-library/jest-dom/extend-expect';
 | 
			
		||||
 | 
			
		||||
import {configure} from 'enzyme';
 | 
			
		||||
import Adapter from 'enzyme-adapter-react-16';
 | 
			
		||||
import GlobalInfos from './GlobalInfos';
 | 
			
		||||
import GlobalInfos from './utils/GlobalInfos';
 | 
			
		||||
 | 
			
		||||
configure({adapter: new Adapter()});
 | 
			
		||||
 | 
			
		||||
@@ -45,3 +45,21 @@ global.prepareViewBinding = (func) => {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
global.callAPIMock = (resonse) => {
 | 
			
		||||
    const helpers = require("./utils/Api");
 | 
			
		||||
    helpers.callAPI = jest.fn().mockImplementation((_, __, func1) => {func1(resonse)});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// code to run before each test
 | 
			
		||||
global.beforeEach(() => {
 | 
			
		||||
    // empty fetch response implementation for each test
 | 
			
		||||
    global.fetch = prepareFetchApi({});
 | 
			
		||||
    // todo with callAPIMock
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
global.afterEach(() => {
 | 
			
		||||
    // clear all mocks after each test
 | 
			
		||||
    jest.resetAllMocks();
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										88
									
								
								src/utils/Api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/utils/Api.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
let customBackendURL: string;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * get the domain of the api backend
 | 
			
		||||
 * @return string domain of backend http://x.x.x.x/bla
 | 
			
		||||
 */
 | 
			
		||||
export function getBackendDomain(): string {
 | 
			
		||||
    let userAgent = navigator.userAgent.toLowerCase();
 | 
			
		||||
    if (userAgent.indexOf(' electron/') > -1) {
 | 
			
		||||
        // Electron-specific code - force a custom backendurl
 | 
			
		||||
        return (customBackendURL);
 | 
			
		||||
    } else {
 | 
			
		||||
        // use custom only if defined
 | 
			
		||||
        if (customBackendURL) {
 | 
			
		||||
            return (customBackendURL);
 | 
			
		||||
        } else {
 | 
			
		||||
            return (window.location.origin);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * set a custom backend domain
 | 
			
		||||
 * @param domain a url in format [http://x.x.x.x/somanode]
 | 
			
		||||
 */
 | 
			
		||||
export function setCustomBackendDomain(domain: string) {
 | 
			
		||||
    customBackendURL = domain;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * a helper function to get the api path
 | 
			
		||||
 */
 | 
			
		||||
function getAPIDomain(): string {
 | 
			
		||||
    return getBackendDomain() + '/api/';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * interface how an api request should look like
 | 
			
		||||
 */
 | 
			
		||||
interface ApiBaseRequest {
 | 
			
		||||
    action: string,
 | 
			
		||||
 | 
			
		||||
    [_: string]: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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]);
 | 
			
		||||
    }
 | 
			
		||||
    return req;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A backend api call
 | 
			
		||||
 * @param apinode which api backend handler to call
 | 
			
		||||
 * @param fd the object to send to backend
 | 
			
		||||
 * @param callback the callback with json reply from backend
 | 
			
		||||
 * @param errorcallback a optional callback if an error occured
 | 
			
		||||
 */
 | 
			
		||||
export function callAPI(apinode: string, fd: ApiBaseRequest, callback: (_: object) => void, errorcallback: (_: object) => void = (_: object) => {
 | 
			
		||||
}): void {
 | 
			
		||||
    fetch(getAPIDomain() + apinode, {method: 'POST', body: buildFormData(fd)})
 | 
			
		||||
        .then((response) => response.json()
 | 
			
		||||
            .then((result) => {
 | 
			
		||||
                callback(result);
 | 
			
		||||
            })).catch(reason => errorcallback(reason));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A backend api call
 | 
			
		||||
 * @param apinode which api backend handler to call
 | 
			
		||||
 * @param fd the object to send to backend
 | 
			
		||||
 * @param callback the callback with PLAIN text reply from backend
 | 
			
		||||
 */
 | 
			
		||||
export function callAPIPlain(apinode: string, fd: ApiBaseRequest, callback: (_: any) => void): void {
 | 
			
		||||
    fetch(getAPIDomain() + apinode, {method: 'POST', body: buildFormData(fd)})
 | 
			
		||||
        .then((response) => response.text()
 | 
			
		||||
            .then((result) => {
 | 
			
		||||
                callback(result);
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import darktheme from './AppDarkTheme.module.css';
 | 
			
		||||
import lighttheme from './AppLightTheme.module.css';
 | 
			
		||||
import darktheme from '../AppDarkTheme.module.css';
 | 
			
		||||
import lighttheme from '../AppLightTheme.module.css';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is available for all components in project
 | 
			
		||||
@@ -7,7 +7,7 @@ import lighttheme from './AppLightTheme.module.css';
 | 
			
		||||
 */
 | 
			
		||||
class StaticInfos {
 | 
			
		||||
    #darktheme = true;
 | 
			
		||||
    #viewbinding = () => {console.warn("Viewbinding not set now!")}
 | 
			
		||||
    #viewbinding = () => {console.warn('Viewbinding not set now!');};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * check if the current theme is the dark theme
 | 
			
		||||
@@ -37,7 +37,7 @@ class StaticInfos {
 | 
			
		||||
     * set the global Viewbinding for the main Navigation
 | 
			
		||||
     * @param cb
 | 
			
		||||
     */
 | 
			
		||||
    setViewBinding(cb){
 | 
			
		||||
    setViewBinding(cb) {
 | 
			
		||||
        this.#viewbinding = cb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +45,7 @@ class StaticInfos {
 | 
			
		||||
     * return the Viewbinding for main navigation
 | 
			
		||||
     * @returns {StaticInfos.viewbinding}
 | 
			
		||||
     */
 | 
			
		||||
    getViewBinding(){
 | 
			
		||||
    getViewBinding() {
 | 
			
		||||
        return this.#viewbinding;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user