Merge branch 'SettingsPage' into 'master'

Settings page

See merge request lukas/openmediacenter!6
This commit is contained in:
Lukas Heiligenbrunner 2020-07-17 21:14:56 +00:00
commit ca499fed99
18 changed files with 740 additions and 104 deletions

39
api/SSettings.php Normal file
View File

@ -0,0 +1,39 @@
<?php
class SSettings
{
private ?Database $database;
/**
* SSettings constructor.
*/
public function __construct() {
$this->database = Database::getInstance();
}
public function getVideoPath() {
$query = "SELECT video_path from settings";
$result = $this->database->getConnection()->query($query);
$r = mysqli_fetch_assoc($result);
return $r['video_path'];
}
/**
* check if TMDB is enableds
* @return bool isenabled?
*/
public function isTMDBGrabbingEnabled(): bool
{
$query = "SELECT TMDB_grabbing from settings";
$result = $this->database->getConnection()->query($query);
if(!$result){
return true; // if undefined in db --> default true
}else{
$r = mysqli_fetch_assoc($result);
return $r['TMDB_grabbing'] == '1';
}
}
}

65
api/Settings.php Normal file
View File

@ -0,0 +1,65 @@
<?php
require 'Database.php';
require 'SSettings.php';
$conn = Database::getInstance()->getConnection();
$settings = new SSettings();
if (isset($_POST['action'])) {
$action = $_POST['action'];
switch ($action) {
case "loadGeneralSettings":
$query = "SELECT * from settings";
$result = $conn->query($query);
if ($result->num_rows > 1) {
// todo throw error
}
$r = mysqli_fetch_assoc($result);
// booleans need to be set manually
$r['passwordEnabled'] = $r['password'] != "-1";
$r['TMDB_grabbing'] = ($r['TMDB_grabbing'] != '0');
echo json_encode($r);
break;
case "saveGeneralSettings":
$mediacentername = $_POST['mediacentername'];
$password = $_POST['password'];
$videopath = $_POST['videopath'];
$tvshowpath = $_POST['tvshowpath'];
$tmdbsupport = $_POST['tmdbsupport'];
$query = "UPDATE settings SET
video_path='$videopath',
episode_path='$tvshowpath',
password='$password',
mediacenter_name='$mediacentername',
TMDB_grabbing=$tmdbsupport
WHERE 1";
if ($conn->query($query) === true) {
echo '{"success": true}';
} else {
echo '{"success": true}';
}
break;
case "loadInitialData":
$query = "SELECT * from settings";
$result = $conn->query($query);
if ($result->num_rows > 1) {
// todo throw error
}
$r = mysqli_fetch_assoc($result);
if ($r['password'] != "-1") {
$r['passwordEnabled'] = true;
} else {
$r['passwordEnabled'] = false;
}
unset($r['password']);
echo json_encode($r);
break;
}
}

View File

@ -1,6 +1,7 @@
<?php
require 'Database.php';
require 'TMDBMovie.php';
require 'SSettings.php';
writeLog("starting extraction!\n");
@ -10,9 +11,12 @@ $tmdb = new TMDBMovie();
$tmdbgenres = $tmdb->getAllGenres();
$conn = Database::getInstance()->getConnection();
$settings = new SSettings();
$scandir = "../videos/prn/";
// load video path from settings
$scandir = "../" . $settings->getVideoPath();
$arr = scandir($scandir);
$TMDB_enabled = $settings->isTMDBGrabbingEnabled();
$all = 0;
$added = 0;
@ -33,15 +37,22 @@ foreach ($arr as $elem) {
$poster = -1;
$genres = -1;
if (!is_null($dta = $tmdb->searchMovie($moviename))) {
$pic = file_get_contents($tmdb->picturebase . $dta->poster_path);
$poster = shell_exec("ffmpeg -hide_banner -loglevel panic -ss 00:04:00 -i \"../videos/prn/$elem\" -vframes 1 -q:v 2 -f singlejpeg pipe:1 2>/dev/null");
// check if tmdb support is enabled
if ($TMDB_enabled) {
$pic = file_get_contents($tmdb->picturebase . $dta->poster_path);
// error handling for download error
if (!$pic) {
$pic = $poster;
$poster = -1;
echo "Failed to load Picture from TMDB! \n";
}
} else {
$pic = $poster;
$poster = -1;
}
$genres = $dta->genre_ids;
} else {

View File

@ -1,9 +1,15 @@
<?php
require 'Database.php';
require 'SSettings.php';
// establish initial db connection
$conn = Database::getInstance()->getConnection();
$settings = new SSettings();
// load video path from settings
$videopath = $settings->getVideoPath();
//$_POST['action'] = "getRandomMovies";$_POST['number'] =6;
if (isset($_POST['action'])) {
$action = $_POST['action'];
switch ($action) {
@ -84,7 +90,9 @@ if (isset($_POST['action'])) {
$arr["movie_id"] = $row["movie_id"];
$arr["movie_name"] = $row["movie_name"];
$arr["movie_url"] = str_replace("?","%3F",$row["movie_url"]);
// todo drop video url from db -- maybe one with and one without extension
// extension hardcoded here!!!
$arr["movie_url"] = str_replace("?", "%3F", $videopath . $row["movie_name"] . ".mp4");
$arr["likes"] = $row["likes"];
$arr["quality"] = $row["quality"];
$arr["length"] = $row["length"];

View File

@ -29,9 +29,22 @@ create table if not exists video_tags
foreign key (video_id) references videos (movie_id)
);
create table settings
(
id enum(1) NOT NULL default 0,
video_path varchar(255) null,
episode_path varchar(255) null,
password varchar(32) default '-1' null,
mediacenter_name varchar(32) default 'OpenMediaCenter' null,
PRIMARY KEY (id)
);
INSERT INTO tags (tag_id, tag_name)
VALUES (2, 'fullhd');
INSERT INTO tags (tag_id, tag_name)
VALUES (3, 'lowquality');
INSERT INTO tags (tag_id, tag_name)
VALUES (4, 'hd');
INSERT INTO settings (video_path, episode_path, password, mediacenter_name)
VALUES ('./videos/', './tvshows/', -1, 'OpenMediaCenter');

View File

@ -11,37 +11,57 @@ import CategoryPage from "./pages/CategoryPage/CategoryPage";
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {page: "default"};
this.state = {
page: "default",
generalSettingsLoaded: false,
passwordsupport: null,
mediacentername: "OpenMediaCenter"
};
// bind this to the method for being able to call methods such as this.setstate
this.changeRootElement = this.changeRootElement.bind(this);
this.returnToLastElement = this.returnToLastElement.bind(this);
}
componentDidMount() {
const updateRequest = new FormData();
updateRequest.append('action', 'loadInitialData');
fetch('/api/Settings.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
console.log(result);
this.setState({
generalSettingsLoaded: true,
passwordsupport: result.passwordEnabled,
mediacentername: result.mediacenter_name
});
console.log(this.state);
}));
}
newElement = null;
constructViewBinding() {
return {
changeRootElement: this.changeRootElement,
returnToLastElement: this.returnToLastElement
};
}
MainBody() {
let page;
if (this.state.page === "default") {
page = <HomePage viewbinding={{
changeRootElement: this.changeRootElement,
returnToLastElement: this.returnToLastElement
}}/>;
page = <HomePage viewbinding={this.constructViewBinding()}/>;
this.mypage = page;
} else if (this.state.page === "random") {
page = <RandomPage viewbinding={{
changeRootElement: this.changeRootElement,
returnToLastElement: this.returnToLastElement
}}/>;
page = <RandomPage viewbinding={this.constructViewBinding()}/>;
this.mypage = page;
} else if (this.state.page === "settings") {
page = <SettingsPage/>;
this.mypage = page;
} else if (this.state.page === "categories") {
page = <CategoryPage viewbinding={{
changeRootElement: this.changeRootElement,
returnToLastElement: this.returnToLastElement
}}/>;
page = <CategoryPage viewbinding={this.constructViewBinding()}/>;
this.mypage = page;
} else if (this.state.page === "video") {
// show videoelement if neccessary
@ -61,7 +81,7 @@ class App extends React.Component {
return (
<div className="App">
<nav className="navbar navbar-expand-sm bg-primary navbar-dark">
<div className="navbar-brand">OpenMediaCenter</div>
<div className="navbar-brand">{this.state.mediacentername}</div>
<ul className="navbar-nav">
<li className="nav-item">
@ -90,7 +110,7 @@ class App extends React.Component {
</li>
</ul>
</nav>
{this.MainBody()}
{this.state.generalSettingsLoaded ? this.MainBody() : "loading"}
</div>
);
}

View File

@ -2,6 +2,14 @@ import React from 'react';
import App from './App';
import {shallow} from 'enzyme'
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
describe('<App/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<App/>);
@ -20,6 +28,7 @@ describe('<App/>', function () {
it('simulate video view change ', function () {
const wrapper = shallow(<App/>);
wrapper.setState({generalSettingsLoaded: true}); // simulate fetch to have already finisheed
wrapper.instance().changeRootElement(<div id='testit'></div>);
@ -28,6 +37,7 @@ describe('<App/>', function () {
it('test hide video again', function () {
const wrapper = shallow(<App/>);
wrapper.setState({generalSettingsLoaded: true}); // simulate fetch to have already finisheed
wrapper.instance().changeRootElement(<div id='testit'></div>);
@ -40,6 +50,7 @@ describe('<App/>', function () {
it('test fallback to last loaded page', function () {
const wrapper = shallow(<App/>);
wrapper.setState({generalSettingsLoaded: true}); // simulate fetch to have already finisheed
wrapper.find(".nav-link").findWhere(t => t.text() === "Random Video" && t.type() === "div").simulate("click");
@ -54,6 +65,8 @@ describe('<App/>', function () {
it('test home click', function () {
const wrapper = shallow(<App/>);
wrapper.setState({generalSettingsLoaded: true}); // simulate fetch to have already finisheed
wrapper.setState({page: "wrongvalue"});
expect(wrapper.find("HomePage")).toHaveLength(0);
wrapper.find(".nav-link").findWhere(t => t.text() === "Home" && t.type() === "div").simulate("click");
@ -62,6 +75,7 @@ describe('<App/>', function () {
it('test category click', function () {
const wrapper = shallow(<App/>);
wrapper.setState({generalSettingsLoaded: true}); // simulate fetch to have already finisheed
expect(wrapper.find("CategoryPage")).toHaveLength(0);
wrapper.find(".nav-link").findWhere(t => t.text() === "Categories" && t.type() === "div").simulate("click");
@ -70,9 +84,33 @@ describe('<App/>', function () {
it('test settings click', function () {
const wrapper = shallow(<App/>);
wrapper.setState({generalSettingsLoaded: true}); // simulate fetch to have already finisheed
expect(wrapper.find("SettingsPage")).toHaveLength(0);
wrapper.find(".nav-link").findWhere(t => t.text() === "Settings" && t.type() === "div").simulate("click");
expect(wrapper.find("SettingsPage")).toHaveLength(1);
});
it('test initial fetch from api', done => {
global.fetch = prepareFetchApi({
generalSettingsLoaded: true,
passwordsupport: true,
mediacentername: "testname"
});
const wrapper = shallow(<App/>);
const func = jest.fn();
wrapper.instance().setState = func;
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
expect(func).toBeCalledTimes(1);
global.fetch.mockClear();
done();
});
});
});

View File

@ -13,10 +13,6 @@ class Preview extends React.Component {
};
}
componentWillUnmount() {
this.setState({});
}
componentDidMount() {
this.setState({
previewpicture: null,
@ -30,9 +26,9 @@ class Preview extends React.Component {
fetch('/api/videoload.php', {method: 'POST', body: updateRequest})
.then((response) => response.text()
.then((result) => {
this.setState(prevState => ({
...prevState.previewpicture, previewpicture: result
}));
this.setState({
previewpicture: result
});
}));
}

View File

@ -1,7 +1,6 @@
import {shallow, mount} from "enzyme";
import {mount, shallow} from "enzyme";
import React from "react";
import CategoryPage from "./CategoryPage";
import VideoContainer from "../../elements/VideoContainer/VideoContainer";
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
@ -111,4 +110,21 @@ describe('<CategoryPage/>', function () {
expect(func).toBeCalledTimes(1);
});
it('test sidebar tag clicks', function () {
const func = jest.fn();
const wrapper = mount(<CategoryPage category="fullhd"/>);
wrapper.instance().loadTag = func;
console.log(wrapper.debug());
expect(func).toBeCalledTimes(0);
wrapper.find("SideBar").find("Tag").forEach(e => {
e.simulate("click");
})
expect(func).toBeCalledTimes(4);
});
});

View File

@ -0,0 +1,131 @@
import React from "react";
import {Button, Col, Form} from "react-bootstrap";
import style from "./GeneralSettings.module.css"
class GeneralSettings extends React.Component {
constructor(props) {
super(props);
this.state = {
passwordsupport: false,
tmdbsupport: null,
videopath: "",
tvshowpath: "",
mediacentername: "",
password: ""
};
}
componentDidMount() {
const updateRequest = new FormData();
updateRequest.append('action', 'loadGeneralSettings');
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
});
}));
}
render() {
return (
<>
<div className={style.GeneralForm}>
<Form data-testid='mainformsettings' onSubmit={(e) => {
e.preventDefault();
this.saveSettings();
}}>
<Form.Row>
<Form.Group as={Col} data-testid="videpathform">
<Form.Label>Video Path</Form.Label>
<Form.Control type="text" placeholder="/var/www/html/video" value={this.state.videopath}
onChange={(ee) => this.setState({videopath: ee.target.value})}/>
</Form.Group>
<Form.Group as={Col} data-testid="tvshowpath">
<Form.Label>TV Show Path</Form.Label>
<Form.Control type='text' placeholder="/var/www/html/tvshow"
value={this.state.tvshowpath}
onChange={(e) => this.setState({tvshowpath: e.target.value})}/>
</Form.Group>
</Form.Row>
<Form.Check
type="switch"
id="custom-switch"
data-testid='passwordswitch'
label="Enable Password support"
checked={this.state.passwordsupport}
onChange={() => {
this.setState({passwordsupport: !this.state.passwordsupport})
}}
/>
<Form.Check
type="switch"
id="custom-switch-2"
data-testid='tmdb-switch'
label="Enable TMDB video grabbing support"
checked={this.state.tmdbsupport}
onChange={() => {
this.setState({tmdbsupport: !this.state.tmdbsupport})
}}
/>
{this.state.passwordsupport ?
<Form.Group data-testid="passwordfield">
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="**********" value={this.state.password}
onChange={(e) => this.setState({password: e.target.value})}/>
</Form.Group> : null
}
<Form.Group className={style.mediacenternameform} data-testid="nameform">
<Form.Label>The name of the Mediacenter</Form.Label>
<Form.Control type="text" placeholder="Mediacentername" value={this.state.mediacentername}
onChange={(e) => this.setState({mediacentername: e.target.value})}/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
</>
);
}
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);
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
}
}));
}
}
export default GeneralSettings;

View File

@ -0,0 +1,8 @@
.GeneralForm {
width: 60%;
}
.mediacenternameform {
margin-top: 25px;
width: 40%;
}

View File

@ -0,0 +1,110 @@
import {shallow} from "enzyme";
import React from "react";
import GeneralSettings from "./GeneralSettings";
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
describe('<GeneralSettings/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.unmount();
});
it('test password hide/show switchbutton', function () {
const wrapper = shallow(<GeneralSettings/>);
expect(wrapper.find("[data-testid='passwordfield']")).toHaveLength(0);
wrapper.find("FormCheck").findWhere(it => it.props().label === "Enable Password support").simulate("change");
expect(wrapper.find("[data-testid='passwordfield']")).toHaveLength(1);
});
it('test savesettings', done => {
const wrapper = shallow(<GeneralSettings/>);
global.fetch = prepareFetchApi({success: true});
expect(global.fetch).toBeCalledTimes(0);
const fakeEvent = {preventDefault: () => console.log('preventDefault')};
wrapper.find("[data-testid='mainformsettings']").simulate("submit", fakeEvent);
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
// todo 2020-07-13: test popup of error success here
global.fetch.mockClear();
done();
});
});
it('test failing savesettings', done => {
const wrapper = shallow(<GeneralSettings/>);
global.fetch = prepareFetchApi({success: false});
expect(global.fetch).toBeCalledTimes(0);
const fakeEvent = {preventDefault: () => console.log('preventDefault')};
wrapper.find("[data-testid='mainformsettings']").simulate("submit", fakeEvent);
expect(global.fetch).toBeCalledTimes(1);
process.nextTick(() => {
// todo 2020-07-13: test error popup here!
global.fetch.mockClear();
done();
});
});
it('test videopath change event', function () {
const wrapper = shallow(<GeneralSettings/>);
expect(wrapper.state().videopath).not.toBe("test");
const event = {target: {name: "pollName", value: "test"}};
wrapper.find("[data-testid='videpathform']").find("FormControl").simulate("change", event);
expect(wrapper.state().videopath).toBe("test");
});
it('test tvshowpath change event', function () {
const wrapper = shallow(<GeneralSettings/>);
const event = {target: {name: "pollName", value: "test"}};
expect(wrapper.state().tvshowpath).not.toBe("test");
wrapper.find("[data-testid='tvshowpath']").find("FormControl").simulate("change", event);
expect(wrapper.state().tvshowpath).toBe("test");
});
it('test mediacentername-form change event', function () {
const wrapper = shallow(<GeneralSettings/>);
const event = {target: {name: "pollName", value: "test"}};
expect(wrapper.state().mediacentername).not.toBe("test");
wrapper.find("[data-testid='nameform']").find("FormControl").simulate("change", event);
expect(wrapper.state().mediacentername).toBe("test");
});
it('test password-form change event', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.setState({passwordsupport: true});
const event = {target: {name: "pollName", value: "test"}};
expect(wrapper.state().password).not.toBe("test");
wrapper.find("[data-testid='passwordfield']").find("FormControl").simulate("change", event);
expect(wrapper.state().password).toBe("test");
});
it('test tmdbsupport change event', function () {
const wrapper = shallow(<GeneralSettings/>);
wrapper.setState({tmdbsupport: true});
expect(wrapper.state().tmdbsupport).toBe(true);
wrapper.find("[data-testid='tmdb-switch']").simulate("change");
expect(wrapper.state().tmdbsupport).toBe(false);
});
});

View File

@ -0,0 +1,89 @@
import React from "react";
import style from "./MovieSettings.module.css"
class MovieSettings extends React.Component {
constructor(props) {
super(props);
this.state = {
text: [],
startbtnDisabled: false
};
}
componentDidMount() {
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
}
componentWillUnmount() {
clearInterval(this.myinterval);
}
render() {
return (
<>
<button disabled={this.state.startbtnDisabled} className='reindexbtn btn btn-success' onClick={() => {
this.startReindex()
}}>Reindex Movies
</button>
<div className={style.indextextarea}>{this.state.text.map(m => (
<div className='textarea-element'>{m}</div>
))}</div>
</>
);
}
startReindex() {
// clear output text before start
this.setState({text: []});
this.setState({startbtnDisabled: true});
console.log("starting");
const updateRequest = new FormData();
// fetch all videos available
fetch('/api/extractvideopreviews.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
// todo 2020-07-4: some kind of start event
console.log("returned");
}))
.catch(() => {
console.log("no connection to backend");
});
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
}
updateStatus = () => {
const updateRequest = new FormData();
fetch('/api/extractionData.php', {method: 'POST', body: updateRequest})
.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");
});
};
}
export default MovieSettings;

View File

@ -0,0 +1,13 @@
.indextextarea {
margin-top: 15px;
padding: 10px;
overflow-y: scroll;
overflow-x: auto;
min-height: 100px;
max-height: 300px;
width: 50%;
background-color: #c2c2c2;
border-radius: 5px;
}

View File

@ -0,0 +1,62 @@
import {shallow} from "enzyme";
import React from "react";
import MovieSettings from "./MovieSettings";
function prepareFetchApi(response) {
const mockJsonPromise = Promise.resolve(response);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
return (jest.fn().mockImplementation(() => mockFetchPromise));
}
describe('<MovieSettings/>', function () {
it('renders without crashing ', function () {
const wrapper = shallow(<MovieSettings/>);
wrapper.unmount();
});
it('received text renders into dom', function () {
const wrapper = shallow(<MovieSettings/>);
wrapper.setState({
text: [
"firstline",
"secline"
]
});
expect(wrapper.find(".indextextarea").find(".textarea-element")).toHaveLength(2);
});
it('test simulate reindex', function () {
global.fetch = prepareFetchApi({});
const wrapper = shallow(<MovieSettings/>);
wrapper.find(".reindexbtn").simulate("click");
// initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1);
});
it('content available received and in state', done => {
global.fetch = prepareFetchApi({
contentAvailable: true,
message: "firstline\nsecondline"
});
const wrapper = shallow(<MovieSettings/>);
wrapper.instance().updateStatus();
process.nextTick(() => {
expect(wrapper.state()).toMatchObject({
text: [
"firstline",
"secondline"
]
});
global.fetch.mockClear();
done();
});
});
});

View File

@ -1,82 +1,52 @@
import React from "react";
import PageTitle from "../../elements/PageTitle/PageTitle";
import MovieSettings from "./MovieSettings";
import GeneralSettings from "./GeneralSettings";
import style from "./SettingsPage.module.css"
class SettingsPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
text: []
currentpage: "general"
};
}
updateStatus = () => {
const updateRequest = new FormData();
fetch('/api/extractionData.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
if (result.contentAvailable === true) {
console.log(result);
this.setState({
text: [...result.message.split("\n"),
...this.state.text]
});
} else {
clearInterval(this.myinterval);
getContent() {
switch (this.state.currentpage) {
case "general":
return <GeneralSettings/>;
case "movies":
return <MovieSettings/>;
case "tv":
return <span/>; // todo this page
default:
return "unknown button clicked";
}
}))
.catch(() => {
console.log("no connection to backend");
});
};
componentDidMount() {
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
}
componentWillUnmount() {
clearInterval(this.myinterval);
}
render() {
return (
<div>
<PageTitle
title='Settings Page'
subtitle='todo'/>
<button className='reindexbtn btn btn-success' onClick={() => {
this.startReindex()
}}>Reindex Movies
</button>
<div className='indextextarea'>{this.state.text.map(m => (
<div className='textarea-element'>{m}</div>
))}</div>
<div className={style.SettingsSidebar}>
<div className={style.SettingsSidebarTitle}>Settings</div>
<div onClick={() => this.setState({currentpage: "general"})}
className={style.SettingSidebarElement}>General
</div>
<div onClick={() => this.setState({currentpage: "movies"})}
className={style.SettingSidebarElement}>Movies
</div>
<div onClick={() => this.setState({currentpage: "tv"})}
className={style.SettingSidebarElement}>TV Shows
</div>
</div>
<div className={style.SettingsContent}>
{this.getContent()}
</div>
</div>
);
}
startReindex() {
console.log("starting");
const updateRequest = new FormData();
// fetch all videos available
fetch('/api/extractvideopreviews.php', {method: 'POST', body: updateRequest})
.then((response) => response.json()
.then((result) => {
console.log("returned");
}))
.catch(() => {
console.log("no connection to backend");
});
if (this.myinterval) {
clearInterval(this.myinterval);
}
this.myinterval = setInterval(this.updateStatus, 1000);
console.log("sent");
}
}
export default SettingsPage;

View File

@ -0,0 +1,40 @@
.SettingsSidebar {
padding-top: 20px;
float: left;
width: 10%;
background-color: #d3dcef;
min-height: calc(100vh - 56px);
min-width: 110px;
}
.SettingsSidebarTitle {
text-align: center;
font-weight: bold;
text-transform: uppercase;
font-size: larger;
margin-bottom: 25px;
}
.SettingsContent {
float: left;
width: 80%;
padding-left: 30px;
padding-top: 30px;
}
.SettingSidebarElement {
margin: 10px 5px 5px;
padding: 5px;
background-color: #a8b2de;
text-align: center;
font-weight: bold;
border-radius: 7px;
}
.SettingSidebarElement:hover {
font-weight: bolder;
background-color: #7d8dd4;
box-shadow: #7d8dd4 0 0 0 5px;
transition: all 300ms;
cursor: pointer;
}

View File

@ -16,26 +16,33 @@ describe('<RandomPage/>', function () {
wrapper.unmount();
});
it('received text renders into dom', function () {
it('simulate topic clicka', function () {
const wrapper = shallow(<SettingsPage/>);
wrapper.setState({
text: [
"firstline",
"secline"
]
simulateSideBarClick("General", wrapper);
expect(wrapper.state().currentpage).toBe("general");
expect(wrapper.find(".SettingsContent").find("GeneralSettings")).toHaveLength(1);
simulateSideBarClick("Movies", wrapper);
expect(wrapper.state().currentpage).toBe("movies");
expect(wrapper.find(".SettingsContent").find("MovieSettings")).toHaveLength(1);
simulateSideBarClick("TV Shows", wrapper);
expect(wrapper.state().currentpage).toBe("tv");
expect(wrapper.find(".SettingsContent").find("span")).toHaveLength(1);
});
expect(wrapper.find(".indextextarea").find(".textarea-element")).toHaveLength(2);
});
function simulateSideBarClick(name, wrapper) {
wrapper.find(".SettingSidebarElement").findWhere(it =>
it.text() === name &&
it.type() === "div").simulate("click");
}
it('test simulate reindex', function () {
global.fetch = prepareFetchApi({});
it('simulate unknown topic', function () {
const wrapper = shallow(<SettingsPage/>);
wrapper.setState({currentpage: "unknown"});
wrapper.find(".reindexbtn").simulate("click");
expect(wrapper.find(".SettingsContent").text()).toBe("unknown button clicked");
// initial send of reindex request to server
expect(global.fetch).toBeCalledTimes(1);
});
});