diff --git a/README.md b/README.md index 3440fda..e017093 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,18 @@ Feel free to contribute or open an issue here: https://gitlab.heili.eu/lukas/ope ## What is this? Open Media Center is an open source solution for a mediacenter in your home network. Transform your webserver into a mediaserver. -It's based on Reactjs and PHP is used as backend. +It's based on Reactjs and PHP is used for backend. It is optimized for general videos as well as for movies. For grabbing movie data TMDB is used. -For organizing videos tags are used. +With the help of tags you can organize your video gravity. -Here you can see an example main page: +Here you can see an example main page in light mode: -![Image of OpenMediaCenter](https://i.ibb.co/2PC3fmk/Screenshot-20200604-163448.png) +![Image of OpenMediaCenter](https://i.ibb.co/pnDjgNT/Screenshot-20200812-172945.png) + +and in dark mode: + +![](https://i.ibb.co/xzhdsbJ/Screenshot-20200812-172926.png) ## Installation First of all clone the repository. @@ -32,9 +36,9 @@ You need also to setup a Database with the structure described in [SQL Style Ref The login data to this database needs to be specified in the `api/Database.php` file. ## Usage -To index Videos run on your server: `php extractvideopreviews.php`. +Now you can access your MediaCenter via your servers global ip (: -Now you can access your MediaCenter via the servers global ip (: +At the settings tab you can set the correct videopath on server and click reindex afterwards. ## Contact Any contribution is appreciated. diff --git a/api/Tags.php b/api/Tags.php deleted file mode 100644 index 2953419..0000000 --- a/api/Tags.php +++ /dev/null @@ -1,30 +0,0 @@ -addActionHandler("getAllTags", function () { - $query = "SELECT tag_name,tag_id from tags"; - - $result = $this->conn->query($query); - $rows = array(); - while ($r = mysqli_fetch_assoc($result)) { - array_push($rows, $r); - } - echo json_encode($rows); - }); - - $this->addActionHandler("createTag", function (){ - $query = "INSERT INTO tags (tag_name) VALUES ('" . $_POST['tagname'] . "')"; - - if ($this->conn->query($query) === TRUE) { - echo('{"result":"success"}'); - } else { - echo('{"result":"' . $this->conn->error . '"}'); - } - }); - } -} - -$tags = new Tags(); -$tags->handleAction(); diff --git a/api/extractvideopreviews.php b/api/extractvideopreviews.php index 2f5076e..38f77ac 100755 --- a/api/extractvideopreviews.php +++ b/api/extractvideopreviews.php @@ -1,7 +1,7 @@ handleAction(); diff --git a/api/Database.php b/api/src/Database.php similarity index 87% rename from api/Database.php rename to api/src/Database.php index f12d6ac..bbea467 100644 --- a/api/Database.php +++ b/api/src/Database.php @@ -5,8 +5,7 @@ * * Class with all neccessary stuff for the Database connections. */ -class Database -{ +class Database { private static ?Database $instance = null; private mysqli $conn; @@ -16,8 +15,7 @@ class Database private string $dbname = "mediacenter"; // The db connection is established in the private constructor. - private function __construct() - { + private function __construct() { // Create connection $this->conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname); @@ -32,8 +30,7 @@ class Database * * @return Database dbobject */ - public static function getInstance() - { + public static function getInstance() { if (!self::$instance) { self::$instance = new Database(); } @@ -46,8 +43,7 @@ class Database * * @return mysqli mysqli instance */ - public function getConnection() - { + public function getConnection() { return $this->conn; } @@ -55,7 +51,7 @@ class Database * get name of current active database * @return string name */ - public function getDatabaseName(){ + public function getDatabaseName() { return $this->dbname; } } diff --git a/api/SSettings.php b/api/src/SSettings.php similarity index 74% rename from api/SSettings.php rename to api/src/SSettings.php index 965ba0b..8d32ca2 100644 --- a/api/SSettings.php +++ b/api/src/SSettings.php @@ -1,7 +1,10 @@ database = Database::getInstance(); } + /** + * get the videopath saved in db + * @return string videopath + */ public function getVideoPath() { $query = "SELECT video_path from settings"; @@ -24,16 +31,15 @@ class SSettings * check if TMDB is enableds * @return bool isenabled? */ - public function isTMDBGrabbingEnabled(): bool - { + public function isTMDBGrabbingEnabled(): bool { $query = "SELECT TMDB_grabbing from settings"; $result = $this->database->getConnection()->query($query); - if(!$result){ + if (!$result) { return true; // if undefined in db --> default true - }else{ + } else { $r = mysqli_fetch_assoc($result); return $r['TMDB_grabbing'] == '1'; } } -} \ No newline at end of file +} diff --git a/api/TMDBMovie.php b/api/src/TMDBMovie.php similarity index 83% rename from api/TMDBMovie.php rename to api/src/TMDBMovie.php index e4fe023..5d481d3 100644 --- a/api/TMDBMovie.php +++ b/api/src/TMDBMovie.php @@ -1,10 +1,13 @@ baseurl . "search/movie?api_key=" . $this->apikey . "&query=" . urlencode($moviename))); if ($reply->total_results == 0) { // no results found @@ -29,8 +31,7 @@ class TMDBMovie * * @return array of all available genres */ - public function getAllGenres() - { + public function getAllGenres() { $reply = json_decode(file_get_contents($this->baseurl . "genre/movie/list?api_key=" . $this->apikey)); return $reply->genres; } diff --git a/api/RequestBase.php b/api/src/handlers/RequestBase.php similarity index 80% rename from api/RequestBase.php rename to api/src/handlers/RequestBase.php index 3eea2ec..25f3d6d 100644 --- a/api/RequestBase.php +++ b/api/src/handlers/RequestBase.php @@ -1,14 +1,9 @@ getFromDB(); + $this->saveToDB(); + } + + /** + * handle settings stuff to load from db + */ + private function getFromDB(){ + /** + * load currently set settings form db for init of settings page + */ $this->addActionHandler("loadGeneralSettings", function () { $query = "SELECT * from settings"; @@ -16,6 +31,30 @@ class Settings extends RequestBase { echo json_encode($r); }); + /** + * load initial data for home page load to check if pwd is set + */ + $this->addActionHandler("loadInitialData", function () { + $query = "SELECT * from settings"; + + $result = $this->conn->query($query); + + $r = mysqli_fetch_assoc($result); + + $r['passwordEnabled'] = $r['password'] != "-1"; + unset($r['password']); + $r['DarkMode'] = (bool)($r['DarkMode'] != '0'); + $this->commitMessage(json_encode($r)); + }); + } + + /** + * handle setting stuff to save to db + */ + private function saveToDB(){ + /** + * save changed settings to db + */ $this->addActionHandler("saveGeneralSettings", function () { $mediacentername = $_POST['mediacentername']; $password = $_POST['password']; @@ -34,26 +73,10 @@ class Settings extends RequestBase { WHERE 1"; if ($this->conn->query($query) === true) { - echo '{"success": true}'; + $this->commitMessage('{"success": true}'); } else { - echo '{"success": true}'; + $this->commitMessage('{"success": true}'); } }); - - $this->addActionHandler("loadInitialData", function () { - $query = "SELECT * from settings"; - - $result = $this->conn->query($query); - - $r = mysqli_fetch_assoc($result); - - $r['passwordEnabled'] = $r['password'] != "-1"; - unset($r['password']); - $r['DarkMode'] = (bool)($r['DarkMode'] != '0'); - echo json_encode($r); - }); } } - -$sett = new Settings(); -$sett->handleAction(); diff --git a/api/src/handlers/Tags.php b/api/src/handlers/Tags.php new file mode 100644 index 0000000..1a49cda --- /dev/null +++ b/api/src/handlers/Tags.php @@ -0,0 +1,66 @@ +addToDB(); + $this->getFromDB(); + } + + private function getFromDB(){ + /** + * returns all available tags from database + */ + $this->addActionHandler("getAllTags", function () { + $query = "SELECT tag_name,tag_id from tags"; + $result = $this->conn->query($query); + + $rows = array(); + while ($r = mysqli_fetch_assoc($result)) { + array_push($rows, $r); + } + $this->commitMessage(json_encode($rows)); + }); + } + + private function addToDB(){ + /** + * creates a new tag + * query requirements: + * * tagname -- name of the new tag + */ + $this->addActionHandler("createTag", function () { + $query = "INSERT INTO tags (tag_name) VALUES ('" . $_POST['tagname'] . "')"; + + if ($this->conn->query($query) === TRUE) { + $this->commitMessage('{"result":"success"}'); + } else { + $this->commitMessage('{"result":"' . $this->conn->error . '"}'); + } + }); + + /** + * adds a new tag to an existing video + * + * query requirements: + * * movieid -- the id of the video to add the tag to + * * id -- the tag id which tag to add + */ + $this->addActionHandler("addTag", function () { + $movieid = $_POST['movieid']; + $tagid = $_POST['id']; + + $query = "INSERT INTO video_tags(tag_id, video_id) VALUES ('$tagid','$movieid')"; + + if ($this->conn->query($query) === TRUE) { + $this->commitMessage('{"result":"success"}'); + } else { + $this->commitMessage('{"result":"' . $this->conn->error . '"}'); + } + }); + } +} diff --git a/api/videoload.php b/api/src/handlers/Video.php similarity index 72% rename from api/videoload.php rename to api/src/handlers/Video.php index b2c2e0f..4d2ecfe 100755 --- a/api/videoload.php +++ b/api/src/handlers/Video.php @@ -1,8 +1,11 @@ getVideos(); + $this->loadVideos(); + $this->addToVideo(); + } + + /** + * function handles load of all videos and search for videos + */ + private function getVideos() { $this->addActionHandler("getMovies", function () { $query = "SELECT movie_id,movie_name FROM videos ORDER BY create_date DESC, movie_name"; if (isset($_POST['tag'])) { @@ -31,7 +43,7 @@ class Video extends RequestBase { array_push($rows, $r); } - echo(json_encode($rows)); + $this->commitMessage(json_encode($rows)); }); $this->addActionHandler("getRandomMovies", function () { @@ -59,7 +71,7 @@ class Video extends RequestBase { array_push($return->tags, $r); } - echo(json_encode($return)); + $this->commitMessage(json_encode($return)); }); $this->addActionHandler("getSearchKeyWord", function () { @@ -74,9 +86,14 @@ class Video extends RequestBase { array_push($rows, $r); } - echo(json_encode($rows)); + $this->commitMessage(json_encode($rows)); }); + } + /** + * function to handle stuff for loading specific videos and startdata + */ + private function loadVideos() { $this->addActionHandler("loadVideo", function () { $query = "SELECT movie_name,movie_id,movie_url,thumbnail,poster,likes,quality,length FROM videos WHERE movie_id='" . $_POST['movieid'] . "'"; @@ -110,23 +127,7 @@ class Video extends RequestBase { array_push($arr['tags'], $r); } - echo(json_encode($arr)); - }); - - $this->addActionHandler("getDbSize", function () { - $dbname = Database::getInstance()->getDatabaseName(); - - $query = "SELECT table_schema AS \"Database\", - ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS \"Size\" - FROM information_schema.TABLES - WHERE TABLE_SCHEMA='$dbname' - GROUP BY table_schema;"; - $result = $this->conn->query($query); - - if ($result->num_rows == 1) { - $row = $result->fetch_assoc(); - echo '{"data":"' . $row["Size"] . 'MB"}'; - } + $this->commitMessage(json_encode($arr)); }); $this->addActionHandler("readThumbnail", function () { @@ -135,38 +136,7 @@ class Video extends RequestBase { $result = $this->conn->query($query); $row = $result->fetch_assoc(); - echo($row["thumbnail"]); - }); - - $this->addActionHandler("getTags", function () { - // todo add this to loadVideo maybe - $movieid = $_POST['movieid']; - - $query = "SELECT tag_name FROM video_tags - INNER JOIN tags t on video_tags.tag_id = t.tag_id - WHERE video_id='$movieid'"; - - $result = $this->conn->query($query); - - $rows = array(); - $rows['tags'] = array(); - while ($r = mysqli_fetch_assoc($result)) { - array_push($rows['tags'], $r['tag_name']); - } - - echo(json_encode($rows)); - }); - - $this->addActionHandler("addLike", function () { - $movieid = $_POST['movieid']; - - $query = "update videos set likes = likes + 1 where movie_id = '$movieid'"; - - if ($this->conn->query($query) === TRUE) { - echo('{"result":"success"}'); - } else { - echo('{"result":"' . $this->conn->error . '"}'); - } + $this->commitMessage($row["thumbnail"]); }); $this->addActionHandler("getStartData", function () { @@ -213,34 +183,24 @@ class Video extends RequestBase { $r = mysqli_fetch_assoc($result); $arr['tags'] = $r['nr']; - echo(json_encode($arr)); + $this->commitMessage(json_encode($arr)); }); + } - $this->addActionHandler("getAllTags", function () { - $query = "SELECT tag_name,tag_id from tags"; - $result = $this->conn->query($query); - - $rows = array(); - while ($r = mysqli_fetch_assoc($result)) { - array_push($rows, $r); - } - echo(json_encode($rows)); - }); - - $this->addActionHandler("addTag", function () { + /** + * function to handle api handlers for stuff to add to video or database + */ + private function addToVideo() { + $this->addActionHandler("addLike", function () { $movieid = $_POST['movieid']; - $tagid = $_POST['id']; - $query = "INSERT INTO video_tags(tag_id, video_id) VALUES ('$tagid','$movieid')"; + $query = "update videos set likes = likes + 1 where movie_id = '$movieid'"; if ($this->conn->query($query) === TRUE) { - echo('{"result":"success"}'); + $this->commitMessage('{"result":"success"}'); } else { - echo('{"result":"' . $this->conn->error . '"}'); + $this->commitMessage('{"result":"' . $this->conn->error . '"}'); } }); } } - -$video = new Video(); -$video->handleAction(); diff --git a/api/tags.php b/api/tags.php new file mode 100644 index 0000000..88ce8fe --- /dev/null +++ b/api/tags.php @@ -0,0 +1,5 @@ +handleAction(); diff --git a/api/video.php b/api/video.php new file mode 100644 index 0000000..70ca49a --- /dev/null +++ b/api/video.php @@ -0,0 +1,5 @@ +handleAction(); diff --git a/src/App.js b/src/App.js index 6aba9c4..fa8ecd6 100644 --- a/src/App.js +++ b/src/App.js @@ -10,6 +10,9 @@ import style from './App.module.css' import SettingsPage from "./pages/SettingsPage/SettingsPage"; import CategoryPage from "./pages/CategoryPage/CategoryPage"; +/** + * The main App handles the main tabs and which content to show + */ class App extends React.Component { newElement = null; @@ -31,7 +34,7 @@ class App extends React.Component { const updateRequest = new FormData(); updateRequest.append('action', 'loadInitialData'); - fetch('/api/Settings.php', {method: 'POST', body: updateRequest}) + fetch('/api/settings.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { // set theme @@ -47,6 +50,10 @@ class App extends React.Component { })); } + /** + * create a viewbinding to call APP functions from child elements + * @returns a set of callback functions + */ constructViewBinding() { return { changeRootElement: this.changeRootElement, @@ -54,6 +61,10 @@ class App extends React.Component { }; } + /** + * load the selected component into the main view + * @returns {JSX.Element} body element of selected page + */ MainBody() { let page; if (this.state.page === "default") { @@ -109,6 +120,9 @@ class App extends React.Component { ); } + /** + * render a new root element into the main body + */ changeRootElement(element) { this.newElement = element; @@ -117,6 +131,9 @@ class App extends React.Component { }); } + /** + * return from page to the previous page before a change + */ returnToLastElement() { this.setState({ page: "lastpage" diff --git a/src/App.module.css b/src/App.module.css index adb0857..c4af91d 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -1,12 +1,12 @@ .navitem { - float: left; - margin-left: 20px; - cursor: pointer; - opacity: 0.6; + float: left; font-size: large; font-weight: bold; + + margin-left: 20px; + opacity: 0.6; text-transform: capitalize; } @@ -18,9 +18,9 @@ .navitem::after { content: ''; display: block; - width: 0; height: 2px; transition: width .3s; + width: 0; } .navitem:hover::after { @@ -33,22 +33,22 @@ } .navcontainer { + border-bottom-width: 2px; + border-style: dotted; + border-width: 0; + + padding-bottom: 40px; padding-top: 20px; width: 100%; - padding-bottom: 40px; - - border-width: 0; - border-style: dotted; - border-bottom-width: 2px; } .navbrand { - margin-left: 20px; - margin-right: 20px; + float: left; font-size: large; font-weight: bold; + margin-left: 20px; + margin-right: 20px; text-transform: capitalize; - float: left; } diff --git a/src/GlobalInfos.js b/src/GlobalInfos.js index af86c41..5c45ac4 100644 --- a/src/GlobalInfos.js +++ b/src/GlobalInfos.js @@ -1,23 +1,37 @@ import darktheme from "./AppDarkTheme.module.css"; import lighttheme from "./AppLightTheme.module.css"; +/** + * This class is available for all components in project + * it contains general infos about app - like theme + */ class StaticInfos { #darktheme = true; + /** + * check if the current theme is the dark theme + * @returns {boolean} is dark theme? + */ isDarkTheme() { return this.#darktheme; }; - enableDarkTheme(enable = true){ + /** + * setter to enable or disable the dark or light theme + * @param enable enable the dark theme? + */ + enableDarkTheme(enable = true) { this.#darktheme = enable; } - getThemeStyle(){ + /** + * get the currently selected theme stylesheet + * @returns {*} the style object of the current active theme + */ + getThemeStyle() { return this.isDarkTheme() ? darktheme : lighttheme; } } const GlobalInfos = new StaticInfos(); -//Object.freeze(StaticInfos); - export default GlobalInfos; diff --git a/src/elements/AddTagPopup/AddTagPopup.js b/src/elements/AddTagPopup/AddTagPopup.js index d49ecfd..d6b8a5a 100644 --- a/src/elements/AddTagPopup/AddTagPopup.js +++ b/src/elements/AddTagPopup/AddTagPopup.js @@ -3,6 +3,9 @@ import Modal from 'react-bootstrap/Modal' import Dropdown from "react-bootstrap/Dropdown"; import DropdownButton from "react-bootstrap/DropdownButton"; +/** + * component creates overlay to add a new tag to a video + */ class AddTagPopup extends React.Component { constructor(props, context) { super(props, context); @@ -22,7 +25,7 @@ class AddTagPopup extends React.Component { const updateRequest = new FormData(); updateRequest.append('action', 'getAllTags'); - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/tags.php', {method: 'POST', body: updateRequest}) .then((response) => response.json()) .then((result) => { this.setState({ @@ -68,13 +71,16 @@ class AddTagPopup extends React.Component { ); } + /** + * store the filled in form to the backend + */ storeselection() { const updateRequest = new FormData(); updateRequest.append('action', 'addTag'); updateRequest.append('id', this.state.selection.id); updateRequest.append('movieid', this.props.movie_id); - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/tags.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { if (result.result !== "success") { diff --git a/src/elements/NewTagPopup/NewTagPopup.js b/src/elements/NewTagPopup/NewTagPopup.js index 13e5710..bbe6a04 100644 --- a/src/elements/NewTagPopup/NewTagPopup.js +++ b/src/elements/NewTagPopup/NewTagPopup.js @@ -2,6 +2,9 @@ import React from "react"; import Modal from 'react-bootstrap/Modal' import {Form} from "react-bootstrap"; +/** + * creates modal overlay to define a new Tag + */ class NewTagPopup extends React.Component { constructor(props, context) { super(props, context); @@ -45,12 +48,15 @@ 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}) + fetch('/api/tags.php', {method: 'POST', body: updateRequest}) .then((response) => response.json()) .then((result) => { if (result.result !== "success") { diff --git a/src/elements/PageTitle/PageTitle.js b/src/elements/PageTitle/PageTitle.js index a71d946..df3096a 100644 --- a/src/elements/PageTitle/PageTitle.js +++ b/src/elements/PageTitle/PageTitle.js @@ -2,14 +2,10 @@ import React from "react"; import style from "./PageTitle.module.css" import GlobalInfos from "../../GlobalInfos"; +/** + * Component for generating PageTitle with bottom Line + */ class PageTitle extends React.Component { - constructor(props) { - super(props); - - this.props = props; - - } - render() { const themeStyle = GlobalInfos.getThemeStyle(); return ( diff --git a/src/elements/PageTitle/PageTitle.module.css b/src/elements/PageTitle/PageTitle.module.css index 76b3fcb..d5ffae7 100644 --- a/src/elements/PageTitle/PageTitle.module.css +++ b/src/elements/PageTitle/PageTitle.module.css @@ -8,7 +8,7 @@ } .pageheadersubtitle { - margin-left: 20px; font-size: 23pt; + margin-left: 20px; opacity: 0.6; } diff --git a/src/elements/Preview/Preview.js b/src/elements/Preview/Preview.js index 0a1cb5a..3c6b1f3 100644 --- a/src/elements/Preview/Preview.js +++ b/src/elements/Preview/Preview.js @@ -4,6 +4,10 @@ import Player from "../../pages/Player/Player"; import {Spinner} from "react-bootstrap"; import GlobalInfos from "../../GlobalInfos"; +/** + * Component for single preview tile + * floating side by side + */ class Preview extends React.Component { constructor(props, context) { super(props, context); @@ -24,7 +28,7 @@ class Preview extends React.Component { updateRequest.append('action', 'readThumbnail'); updateRequest.append('movieid', this.props.movie_id); - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.text() .then((result) => { this.setState({ @@ -36,8 +40,8 @@ class Preview extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); return ( -
this.itemClick()}> -
{this.state.name}
+
this.itemClick()}> +
{this.state.name}
{this.state.previewpicture !== null ? this.itemClick()}> +
this.itemClick()}>
{this.props.name}
@@ -75,6 +85,9 @@ export class TagPreview extends React.Component { ); } + /** + * handle the click event of a Tag tile + */ itemClick() { this.props.categorybinding(this.props.name); } diff --git a/src/elements/Preview/Preview.module.css b/src/elements/Preview/Preview.module.css index ae23695..756327d 100644 --- a/src/elements/Preview/Preview.module.css +++ b/src/elements/Preview/Preview.module.css @@ -1,11 +1,11 @@ .previewtitle { - height: 20px; - text-align: center; font-size: smaller; font-weight: bold; height: 20px; + height: 20px; max-width: 266px; text-align: center; + text-align: center; } .previewpic { diff --git a/src/elements/SideBar/SideBar.js b/src/elements/SideBar/SideBar.js index 1b050a8..15573da 100644 --- a/src/elements/SideBar/SideBar.js +++ b/src/elements/SideBar/SideBar.js @@ -2,24 +2,33 @@ import React from "react"; import style from "./SideBar.module.css" import GlobalInfos from "../../GlobalInfos"; +/** + * component for sidebar-info + */ class SideBar extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); - return (
+ return (
{this.props.children}
); } } +/** + * The title of the sidebar + */ export class SideBarTitle extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); return ( -
{this.props.children}
+
{this.props.children}
); } } +/** + * An item of the sidebar + */ export class SideBarItem extends React.Component { render() { const themeStyle = GlobalInfos.getThemeStyle(); diff --git a/src/elements/Tag/Tag.js b/src/elements/Tag/Tag.js index d4885da..45e5f43 100644 --- a/src/elements/Tag/Tag.js +++ b/src/elements/Tag/Tag.js @@ -3,6 +3,9 @@ import React from "react"; import styles from "./Tag.module.css" import CategoryPage from "../../pages/CategoryPage/CategoryPage"; +/** + * A Component representing a single Category tag + */ class Tag extends React.Component { render() { return ( @@ -11,9 +14,13 @@ class Tag extends React.Component { ); } + /** + * click handling for a Tag + */ TagClick() { const tag = this.props.children.toString().toLowerCase(); + // call callback functin to switch to category page with specified tag this.props.viewbinding.changeRootElement( load new previews if on bottom + */ trackScrolling = () => { - // comparison if current scroll position is on bottom - // 200 stands for bottom offset to trigger load + // comparison if current scroll position is on bottom --> 200 is bottom offset to trigger load if (window.innerHeight + document.documentElement.scrollTop + 200 >= document.documentElement.offsetHeight) { this.loadPreviewBlock(8); } diff --git a/src/pages/CategoryPage/CategoryPage.js b/src/pages/CategoryPage/CategoryPage.js index 2e26468..d10b834 100644 --- a/src/pages/CategoryPage/CategoryPage.js +++ b/src/pages/CategoryPage/CategoryPage.js @@ -8,6 +8,10 @@ import NewTagPopup from "../../elements/NewTagPopup/NewTagPopup"; import PageTitle, {Line} from "../../elements/PageTitle/PageTitle"; import VideoContainer from "../../elements/VideoContainer/VideoContainer"; +/** + * Component for Category Page + * Contains a Tag Overview and loads specific Tag videos in VideoContainer + */ class CategoryPage extends React.Component { constructor(props, context) { super(props, context); @@ -27,7 +31,11 @@ class CategoryPage extends React.Component { } } - render() { + /** + * render the Title and SideBar component for the Category page + * @returns {JSX.Element} corresponding jsx element for Title and Sidebar + */ + renderSideBarATitle() { return ( <> Add a new Tag! - + + ); + } + + render() { + return ( + <> + {this.renderSideBarATitle()} {this.state.selected ? <> @@ -100,10 +115,18 @@ class CategoryPage extends React.Component { ); } + /** + * load a specific tag into a new previewcontainer + * @param tagname + */ loadTag = (tagname) => { this.fetchVideoData(tagname); }; + /** + * fetch data for a specific tag from backend + * @param tag tagname + */ fetchVideoData(tag) { console.log(tag); const updateRequest = new FormData(); @@ -113,7 +136,7 @@ class CategoryPage extends React.Component { console.log("fetching data"); // fetch all videos available - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { this.videodata = result; @@ -125,6 +148,9 @@ class CategoryPage extends React.Component { }); } + /** + * go back to the default category overview + */ loadCategoryPageDefault = () => { this.setState({selected: null}); this.loadTags(); @@ -138,7 +164,7 @@ class CategoryPage extends React.Component { updateRequest.append('action', 'getAllTags'); // fetch all videos available - fetch('/api/Tags.php', {method: 'POST', body: updateRequest}) + fetch('/api/tags.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { this.setState({loadedtags: result}); diff --git a/src/pages/HomePage/HomePage.js b/src/pages/HomePage/HomePage.js index 4b2a14e..cd05329 100644 --- a/src/pages/HomePage/HomePage.js +++ b/src/pages/HomePage/HomePage.js @@ -6,6 +6,9 @@ import VideoContainer from "../../elements/VideoContainer/VideoContainer"; import style from "./HomePage.module.css" import PageTitle, {Line} from "../../elements/PageTitle/PageTitle"; +/** + * The home page component showing on the initial pageload + */ class HomePage extends React.Component { /** keyword variable needed temporary store search keyword */ keyword = ""; @@ -47,7 +50,7 @@ class HomePage extends React.Component { console.log("fetching data"); // fetch all videos available - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { this.setState({ @@ -71,7 +74,7 @@ class HomePage extends React.Component { updateRequest.append('action', 'getStartData'); // fetch all videos available - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { this.setState({ @@ -102,7 +105,7 @@ class HomePage extends React.Component { updateRequest.append('keyword', keyword); // fetch all videos available - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { this.setState({ diff --git a/src/pages/Player/Player.js b/src/pages/Player/Player.js index d9b4906..18d3801 100644 --- a/src/pages/Player/Player.js +++ b/src/pages/Player/Player.js @@ -8,6 +8,10 @@ import AddTagPopup from "../../elements/AddTagPopup/AddTagPopup"; import PageTitle, {Line} from "../../elements/PageTitle/PageTitle"; +/** + * Player page loads when a video is selected to play and handles the video view + * and actions such as tag adding and liking + */ class Player extends React.Component { options = { controls: [ @@ -59,7 +63,8 @@ class Player extends React.Component { {this.state.quality !== 0 ? {this.state.quality}p Quality! : null} {this.state.length !== 0 ? - {Math.round(this.state.length / 60)} Minutes of length!: null} + {Math.round(this.state.length / 60)} Minutes of + length! : null} Tags: {this.state.tags.map((m) => ( @@ -98,12 +103,15 @@ 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/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json()) .then((result) => { this.setState({ @@ -129,13 +137,15 @@ class Player extends React.Component { } - /* Click Listener */ + /** + * click handler for the like btn + */ likebtn() { const updateRequest = new FormData(); updateRequest.append('action', 'addLike'); updateRequest.append('movieid', this.props.movie_id); - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { if (result.result === "success") { @@ -147,6 +157,10 @@ class Player extends React.Component { })); } + /** + * closebtn click handler + * calls callback to viewbinding to show previous page agains + */ closebtn() { this.props.viewbinding.returnToLastElement(); } diff --git a/src/pages/Player/Player.module.css b/src/pages/Player/Player.module.css index 115329b..6a21fec 100644 --- a/src/pages/Player/Player.module.css +++ b/src/pages/Player/Player.module.css @@ -13,8 +13,8 @@ float: left; margin-left: 20px; margin-top: 25px; - width: 60%; margin-top: 20px; + width: 60%; } .videoactions { diff --git a/src/pages/RandomPage/RandomPage.js b/src/pages/RandomPage/RandomPage.js index 085633a..e360889 100644 --- a/src/pages/RandomPage/RandomPage.js +++ b/src/pages/RandomPage/RandomPage.js @@ -5,6 +5,9 @@ import Tag from "../../elements/Tag/Tag"; import PageTitle from "../../elements/PageTitle/PageTitle"; import VideoContainer from "../../elements/VideoContainer/VideoContainer"; +/** + * Randompage shuffles random viedeopreviews and provides a shuffle btn + */ class RandomPage extends React.Component { constructor(props, context) { super(props, context); @@ -50,17 +53,24 @@ class RandomPage extends React.Component { ); } + /** + * click handler for shuffle btn + */ shuffleclick() { this.loadShuffledvideos(4); } + /** + * load random videos from backend + * @param nr number of videos to load + */ loadShuffledvideos(nr) { const updateRequest = new FormData(); updateRequest.append('action', 'getRandomMovies'); updateRequest.append('number', nr); // fetch all videos available - fetch('/api/videoload.php', {method: 'POST', body: updateRequest}) + fetch('/api/video.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { console.log(result); diff --git a/src/pages/SettingsPage/GeneralSettings.js b/src/pages/SettingsPage/GeneralSettings.js index 39f5a26..d2443de 100644 --- a/src/pages/SettingsPage/GeneralSettings.js +++ b/src/pages/SettingsPage/GeneralSettings.js @@ -3,6 +3,10 @@ import {Button, Col, Form} from "react-bootstrap"; import style from "./GeneralSettings.module.css" import GlobalInfos from "../../GlobalInfos"; +/** + * Component for Generalsettings tag on Settingspage + * handles general settings of mediacenter which concerns to all pages + */ class GeneralSettings extends React.Component { constructor(props) { super(props); @@ -19,22 +23,7 @@ class GeneralSettings extends React.Component { } 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 - }); - })); + this.loadSettings(); } render() { @@ -119,6 +108,31 @@ class GeneralSettings extends React.Component { ); } + /** + * inital load of already specified settings from backend + */ + loadSettings() { + 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 + }); + })); + } + + /** + * save the selected and typed settings to the backend + */ saveSettings() { const updateRequest = new FormData(); updateRequest.append('action', 'saveGeneralSettings'); @@ -130,7 +144,7 @@ class GeneralSettings extends React.Component { updateRequest.append("tmdbsupport", this.state.tmdbsupport); updateRequest.append("darkmodeenabled", GlobalInfos.isDarkTheme()); - fetch('/api/Settings.php', {method: 'POST', body: updateRequest}) + fetch('/api/settings.php', {method: 'POST', body: updateRequest}) .then((response) => response.json() .then((result) => { if (result.success) { diff --git a/src/pages/SettingsPage/MovieSettings.js b/src/pages/SettingsPage/MovieSettings.js index f563ec0..5964252 100644 --- a/src/pages/SettingsPage/MovieSettings.js +++ b/src/pages/SettingsPage/MovieSettings.js @@ -1,6 +1,10 @@ import React from "react"; import style from "./MovieSettings.module.css" +/** + * Component for MovieSettings on Settingspage + * handles settings concerning to movies in general + */ class MovieSettings extends React.Component { constructor(props) { super(props); @@ -36,6 +40,9 @@ class MovieSettings extends React.Component { ); } + /** + * starts the reindex process of the videos in the specified folder + */ startReindex() { // clear output text before start this.setState({text: []}); @@ -60,6 +67,9 @@ class MovieSettings extends React.Component { this.myinterval = setInterval(this.updateStatus, 1000); } + /** + * This interval function reloads the current status of reindexing from backend + */ updateStatus = () => { const updateRequest = new FormData(); fetch('/api/extractionData.php', {method: 'POST', body: updateRequest}) diff --git a/src/pages/SettingsPage/SettingsPage.js b/src/pages/SettingsPage/SettingsPage.js index b663eda..75f404b 100644 --- a/src/pages/SettingsPage/SettingsPage.js +++ b/src/pages/SettingsPage/SettingsPage.js @@ -4,7 +4,10 @@ import GeneralSettings from "./GeneralSettings"; import style from "./SettingsPage.module.css" import GlobalInfos from "../../GlobalInfos"; - +/** + * 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) { super(props, context); @@ -14,6 +17,10 @@ class SettingsPage extends React.Component { }; } + /** + * load the selected tab + * @returns {JSX.Element|string} the jsx element of the selected tab + */ getContent() { switch (this.state.currentpage) { case "general": diff --git a/src/setupTests.js b/src/setupTests.js index 325f27d..95f17f2 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -9,6 +9,11 @@ import Adapter from 'enzyme-adapter-react-16'; configure({adapter: new Adapter()}); +/** + * prepares fetch api for a virtual test call + * @param response the response fetch should give you back + * @returns {jest.Mock} a jest mock function simulating a fetch + */ global.prepareFetchApi = (response) => { const mockJsonPromise = Promise.resolve(response); const mockFetchPromise = Promise.resolve({ @@ -17,6 +22,10 @@ global.prepareFetchApi = (response) => { return (jest.fn().mockImplementation(() => mockFetchPromise)); } +/** + * prepares a failing virtual fetch api call + * @returns {jest.Mock} a jest moch function simulating a failing fetch call + */ global.prepareFailingFetchApi = () => { const mockFetchPromise = Promise.reject("myreason"); return (jest.fn().mockImplementation(() => mockFetchPromise));