fix incorrect gui refresh if theme is changed
implement custom clientstore add new Password page if password is set force entering password to successfully receive the token add a new unsafe api call for init call only
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import App from './App';
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
import GlobalInfos from "./utils/GlobalInfos";
 | 
			
		||||
 | 
			
		||||
describe('<App/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
@@ -10,34 +11,37 @@ describe('<App/>', function () {
 | 
			
		||||
 | 
			
		||||
    it('renders title', () => {
 | 
			
		||||
        const wrapper = shallow(<App/>);
 | 
			
		||||
        wrapper.setState({password: false});
 | 
			
		||||
        expect(wrapper.find('.navbrand').text()).toBe('OpenMediaCenter');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('are navlinks correct', function () {
 | 
			
		||||
        const wrapper = shallow(<App/>);
 | 
			
		||||
        wrapper.setState({password: false});
 | 
			
		||||
        expect(wrapper.find('.navitem')).toHaveLength(4);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test initial fetch from api', done => {
 | 
			
		||||
        global.fetch = global.prepareFetchApi({
 | 
			
		||||
            generalSettingsLoaded: true,
 | 
			
		||||
            passwordsupport: true,
 | 
			
		||||
            mediacentername: 'testname'
 | 
			
		||||
        });
 | 
			
		||||
        callAPIMock({
 | 
			
		||||
            MediacenterName: 'testname'
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        GlobalInfos.enableDarkTheme = jest.fn((r) => {})
 | 
			
		||||
 | 
			
		||||
        const wrapper = shallow(<App/>);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const func = jest.fn();
 | 
			
		||||
        wrapper.instance().setState = func;
 | 
			
		||||
 | 
			
		||||
        expect(global.fetch).toBeCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
        process.nextTick(() => {
 | 
			
		||||
            expect(func).toBeCalledTimes(1);
 | 
			
		||||
            expect(document.title).toBe('testname');
 | 
			
		||||
 | 
			
		||||
            global.fetch.mockClear();
 | 
			
		||||
            done();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test render of password page', function () {
 | 
			
		||||
        const wrapper = shallow(<App/>);
 | 
			
		||||
        wrapper.setState({password: true});
 | 
			
		||||
 | 
			
		||||
        expect(wrapper.find('AuthenticationPage')).toHaveLength(1);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										91
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								src/App.tsx
									
									
									
									
									
								
							@@ -9,7 +9,7 @@ import style from './App.module.css';
 | 
			
		||||
 | 
			
		||||
import SettingsPage from './pages/SettingsPage/SettingsPage';
 | 
			
		||||
import CategoryPage from './pages/CategoryPage/CategoryPage';
 | 
			
		||||
import {APINode, callAPI} from './utils/Api';
 | 
			
		||||
import {APINode, apiTokenValid, callApiUnsafe, refreshAPIToken} from './utils/Api';
 | 
			
		||||
import {NoBackendConnectionPopup} from './elements/Popups/NoBackendConnectionPopup/NoBackendConnectionPopup';
 | 
			
		||||
 | 
			
		||||
import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom';
 | 
			
		||||
@@ -17,10 +17,10 @@ import Player from './pages/Player/Player';
 | 
			
		||||
import ActorOverviewPage from './pages/ActorOverviewPage/ActorOverviewPage';
 | 
			
		||||
import ActorPage from './pages/ActorPage/ActorPage';
 | 
			
		||||
import {SettingsTypes} from './types/ApiTypes';
 | 
			
		||||
import AuthenticationPage from "./pages/AuthenticationPage/AuthenticationPage";
 | 
			
		||||
 | 
			
		||||
interface state {
 | 
			
		||||
    generalSettingsLoaded: boolean;
 | 
			
		||||
    passwordsupport: boolean;
 | 
			
		||||
    password: boolean | null; // null if uninitialized - true if pwd needed false if not needed
 | 
			
		||||
    mediacentername: string;
 | 
			
		||||
    onapierror: boolean;
 | 
			
		||||
}
 | 
			
		||||
@@ -31,30 +31,48 @@ interface state {
 | 
			
		||||
class App extends React.Component<{}, state> {
 | 
			
		||||
    constructor(props: {}) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
        let pwdneeded: boolean | null = null;
 | 
			
		||||
 | 
			
		||||
        if (apiTokenValid()) {
 | 
			
		||||
            pwdneeded = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            refreshAPIToken((err) => {
 | 
			
		||||
                if (err === 'invalid_client') {
 | 
			
		||||
                    this.setState({password: true})
 | 
			
		||||
                } else if (err === '') {
 | 
			
		||||
                    this.setState({password: false})
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log("unimplemented token error: " + err)
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
            generalSettingsLoaded: false,
 | 
			
		||||
            passwordsupport: false,
 | 
			
		||||
            mediacentername: 'OpenMediaCenter',
 | 
			
		||||
            onapierror: false
 | 
			
		||||
            onapierror: false,
 | 
			
		||||
            password: pwdneeded
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        GlobalInfos.onThemeChange(() => {
 | 
			
		||||
            this.forceUpdate();
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialAPICall(): void {
 | 
			
		||||
        // this is the first api call so if it fails we know there is no connection to backend
 | 
			
		||||
        callAPI(APINode.Settings, {action: 'loadInitialData'}, (result: SettingsTypes.initialApiCallData) => {
 | 
			
		||||
        callApiUnsafe(APINode.Init, {action: 'loadInitialData'}, (result: SettingsTypes.initialApiCallData) => {
 | 
			
		||||
            // set theme
 | 
			
		||||
            GlobalInfos.enableDarkTheme(result.DarkMode);
 | 
			
		||||
 | 
			
		||||
            GlobalInfos.setVideoPath(result.VideoPath);
 | 
			
		||||
 | 
			
		||||
            this.setState({
 | 
			
		||||
                generalSettingsLoaded: true,
 | 
			
		||||
                passwordsupport: result.Password,
 | 
			
		||||
                mediacentername: result.Mediacenter_name,
 | 
			
		||||
                mediacentername: result.MediacenterName,
 | 
			
		||||
                onapierror: false
 | 
			
		||||
            });
 | 
			
		||||
            // set tab title to received mediacenter name
 | 
			
		||||
            document.title = result.Mediacenter_name;
 | 
			
		||||
            document.title = result.MediacenterName;
 | 
			
		||||
        }, error => {
 | 
			
		||||
            this.setState({onapierror: true});
 | 
			
		||||
        });
 | 
			
		||||
@@ -70,23 +88,44 @@ class App extends React.Component<{}, state> {
 | 
			
		||||
        // add the main theme to the page body
 | 
			
		||||
        document.body.className = themeStyle.backgroundcolor;
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <Router>
 | 
			
		||||
                <div className={style.app}>
 | 
			
		||||
                    <div className={[style.navcontainer, themeStyle.backgroundcolor, themeStyle.textcolor, themeStyle.hrcolor].join(' ')}>
 | 
			
		||||
                        <div className={style.navbrand}>{this.state.mediacentername}</div>
 | 
			
		||||
                        <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/'} activeStyle={{opacity: '0.85'}}>Home</NavLink>
 | 
			
		||||
                        <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/random'} activeStyle={{opacity: '0.85'}}>Random
 | 
			
		||||
                            Video</NavLink>
 | 
			
		||||
        if (this.state.password === true) {
 | 
			
		||||
            return (
 | 
			
		||||
                <AuthenticationPage submit={(password): void => {
 | 
			
		||||
                    refreshAPIToken((error) => {
 | 
			
		||||
                        if (error !== '') {
 | 
			
		||||
                            console.log("wrong password!!!");
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this.setState({password: false});
 | 
			
		||||
                        }
 | 
			
		||||
                    }, password);
 | 
			
		||||
                }}/>
 | 
			
		||||
            );
 | 
			
		||||
        } else if (this.state.password === false) {
 | 
			
		||||
            return (
 | 
			
		||||
                <Router>
 | 
			
		||||
                    <div className={style.app}>
 | 
			
		||||
                        <div
 | 
			
		||||
                            className={[style.navcontainer, themeStyle.backgroundcolor, themeStyle.textcolor, themeStyle.hrcolor].join(' ')}>
 | 
			
		||||
                            <div className={style.navbrand}>{this.state.mediacentername}</div>
 | 
			
		||||
                            <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/'}
 | 
			
		||||
                                     activeStyle={{opacity: '0.85'}}>Home</NavLink>
 | 
			
		||||
                            <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/random'}
 | 
			
		||||
                                     activeStyle={{opacity: '0.85'}}>Random
 | 
			
		||||
                                Video</NavLink>
 | 
			
		||||
 | 
			
		||||
                        <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/categories'} activeStyle={{opacity: '0.85'}}>Categories</NavLink>
 | 
			
		||||
                        <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/settings'} activeStyle={{opacity: '0.85'}}>Settings</NavLink>
 | 
			
		||||
                            <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/categories'}
 | 
			
		||||
                                     activeStyle={{opacity: '0.85'}}>Categories</NavLink>
 | 
			
		||||
                            <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/settings'}
 | 
			
		||||
                                     activeStyle={{opacity: '0.85'}}>Settings</NavLink>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {this.routing()}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {this.routing()}
 | 
			
		||||
                </div>
 | 
			
		||||
                {this.state.onapierror ? this.ApiError() : null}
 | 
			
		||||
            </Router>
 | 
			
		||||
        );
 | 
			
		||||
                    {this.state.onapierror ? this.ApiError() : null}
 | 
			
		||||
                </Router>
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            return (<>still loading...</>);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    routing(): JSX.Element {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								src/pages/AuthenticationPage/AuthenticationPage.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/pages/AuthenticationPage/AuthenticationPage.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
.main {
 | 
			
		||||
    background-color: #00b3ff;
 | 
			
		||||
    margin-left: calc(50% - 125px);
 | 
			
		||||
    margin-top: 5%;
 | 
			
		||||
    padding-bottom: 15px;
 | 
			
		||||
    width: 250px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.loginText {
 | 
			
		||||
    font-size: xx-large;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    font-weight: bolder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.openmediacenterlabel {
 | 
			
		||||
    margin-top: 5%;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: xxx-large;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    text-transform: capitalize;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input {
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
    width: calc(100% - 20px);
 | 
			
		||||
    background: transparent;
 | 
			
		||||
    border-width: 0 0 1px 0;
 | 
			
		||||
    color: #505050;
 | 
			
		||||
    border-color: #505050;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-bottom: 25px;
 | 
			
		||||
    font-size: larger;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::placeholder {
 | 
			
		||||
    color: #505050;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input:focus {
 | 
			
		||||
    color: black;
 | 
			
		||||
    border-color: black;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/pages/AuthenticationPage/AuthenticationPage.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/pages/AuthenticationPage/AuthenticationPage.test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import AuthenticationPage from './AuthenticationPage';
 | 
			
		||||
import {shallow} from 'enzyme';
 | 
			
		||||
 | 
			
		||||
describe('<AuthenticationPage/>', function () {
 | 
			
		||||
    it('renders without crashing ', function () {
 | 
			
		||||
        const wrapper = shallow(<AuthenticationPage submit={() => {}}/>);
 | 
			
		||||
        wrapper.unmount();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('test button click', function () {
 | 
			
		||||
        let pass;
 | 
			
		||||
        const func = jest.fn((pwd) => {pass = pwd});
 | 
			
		||||
        const wrapper = shallow(<AuthenticationPage submit={func}/>);
 | 
			
		||||
        wrapper.setState({pwdText: 'testpwd'});
 | 
			
		||||
        wrapper.find('Button').simulate('click');
 | 
			
		||||
 | 
			
		||||
        expect(func).toHaveBeenCalledTimes(1);
 | 
			
		||||
        expect(pass).toBe('testpwd');
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										46
									
								
								src/pages/AuthenticationPage/AuthenticationPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/pages/AuthenticationPage/AuthenticationPage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import {Button} from "../../elements/GPElements/Button";
 | 
			
		||||
import style from './AuthenticationPage.module.css'
 | 
			
		||||
 | 
			
		||||
interface state {
 | 
			
		||||
    pwdText: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface props {
 | 
			
		||||
    submit: (password: string) => void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AuthenticationPage extends React.Component<props, state> {
 | 
			
		||||
    constructor(props: props) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
            pwdText: ''
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): JSX.Element {
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
                <div className={style.openmediacenterlabel}>OpenMediaCenter</div>
 | 
			
		||||
                <div className={style.main}>
 | 
			
		||||
                    <div className={style.loginText}>Login</div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <input className={style.input}
 | 
			
		||||
                               placeholder='Password'
 | 
			
		||||
                               type='password'
 | 
			
		||||
                               onChange={(ch): void => this.setState({pwdText: ch.target.value})}
 | 
			
		||||
                               value={this.state.pwdText}/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <Button title='Submit' onClick={(): void => {
 | 
			
		||||
                            this.props.submit(this.state.pwdText);
 | 
			
		||||
                        }}/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default AuthenticationPage;
 | 
			
		||||
@@ -37,6 +37,7 @@ global.prepareFailingFetchApi = () => {
 | 
			
		||||
global.callAPIMock = (resonse) => {
 | 
			
		||||
    const helpers = require('./utils/Api');
 | 
			
		||||
    helpers.callAPI = jest.fn().mockImplementation((_, __, func1) => {func1(resonse);});
 | 
			
		||||
    helpers.callApiUnsafe = jest.fn().mockImplementation((_, __, func1) => {func1(resonse);});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// code to run before each test
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ export namespace SettingsTypes {
 | 
			
		||||
    export interface initialApiCallData {
 | 
			
		||||
        DarkMode: boolean;
 | 
			
		||||
        Password: boolean;
 | 
			
		||||
        Mediacenter_name: string;
 | 
			
		||||
        MediacenterName: string;
 | 
			
		||||
        VideoPath: string;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,13 +47,14 @@ interface ApiBaseRequest {
 | 
			
		||||
let apiToken = ''
 | 
			
		||||
 | 
			
		||||
// a callback que to be called after api token refresh
 | 
			
		||||
let callQue: (() => void)[] = []
 | 
			
		||||
let callQue: ((error: string) => void)[] = []
 | 
			
		||||
// flag to check wheter a api refresh is currently pending
 | 
			
		||||
let refreshInProcess = false;
 | 
			
		||||
// store the expire seconds of token
 | 
			
		||||
let expireSeconds = -1;
 | 
			
		||||
 | 
			
		||||
interface APIToken {
 | 
			
		||||
    error?: string;
 | 
			
		||||
    access_token: string;
 | 
			
		||||
    expires_in: number;
 | 
			
		||||
    scope: string;
 | 
			
		||||
@@ -63,8 +64,9 @@ interface APIToken {
 | 
			
		||||
/**
 | 
			
		||||
 * refresh the api token or use that one in cookie if still valid
 | 
			
		||||
 * @param callback to be called after successful refresh
 | 
			
		||||
 * @param password
 | 
			
		||||
 */
 | 
			
		||||
export function refreshAPIToken(callback: () => void): void {
 | 
			
		||||
export function refreshAPIToken(callback: (error: string) => void, password?: string): void {
 | 
			
		||||
    callQue.push(callback);
 | 
			
		||||
 | 
			
		||||
    // check if already is a token refresh is in process
 | 
			
		||||
@@ -76,30 +78,26 @@ export function refreshAPIToken(callback: () => void): void {
 | 
			
		||||
        refreshInProcess = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // check if a cookie with token is available
 | 
			
		||||
    const token = getTokenCookie();
 | 
			
		||||
    if (token !== null) {
 | 
			
		||||
        // check if token is at least valid for the next minute
 | 
			
		||||
        if (token.expire > (new Date().getTime() / 1000) + 60) {
 | 
			
		||||
            apiToken = token.token;
 | 
			
		||||
            expireSeconds = token.expire;
 | 
			
		||||
            callback();
 | 
			
		||||
            console.log("token still valid...")
 | 
			
		||||
            callFuncQue();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    if (apiTokenValid()) {
 | 
			
		||||
        console.log("token still valid...")
 | 
			
		||||
        callFuncQue('');
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const formData = new FormData();
 | 
			
		||||
    formData.append("grant_type", "client_credentials");
 | 
			
		||||
    formData.append("client_id", "openmediacenter");
 | 
			
		||||
    formData.append("client_secret", 'openmediacenter');
 | 
			
		||||
    formData.append("client_secret", password ? password : 'openmediacenter');
 | 
			
		||||
    formData.append("scope", 'all');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    fetch(getBackendDomain() + '/token', {method: 'POST', body: formData})
 | 
			
		||||
        .then((response) => response.json()
 | 
			
		||||
            .then((result: APIToken) => {
 | 
			
		||||
                if (result.error) {
 | 
			
		||||
                    callFuncQue(result.error);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                console.log(result)
 | 
			
		||||
                // set api token
 | 
			
		||||
                apiToken = result.access_token;
 | 
			
		||||
@@ -107,17 +105,32 @@ export function refreshAPIToken(callback: () => void): void {
 | 
			
		||||
                expireSeconds = (new Date().getTime() / 1000) + result.expires_in;
 | 
			
		||||
                setTokenCookie(apiToken, expireSeconds);
 | 
			
		||||
                // call all handlers and release flag
 | 
			
		||||
                callFuncQue();
 | 
			
		||||
                callFuncQue('');
 | 
			
		||||
            }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function apiTokenValid(): boolean {
 | 
			
		||||
    // check if a cookie with token is available
 | 
			
		||||
    const token = getTokenCookie();
 | 
			
		||||
    if (token !== null) {
 | 
			
		||||
        // check if token is at least valid for the next minute
 | 
			
		||||
        if (token.expire > (new Date().getTime() / 1000) + 60) {
 | 
			
		||||
            apiToken = token.token;
 | 
			
		||||
            expireSeconds = token.expire;
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * call all qued callbacks
 | 
			
		||||
 */
 | 
			
		||||
function callFuncQue(): void {
 | 
			
		||||
function callFuncQue(error: string): void {
 | 
			
		||||
    // call all pending handlers
 | 
			
		||||
    callQue.map(func => {
 | 
			
		||||
        return func();
 | 
			
		||||
        return func(error);
 | 
			
		||||
    })
 | 
			
		||||
    // reset pending que
 | 
			
		||||
    callQue = []
 | 
			
		||||
@@ -221,6 +234,25 @@ export function callAPI<T>(apinode: APINode,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * make a public unsafe api call (without token) -- use as rare as possible only for initialization (eg. check if pwd is neccessary)
 | 
			
		||||
 * @param apinode
 | 
			
		||||
 * @param fd
 | 
			
		||||
 * @param callback
 | 
			
		||||
 */
 | 
			
		||||
export function callApiUnsafe<T>(apinode: APINode, fd: ApiBaseRequest, callback: (_: T) => void, errorcallback?: (_: string) => void): void {
 | 
			
		||||
    fetch(getAPIDomain() + apinode, {method: 'POST', body: JSON.stringify(fd),}).then((response) => {
 | 
			
		||||
        if (response.status !== 200) {
 | 
			
		||||
            console.log('Error: ' + response.statusText);
 | 
			
		||||
            // todo place error popup here
 | 
			
		||||
        } else {
 | 
			
		||||
            response.json().then((result: T) => {
 | 
			
		||||
                callback(result);
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }).catch(reason => errorcallback ? errorcallback(reason) : {})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A backend api call
 | 
			
		||||
 * @param apinode which api backend handler to call
 | 
			
		||||
@@ -249,5 +281,6 @@ export enum APINode {
 | 
			
		||||
    Settings = 'settings',
 | 
			
		||||
    Tags = 'tags',
 | 
			
		||||
    Actor = 'actor',
 | 
			
		||||
    Video = 'video'
 | 
			
		||||
    Video = 'video',
 | 
			
		||||
    Init = 'init'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,9 @@ class StaticInfos {
 | 
			
		||||
     */
 | 
			
		||||
    enableDarkTheme(enable = true): void {
 | 
			
		||||
        this.darktheme = enable;
 | 
			
		||||
        this.handlers.map(func => {
 | 
			
		||||
            return func();
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -33,6 +36,11 @@ class StaticInfos {
 | 
			
		||||
        return this.isDarkTheme() ? darktheme : lighttheme;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handlers: (() => void)[] = [];
 | 
			
		||||
    onThemeChange(func: () => void): void {
 | 
			
		||||
        this.handlers.push(func);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * set the current videopath
 | 
			
		||||
     * @param vidpath videopath with beginning and ending slash
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user