Moved old webpage, app & client into .archive folder
This commit is contained in:
8
.archive/client/.babelrc
Normal file
8
.archive/client/.babelrc
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
./.babelrc
|
||||
*/
|
||||
{
|
||||
"presets":[
|
||||
"es2015", "env", "react"
|
||||
]
|
||||
}
|
||||
58
.archive/client/.gitignore
vendored
Normal file
58
.archive/client/.gitignore
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
BIN
.archive/client/app/DIN-Regular-webfont.woff
Normal file
BIN
.archive/client/app/DIN-Regular-webfont.woff
Normal file
Binary file not shown.
25
.archive/client/app/Root.jsx
Normal file
25
.archive/client/app/Root.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
import { HashRouter as Router, Route, Switch, IndexRoute } from 'react-router-dom';
|
||||
|
||||
import SearchRequest from './components/SearchRequest.jsx';
|
||||
import AdminComponent from './components/admin/Admin.jsx';
|
||||
|
||||
class Root extends Component {
|
||||
|
||||
// We need to provide a list of routes
|
||||
// for our app, and in this case we are
|
||||
// doing so from a Root component
|
||||
render() {
|
||||
return (
|
||||
<Router>
|
||||
<Switch>
|
||||
<Route exact path='/' component={SearchRequest} />
|
||||
<Route path='/admin/:request' component={AdminComponent} />
|
||||
<Route path='/admin' component={AdminComponent} />
|
||||
</Switch>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Root;
|
||||
41
.archive/client/app/app.scss
Normal file
41
.archive/client/app/app.scss
Normal file
@@ -0,0 +1,41 @@
|
||||
@font-face {
|
||||
font-family: "din";
|
||||
src: url('/app/DIN-Regular-webfont.woff')
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'din', 'Open Sans', sans-serif;
|
||||
display: inline-block;
|
||||
color:red;
|
||||
}
|
||||
|
||||
#requestMovieList {
|
||||
display: flex;
|
||||
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.movie_wrapper {
|
||||
color:red;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
width: 30%;
|
||||
background-color: #ffffff;
|
||||
height: 231px;
|
||||
|
||||
margin: 20px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.movie_content {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.movie_header {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
26
.archive/client/app/components/Cookie.jsx
Normal file
26
.archive/client/app/components/Cookie.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
||||
export function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(';');
|
||||
for(var i = 0; i <ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function setCookie(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays*24*60*60*1000));
|
||||
var expires = "expires="+ d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
63
.archive/client/app/components/FetchData.js
Normal file
63
.archive/client/app/components/FetchData.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
|
||||
class FetchData extends React.Component {
|
||||
constructor(props){
|
||||
super(props)
|
||||
this.state = {
|
||||
playing: [],
|
||||
hei: '1',
|
||||
intervalId: null,
|
||||
url: ''
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var that = this;
|
||||
fetch("https://apollo.kevinmidboe.com/api/v1/plex/playing").then(
|
||||
function(response){
|
||||
response.json().then(function(data){
|
||||
that.setState({
|
||||
playing: that.state.playing.concat(data.video)
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// use intervalId from the state to clear the interval
|
||||
clearInterval(this.state.intervalId);
|
||||
}
|
||||
|
||||
getPlaying() {
|
||||
if (this.state.playing.length != 0) {
|
||||
return this.state.playing.map((playingObj) => {
|
||||
if (playingObj.type === 'episode') {
|
||||
console.log('episode')
|
||||
return ([
|
||||
<span>{playingObj.title}</span>,
|
||||
<span>{playingObj.season}</span>,
|
||||
<span>{playingObj.episode}</span>
|
||||
])
|
||||
} else if (playingObj.type === 'movie') {
|
||||
console.log('movie')
|
||||
return ([
|
||||
<span>{playingObj.title}</span>
|
||||
])
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return (<span>Nothing playing</span>)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
return(
|
||||
<div className="FetchData">{this.getPlaying()}</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default FetchData;
|
||||
266
.archive/client/app/components/FetchRequested.jsx
Normal file
266
.archive/client/app/components/FetchRequested.jsx
Normal file
@@ -0,0 +1,266 @@
|
||||
import React from 'react';
|
||||
|
||||
import requestElement from './styles/requestElementStyle.jsx'
|
||||
|
||||
import { getCookie } from './Cookie.jsx';
|
||||
|
||||
class DropdownList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
filter: ['all', 'requested', 'downloading', 'downloaded'],
|
||||
sort: ['requested_date', 'name', 'status', 'requested_by', 'ip', 'user_agent'],
|
||||
status: ['requested', 'downloading', 'downloaded'],
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {elementType, elementId, elementStatus, elementCallback, props} = this.props;
|
||||
|
||||
console.log(elementCallback('downloaded'))
|
||||
|
||||
switch (elementType) {
|
||||
case 'status':
|
||||
return (
|
||||
<div>HERE</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestElement extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dropDownState: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
filterRequestList(requestList, filter) {
|
||||
if (filter === 'all')
|
||||
return requestList
|
||||
|
||||
if (filter === 'movie' || filter === 'show')
|
||||
return requestList.filter(item => item.type === filter)
|
||||
return requestList.filter(item => item.status === filter)
|
||||
}
|
||||
|
||||
sortRequestList(requestList, sort_type, reversed) {
|
||||
requestList.sort(function(a,b) {
|
||||
if(a[sort_type] < b[sort_type]) return -1;
|
||||
if(a[sort_type] > b[sort_type]) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (reversed)
|
||||
requestList.reverse();
|
||||
}
|
||||
|
||||
userAgent(agent) {
|
||||
if (agent) {
|
||||
try {
|
||||
return agent.split(" ")[1].replace(/[\(\;]/g, '');
|
||||
}
|
||||
catch(e) {
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
updateDropDownState(status) {
|
||||
if (status !== this.dropDownState) {
|
||||
this.dropDownState = status;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ItemsStatusDropdown(id, type, status) {
|
||||
return (
|
||||
<div>
|
||||
<select id="lang"
|
||||
defaultValue={status}
|
||||
onChange={event => this.updateDropDownState(event.target.value)}
|
||||
>
|
||||
<option value='requested'>Requested</option>
|
||||
<option value='downloading'>Downloading</option>
|
||||
<option value='downloaded'>Downloaded</option>
|
||||
</select>
|
||||
|
||||
<button onClick={() => { this.updateRequestedItem(id, type)}}>Update Status</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
updateRequestedItem(id, type) {
|
||||
console.log(id, type, this.dropDownState);
|
||||
Promise.resolve()
|
||||
fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + id, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
'authorization': getCookie('token')
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: type,
|
||||
status: this.dropDownState,
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status !== 200) {
|
||||
console.log('error');
|
||||
}
|
||||
|
||||
response.json()
|
||||
.then(data => {
|
||||
if (data.success === true) {
|
||||
console.log('UPDATED :', id, ' with ', this.dropDownState)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
new Error(error);
|
||||
})
|
||||
}
|
||||
|
||||
createHTMLElement(data, index) {
|
||||
var posterPath = 'https://image.tmdb.org/t/p/w300' + data.image_path;
|
||||
|
||||
return (
|
||||
<div style={requestElement.wrappingDiv} key={index}>
|
||||
<img style={requestElement.requestPoster} src={posterPath}></img>
|
||||
<div style={requestElement.infoDiv}>
|
||||
<span><b>Name</b>: {data.name} </span><br></br>
|
||||
<span><b>Year</b>: {data.year}</span><br></br>
|
||||
<span><b>Type</b>: {data.type}</span><br></br>
|
||||
<span><b>Status</b>: {data.status}</span><br></br>
|
||||
<span><b>Address</b>: {data.ip}</span><br></br>
|
||||
<span><b>Requested Data:</b> {data.requested_date}</span><br></br>
|
||||
<span><b>Requested By:</b> {data.requested_by}</span><br></br>
|
||||
<span><b>Agent</b>: { this.userAgent(data.user_agent) }</span><br></br>
|
||||
</div>
|
||||
|
||||
{ this.ItemsStatusDropdown(data.id, data.type, data.status) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {requestedElementsList, requestedElementsFilter, requestedElementsSort, props} = this.props;
|
||||
|
||||
var filteredRequestedList = this.filterRequestList(requestedElementsList, requestedElementsFilter)
|
||||
|
||||
this.sortRequestList(filteredRequestedList, requestedElementsSort.value, requestedElementsSort.reversed)
|
||||
|
||||
return (
|
||||
<div {...props} style={requestElement.bodyDiv}>
|
||||
{filteredRequestedList.map((requestItem, index) => this.createHTMLElement(requestItem, index))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FetchRequested extends React.Component {
|
||||
constructor(props){
|
||||
super(props)
|
||||
this.state = {
|
||||
requested_objects: [],
|
||||
filter: 'all',
|
||||
sort: {
|
||||
value: 'requested_date',
|
||||
reversed: false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
Promise.resolve()
|
||||
fetch('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
'authorization': getCookie('token')
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status !== 200) {
|
||||
console.log('error');
|
||||
}
|
||||
|
||||
response.json()
|
||||
.then(data => {
|
||||
if (data.success === true) {
|
||||
this.setState({
|
||||
requested_objects: data.requestedItems
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
new Error(error);
|
||||
})
|
||||
}
|
||||
|
||||
changeFilter(filter) {
|
||||
this.setState({
|
||||
filter: filter
|
||||
})
|
||||
}
|
||||
|
||||
updateSort(sort=null, reverse=false) {
|
||||
if (sort) {
|
||||
this.setState({
|
||||
sort: { value: sort, reversed: reverse }
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
sort: { value: this.state.sort.value, reversed: reverse }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
render(){
|
||||
return(
|
||||
<div>
|
||||
<select id="lang" onChange={event => this.changeFilter(event.target.value)} value={this.state.value}>
|
||||
<option value="all">All</option>
|
||||
<option value="requested">Requested</option>
|
||||
<option value="downloading">Downloading</option>
|
||||
<option value="downloaded">Downloaded</option>
|
||||
<option value='movie'>Movies</option>
|
||||
<option value='show'>Shows</option>
|
||||
</select>
|
||||
|
||||
<select id="lang" onChange={event => this.updateSort(event.target.value)} value={this.state.value}>
|
||||
<option value='requested_date'>Date</option>
|
||||
<option value='name'>Title</option>
|
||||
<option value='status'>Status</option>
|
||||
<option value='requested_by'>Requested By</option>
|
||||
<option value='ip'>Address</option>
|
||||
<option value='user_agent'>Agent</option>
|
||||
</select>
|
||||
|
||||
<button onClick={() => {this.updateSort(null, !this.state.sort.reversed)}}>Reverse</button>
|
||||
|
||||
<RequestElement
|
||||
requestedElementsList={this.state.requested_objects}
|
||||
requestedElementsFilter={this.state.filter}
|
||||
requestedElementsSort={this.state.sort}
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FetchRequested;
|
||||
11
.archive/client/app/components/Header.jsx
Normal file
11
.archive/client/app/components/Header.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
// The Header creates links that can be used to navigate
|
||||
// between routes.
|
||||
const Header = () => (
|
||||
<header>
|
||||
</header>
|
||||
)
|
||||
|
||||
export default Header
|
||||
44
.archive/client/app/components/ListStrays.jsx
Normal file
44
.archive/client/app/components/ListStrays.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
|
||||
class ListStrays extends React.Component {
|
||||
constructor(props){
|
||||
super(props)
|
||||
this.state = {
|
||||
strays: [],
|
||||
hei: '1'
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
var that = this;
|
||||
fetch('https://apollo.kevinmidboe.com/api/v1/seasoned/all').then(
|
||||
function(response){
|
||||
response.json().then(function(data){
|
||||
// console.log(data);
|
||||
that.setState({
|
||||
strays: that.state.strays.concat(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
render(){
|
||||
return(
|
||||
<div className="ListStrays">
|
||||
{this.state.strays.map((strayObj) => {
|
||||
if (strayObj.verified == 0) {
|
||||
var url = "https://kevinmidboe.com/seasoned/verified.html?id=" + strayObj.id;
|
||||
return ([
|
||||
<span key={strayObj.id}>{strayObj.name}</span>,
|
||||
<a href={url}>{strayObj.id}</a>
|
||||
])
|
||||
}
|
||||
})}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ListStrays;
|
||||
10
.archive/client/app/components/NotFound.js
Normal file
10
.archive/client/app/components/NotFound.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// components/NotFound.js
|
||||
import React from 'react';
|
||||
|
||||
const NotFound = () =>
|
||||
<div>
|
||||
<h3>404 page not found</h3>
|
||||
<p>We are sorry but the page you are looking for does not exist.</p>
|
||||
</div>
|
||||
|
||||
export default NotFound;
|
||||
126
.archive/client/app/components/SearchObject.jsx
Normal file
126
.archive/client/app/components/SearchObject.jsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
|
||||
import Notifications, {notify} from 'react-notify-toast';
|
||||
|
||||
// StyleComponents
|
||||
import searchObjectCSS from './styles/searchObject.jsx';
|
||||
import buttonsCSS from './styles/buttons.jsx';
|
||||
import InfoButton from './buttons/InfoButton.jsx';
|
||||
|
||||
var MediaQuery = require('react-responsive');
|
||||
|
||||
import { fetchJSON } from './http.jsx';
|
||||
|
||||
import Interactive from 'react-interactive';
|
||||
|
||||
|
||||
class SearchObject {
|
||||
constructor(object) {
|
||||
this.id = object.id;
|
||||
this.title = object.title;
|
||||
this.year = object.year;
|
||||
this.type = object.type;
|
||||
this.rating = object.rating;
|
||||
this.poster = object.poster_path;
|
||||
this.background = object.background_path;
|
||||
this.matchedInPlex = object.matchedInPlex;
|
||||
this.summary = object.summary;
|
||||
}
|
||||
|
||||
requestExisting(movie) {
|
||||
console.log('Exists', movie);
|
||||
}
|
||||
|
||||
requestMovie() {
|
||||
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + this.id + '?type='+this.type, 'POST')
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
notify.show(this.title + ' requested!', 'success', 3000);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Request movie fetch went wrong: '+ e);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
getElement(index) {
|
||||
const element_key = index + this.id;
|
||||
|
||||
if (this.poster == null || this.poster == undefined) {
|
||||
var posterPath = 'https://openclipart.org/image/2400px/svg_to_png/211479/Simple-Image-Not-Found-Icon.png'
|
||||
} else {
|
||||
var posterPath = 'https://image.tmdb.org/t/p/w185' + this.poster;
|
||||
}
|
||||
var backgroundPath = 'https://image.tmdb.org/t/p/w640_and_h360_bestv2/' + this.background;
|
||||
|
||||
var foundInPlex;
|
||||
if (this.matchedInPlex) {
|
||||
foundInPlex = <Interactive
|
||||
as='button'
|
||||
onClick={() => {this.requestExisting(this)}}
|
||||
style={buttonsCSS.submit}
|
||||
focus={buttonsCSS.submit_hover}
|
||||
hover={buttonsCSS.submit_hover}>
|
||||
|
||||
<span>Request Anyway</span>
|
||||
</Interactive>;
|
||||
} else {
|
||||
foundInPlex = <Interactive
|
||||
as='button'
|
||||
onClick={() => {this.requestMovie()}}
|
||||
style={buttonsCSS.submit}
|
||||
focus={buttonsCSS.submit_hover}
|
||||
hover={buttonsCSS.submit_hover}>
|
||||
|
||||
<span>+ Request</span>
|
||||
</Interactive>;
|
||||
}
|
||||
|
||||
// TODO go away from using mediaQuery, and create custom resizer
|
||||
return (
|
||||
<div key={element_key}>
|
||||
<Notifications />
|
||||
|
||||
<div style={searchObjectCSS.container} key={this.id}>
|
||||
<MediaQuery minWidth={600}>
|
||||
<div style={searchObjectCSS.posterContainer}>
|
||||
<img style={searchObjectCSS.posterImage} id='poster' src={posterPath}></img>
|
||||
</div>
|
||||
<span style={searchObjectCSS.title_large}>{this.title}</span>
|
||||
<br></br>
|
||||
<span style={searchObjectCSS.stats_large}>
|
||||
Released: { this.year } | Rating: {this.rating} | Type: {this.type}
|
||||
</span>
|
||||
<br></br>
|
||||
<span style={searchObjectCSS.summary}>{this.summary}</span>
|
||||
<br></br>
|
||||
</MediaQuery>
|
||||
|
||||
<MediaQuery maxWidth={600}>
|
||||
<img src={ backgroundPath } style={searchObjectCSS.backgroundImage}></img>
|
||||
<span style={searchObjectCSS.title_small}>{this.title}</span>
|
||||
<br></br>
|
||||
<span style={searchObjectCSS.stats_small}>Released: {this.year} | Rating: {this.rating}</span>
|
||||
</MediaQuery>
|
||||
|
||||
<div style={searchObjectCSS.buttons}>
|
||||
{foundInPlex}
|
||||
|
||||
<InfoButton id={this.id} type={this.type} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MediaQuery maxWidth={600}>
|
||||
<br />
|
||||
</MediaQuery>
|
||||
|
||||
<div style={searchObjectCSS.dividerRow}>
|
||||
<div style={searchObjectCSS.itemDivider}></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchObject;
|
||||
464
.archive/client/app/components/SearchRequest.jsx
Normal file
464
.archive/client/app/components/SearchRequest.jsx
Normal file
@@ -0,0 +1,464 @@
|
||||
import React from 'react';
|
||||
|
||||
import URI from 'urijs';
|
||||
import InfiniteScroll from 'react-infinite-scroller';
|
||||
|
||||
// StyleComponents
|
||||
import searchRequestCSS from './styles/searchRequestStyle.jsx';
|
||||
|
||||
import SearchObject from './SearchObject.jsx';
|
||||
import Loading from './images/loading.jsx'
|
||||
|
||||
import { fetchJSON } from './http.jsx';
|
||||
import { getCookie } from './Cookie.jsx';
|
||||
|
||||
var MediaQuery = require('react-responsive');
|
||||
|
||||
// TODO add option for searching multi, movies or tv shows
|
||||
class SearchRequest extends React.Component {
|
||||
constructor(props){
|
||||
super(props)
|
||||
// Constructor with states holding the search query and the element of reponse.
|
||||
this.state = {
|
||||
lastApiCallURI: '',
|
||||
searchQuery: '',
|
||||
responseMovieList: null,
|
||||
movieFilter: false,
|
||||
showFilter: false,
|
||||
discoverType: '',
|
||||
page: 1,
|
||||
resultHeader: '',
|
||||
loadResults: false,
|
||||
scrollHasMore: true,
|
||||
loading: false,
|
||||
}
|
||||
|
||||
this.allowedListTypes = ['discover', 'popular', 'nowplaying', 'upcoming']
|
||||
|
||||
this.baseUrl = 'https://apollo.kevinmidboe.com/api/v1/tmdb/list';
|
||||
// this.baseUrl = 'http://localhost:31459/api/v1/tmdb/list';
|
||||
this.searchUrl = 'https://apollo.kevinmidboe.com/api/v1/plex/request';
|
||||
// this.searchUrl = 'http://localhost:31459/api/v1/plex/request';
|
||||
}
|
||||
|
||||
|
||||
componentWillMount(){
|
||||
var that = this;
|
||||
// this.setState({responseMovieList: null})
|
||||
this.resetPageNumber();
|
||||
this.state.loadResults = true;
|
||||
this.fetchTmdbList(this.allowedListTypes[Math.floor(Math.random()*this.allowedListTypes.length)]);
|
||||
}
|
||||
|
||||
// Handles all errors of the response of a fetch call
|
||||
handleErrors(response) {
|
||||
if (!response.ok)
|
||||
throw Error(response.status);
|
||||
return response;
|
||||
}
|
||||
|
||||
handleQueryError(response) {
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
this.setState({
|
||||
responseMovieList: <h1>Nothing found for search query: { this.findQueryInURI(uri) }</h1>
|
||||
})
|
||||
}
|
||||
console.log('handleQueryError: ', error);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// Unpacks the query value of a uri
|
||||
findQueryValueInURI(uri) {
|
||||
let uriSearchValues = uri.query(true);
|
||||
let queryValue = uriSearchValues['query']
|
||||
|
||||
return queryValue;
|
||||
}
|
||||
|
||||
// Unpacks the page value of a uri
|
||||
findPageValueInURI(uri) {
|
||||
let uriSearchValues = uri.query(true);
|
||||
let queryValue = uriSearchValues['page']
|
||||
|
||||
return queryValue;
|
||||
}
|
||||
|
||||
resetPageNumber() {
|
||||
this.state.page = 1;
|
||||
}
|
||||
|
||||
setLoading(value) {
|
||||
this.setState({
|
||||
loading: value
|
||||
});
|
||||
}
|
||||
|
||||
// Test this by calling missing endpoint or 404 query and see what code
|
||||
// and filter the error message based on the code.
|
||||
// Calls a uri and returns the response as json
|
||||
callURI(uri, method, data={}) {
|
||||
return fetch(uri, {
|
||||
method: method,
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'authorization': getCookie('token'),
|
||||
'loggedinuser': getCookie('loggedInUser'),
|
||||
})
|
||||
})
|
||||
.then(response => { return response })
|
||||
.catch((error) => {
|
||||
throw Error(error);
|
||||
});
|
||||
}
|
||||
|
||||
// Saves the input string as a h1 element in responseMovieList state
|
||||
fillResponseMovieListWithError(msg) {
|
||||
this.setState({
|
||||
responseMovieList: <h1>{ msg }</h1>
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Here we first call api for a search with the input uri, handle any errors
|
||||
// and fill the reponseData from api into the state of reponseMovieList as movieObjects
|
||||
callSearchFillMovieList(uri) {
|
||||
Promise.resolve()
|
||||
.then(() => this.callURI(uri, 'GET'))
|
||||
.then(response => {
|
||||
// If we get a error code for the request
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
if (this.findPageValueInURI(new URI(response.url)) > 1) {
|
||||
this.state.scrollHasMore = false;
|
||||
console.log(this.state.scrollHasMore)
|
||||
return null
|
||||
let returnMessage = 'this is the return mesasge than will never be delivered'
|
||||
let theSecondReturnMsg = 'this is the second return messag ethat will NEVE be delivered'
|
||||
}
|
||||
else {
|
||||
|
||||
let errorMsg = 'Nothing found for the search query: ' + this.findQueryValueInURI(uri);
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
}
|
||||
else {
|
||||
let errorMsg = 'Error fetching query from server ' + this.response.status;
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to json and update the state of responseMovieList with the results of the api call
|
||||
// mapped as a SearchObject.
|
||||
response.json()
|
||||
.then(responseData => {
|
||||
if (this.state.page === 1) {
|
||||
this.setState({
|
||||
responseMovieList: responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index)),
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
} else {
|
||||
let responseMovieObjects = responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index));
|
||||
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
|
||||
this.setState({
|
||||
responseMovieList: growingReponseMovieObjectList,
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('CallSearchFillMovieList: ', error)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Something went wrong when fetching query.', error)
|
||||
})
|
||||
}
|
||||
|
||||
callListFillMovieList(uri) {
|
||||
// Write loading animation
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => this.callURI(uri, 'GET', undefined))
|
||||
.then(response => {
|
||||
// If we get a error code for the request
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
let errorMsg = 'List not found';
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
else {
|
||||
let errorMsg = 'Error fetching list from server ' + this.response.status;
|
||||
this.fillResponseMovieListWithError(errorMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to json and update the state of responseMovieList with the results of the api call
|
||||
// mapped as a SearchObject.
|
||||
response.json()
|
||||
.then(responseData => {
|
||||
if (this.state.page === 1) {
|
||||
this.setState({
|
||||
responseMovieList: responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index)),
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
} else {
|
||||
let responseMovieObjects = responseData.results.map((searchResultItem, index) => this.createMovieObjects(searchResultItem, index));
|
||||
let growingReponseMovieObjectList = this.state.responseMovieList.concat(responseMovieObjects);
|
||||
this.setState({
|
||||
responseMovieList: growingReponseMovieObjectList,
|
||||
lastApiCallURI: uri // Save the value of the last sucessfull api call
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Something went wrong when fetching query.', error)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
searchSeasonedRequest() {
|
||||
this.state.resultHeader = 'Search result for: ' + this.state.searchQuery;
|
||||
|
||||
// Build uri with the url for searching requests
|
||||
var uri = new URI(this.searchUrl);
|
||||
// Add input of search query and page count to the uri payload
|
||||
uri = uri.search({ 'query': this.state.searchQuery, 'page': this.state.page });
|
||||
|
||||
if (this.state.showFilter)
|
||||
uri = uri.addSearch('type', 'show');
|
||||
else
|
||||
if (this.state.movieFilter)
|
||||
uri = uri.addSearch('type', 'movie')
|
||||
|
||||
// Send uri to call and fill the response list with movie/show objects
|
||||
this.callSearchFillMovieList(uri);
|
||||
}
|
||||
|
||||
fetchTmdbList(tmdbListType) {
|
||||
console.log(tmdbListType)
|
||||
// Check if it is a whitelisted list, this should be replaced with checking if the return call is 500
|
||||
if (this.allowedListTypes.indexOf(tmdbListType) === -1)
|
||||
throw Error('Invalid discover type: ' + tmdbListType);
|
||||
|
||||
this.state.responseMovieList = []
|
||||
// Captialize the first letter of and save the discoverQueryType to resultHeader state.
|
||||
this.state.resultHeader = tmdbListType.toLowerCase().replace(/\b[a-z]/g, function(letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
|
||||
// Build uri with the url for searching requests
|
||||
var uri = new URI(this.baseUrl);
|
||||
uri.segment(tmdbListType);
|
||||
// Add input of search query and page count to the uri payload
|
||||
uri = uri.search({ 'page': this.state.page });
|
||||
|
||||
if (this.state.showFilter)
|
||||
uri = uri.addSearch('type', 'show');
|
||||
|
||||
// Send uri to call and fill the response list with movie/show objects
|
||||
this.callListFillMovieList(uri);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Updates the internal state of the query search field.
|
||||
updateQueryState(event){
|
||||
this.setState({
|
||||
searchQuery: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
// For checking if the enter key was pressed in the search field.
|
||||
_handleQueryKeyPress(e) {
|
||||
if (e.key === 'Enter') {
|
||||
// this.fetchQuery();
|
||||
// Reset page number for a new search
|
||||
this.resetPageNumber();
|
||||
this.searchSeasonedRequest();
|
||||
}
|
||||
}
|
||||
|
||||
// When called passes the variable to SearchObject and calls it's interal function for
|
||||
// generating the wanted HTML
|
||||
createMovieObjects(item, index) {
|
||||
let movie = new SearchObject(item);
|
||||
return movie.getElement(index);
|
||||
}
|
||||
|
||||
toggleFilter(filterType) {
|
||||
if (filterType == 'movies') {
|
||||
this.setState({
|
||||
movieFilter: !this.state.movieFilter
|
||||
})
|
||||
console.log(this.state.movieFilter);
|
||||
}
|
||||
else if (filterType == 'shows') {
|
||||
this.setState({
|
||||
showFilter: !this.state.showFilter
|
||||
})
|
||||
console.log(this.state.showFilter);
|
||||
}
|
||||
}
|
||||
|
||||
pageBackwards() {
|
||||
if (this.state.page > 1) {
|
||||
let pageNumber = this.state.page - 1;
|
||||
let uri = this.state.lastApiCallURI;
|
||||
|
||||
// Augment the page number of the uri with a callback
|
||||
uri.search(function(data) {
|
||||
data.page = pageNumber;
|
||||
});
|
||||
|
||||
// Call the api with the new uri
|
||||
this.callSearchFillMovieList(uri);
|
||||
// Update state of our page number after the call is done
|
||||
this.state.page = pageNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO need to get total page number and save in a state to not overflow
|
||||
pageForwards() {
|
||||
// Wrap this in the check
|
||||
let pageNumber = this.state.page + 1;
|
||||
let uri = this.state.lastApiCallURI;
|
||||
|
||||
// Augment the page number of the uri with a callback
|
||||
uri.search(function(data) {
|
||||
data.page = pageNumber;
|
||||
});
|
||||
|
||||
// Call the api with the new uri
|
||||
this.callSearchFillMovieList(uri);
|
||||
// Update state of our page number after the call is done
|
||||
this.state.page = pageNumber;
|
||||
}
|
||||
|
||||
movieToggle() {
|
||||
if (this.state.movieFilter)
|
||||
return <span style={searchRequestCSS.searchFilterActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('movies')}}
|
||||
id="category_active">Movies</span>
|
||||
else
|
||||
return <span style={searchRequestCSS.searchFilterNotActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('movies')}}
|
||||
id="category_active">Movies</span>
|
||||
}
|
||||
|
||||
showToggle() {
|
||||
if (this.state.showFilter)
|
||||
return <span style={searchRequestCSS.searchFilterActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('shows')}}
|
||||
id="category_active">TV Shows</span>
|
||||
else
|
||||
return <span style={searchRequestCSS.searchFilterNotActive}
|
||||
className="search_category hvrUnderlineFromCenter"
|
||||
onClick={() => {this.toggleFilter('shows')}}
|
||||
id="category_active">TV Shows</span>
|
||||
}
|
||||
|
||||
|
||||
render(){
|
||||
const loader = <div className="loader">Loading ...<br></br></div>;
|
||||
|
||||
|
||||
return(
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={this.pageForwards.bind(this)}
|
||||
hasMore={this.state.scrollHasMore}
|
||||
loader={<Loading />}
|
||||
initialLoad={this.state.loadResults}>
|
||||
|
||||
<MediaQuery minWidth={600}>
|
||||
<div style={searchRequestCSS.body}>
|
||||
<div className='backgroundHeader' style={searchRequestCSS.backgroundLargeHeader}>
|
||||
<div className='pageTitle' style={searchRequestCSS.pageTitle}>
|
||||
<span style={searchRequestCSS.pageTitleLargeSpan}>Request new content for plex</span>
|
||||
</div>
|
||||
|
||||
<div style={searchRequestCSS.searchLargeContainer}>
|
||||
<span style={searchRequestCSS.searchIcon}><i className="fa fa-search"></i></span>
|
||||
|
||||
<input style={searchRequestCSS.searchLargeBar} type="text" id="search" placeholder="Search for new content..."
|
||||
onKeyPress={(event) => this._handleQueryKeyPress(event)}
|
||||
onChange={event => this.updateQueryState(event)}
|
||||
value={this.state.searchQuery}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='requestMovieList' ref='requestMovieList' style={searchRequestCSS.requestWrapper}>
|
||||
<div style={{marginLeft: '30px'}}>
|
||||
<div style={searchRequestCSS.resultLargeHeader}>{this.state.resultHeader}</div>
|
||||
<span style={{content: '', display: 'block', width: '2em', borderTop: '2px solid #000,'}}></span>
|
||||
|
||||
</div>
|
||||
|
||||
<br></br>
|
||||
|
||||
{this.state.responseMovieList}
|
||||
</div>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
|
||||
<MediaQuery maxWidth={600}>
|
||||
<div style={searchRequestCSS.body}>
|
||||
<div className='backgroundHeader' style={searchRequestCSS.backgroundSmallHeader}>
|
||||
<div className='pageTitle' style={searchRequestCSS.pageTitle}>
|
||||
<span style={searchRequestCSS.pageTitleSmallSpan}>Request new content</span>
|
||||
</div>
|
||||
|
||||
<div className='box' style={searchRequestCSS.box}>
|
||||
<div style={searchRequestCSS.searchSmallContainer}>
|
||||
<span style={searchRequestCSS.searchIcon}><i className="fa fa-search"></i></span>
|
||||
|
||||
<input style={searchRequestCSS.searchSmallBar} type="text" id="search" placeholder="Search for new content..."
|
||||
onKeyPress={(event) => this._handleQueryKeyPress(event)}
|
||||
onChange={event => this.updateQueryState(event)}
|
||||
value={this.state.searchQuery}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='requestMovieList' ref='requestMovieList' style={searchRequestCSS.requestWrapper}>
|
||||
<span style={searchRequestCSS.resultSmallHeader}>{this.state.resultHeader}</span>
|
||||
<br></br><br></br>
|
||||
|
||||
{this.state.responseMovieList}
|
||||
</div>
|
||||
</div>
|
||||
</MediaQuery>
|
||||
</InfiniteScroll>
|
||||
)
|
||||
}
|
||||
|
||||
// <form style={searchRequestCSS.controls}>
|
||||
// <label style={searchRequestCSS.withData}>
|
||||
// <div style={searchRequestCSS.sortOptions}>Discover</div>
|
||||
// </label>
|
||||
// </form>
|
||||
|
||||
// <form style={searchRequestCSS.controls}>
|
||||
// <label style={searchRequestCSS.withData}>
|
||||
// <select style={searchRequestCSS.sortOptions}>
|
||||
// <option value="discover">All</option>
|
||||
// <option value="nowplaying">Movies</option>
|
||||
// <option value="nowplaying">TV Shows</option>
|
||||
// </select>
|
||||
// </label>
|
||||
// </form>
|
||||
}
|
||||
|
||||
export default SearchRequest;
|
||||
92
.archive/client/app/components/admin/Admin.jsx
Normal file
92
.archive/client/app/components/admin/Admin.jsx
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import LoginForm from './LoginForm/LoginForm.jsx';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from '../redux/store.jsx';
|
||||
|
||||
import { getCookie } from '../Cookie.jsx';
|
||||
import { fetchJSON } from '../http.jsx';
|
||||
|
||||
import Sidebar from './Sidebar.jsx';
|
||||
import AdminRequestInfo from './AdminRequestInfo.jsx';
|
||||
|
||||
import adminCSS from '../styles/adminComponent.jsx'
|
||||
|
||||
|
||||
class AdminComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
requested_objects: '',
|
||||
}
|
||||
|
||||
this.updateHandler = this.updateHandler.bind(this)
|
||||
}
|
||||
|
||||
// Fetches all requested elements and updates the state with response
|
||||
componentWillMount() {
|
||||
this.fetchRequestedItems()
|
||||
}
|
||||
|
||||
fetchRequestedItems() {
|
||||
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', 'GET')
|
||||
.then(result => {
|
||||
this.setState({
|
||||
requested_objects: result.results.reverse()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updateHandler() {
|
||||
this.fetchRequestedItems()
|
||||
}
|
||||
|
||||
// Displays loginform if not logged in and passes response from
|
||||
// api call to sidebar and infoPanel through props
|
||||
verifyLoggedIn() {
|
||||
const logged_in = getCookie('logged_in');
|
||||
if (!logged_in) {
|
||||
return <LoginForm />
|
||||
}
|
||||
|
||||
let selectedRequest = undefined;
|
||||
let listItemSelected = undefined;
|
||||
|
||||
const requestParam = this.props.match.params.request;
|
||||
|
||||
if (requestParam && this.state.requested_objects !== '') {
|
||||
selectedRequest = this.state.requested_objects[requestParam]
|
||||
listItemSelected = requestParam;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={adminCSS.selectedObjectPanel}>
|
||||
<AdminRequestInfo
|
||||
selectedRequest={selectedRequest}
|
||||
listItemSelected={listItemSelected}
|
||||
updateHandler = {this.updateHandler}
|
||||
/>
|
||||
</div>
|
||||
<div style={adminCSS.sidebar}>
|
||||
<Sidebar
|
||||
requested_objects={this.state.requested_objects}
|
||||
listItemSelected={listItemSelected}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
{ this.verifyLoggedIn() }
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AdminComponent;
|
||||
218
.archive/client/app/components/admin/AdminRequestInfo.jsx
Normal file
218
.archive/client/app/components/admin/AdminRequestInfo.jsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { fetchJSON } from '../http.jsx';
|
||||
|
||||
import PirateSearch from './PirateSearch.jsx'
|
||||
// No in use!
|
||||
import InfoButton from '../buttons/InfoButton.jsx';
|
||||
|
||||
// Stylesheets
|
||||
import requestInfoCSS from '../styles/adminRequestInfo.jsx'
|
||||
import buttonsCSS from '../styles/buttons.jsx';
|
||||
|
||||
|
||||
String.prototype.capitalize = function() {
|
||||
return this.charAt(0).toUpperCase() + this.slice(1);
|
||||
}
|
||||
|
||||
|
||||
class AdminRequestInfo extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
statusValue: '',
|
||||
movieInfo: undefined,
|
||||
expandedSummary: false,
|
||||
}
|
||||
|
||||
this.requestInfo = '';
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
this.requestInfo = props.selectedRequest;
|
||||
this.state.statusValue = this.requestInfo.status;
|
||||
this.state.expandedSummary = false;
|
||||
this.fetchIteminfo()
|
||||
}
|
||||
|
||||
userAgent(agent) {
|
||||
if (agent) {
|
||||
try {
|
||||
return agent.split(" ")[1].replace(/[\(\;]/g, '');
|
||||
}
|
||||
catch(e) {
|
||||
return agent;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
generateStatusDropdown() {
|
||||
return (
|
||||
<select onChange={ event => this.updateRequestStatus(event) } value={this.state.statusValue}>
|
||||
<option value='requested'>Requested</option>
|
||||
<option value='downloading'>Downloading</option>
|
||||
<option value='downloaded'>Downloaded</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
||||
updateRequestStatus(event) {
|
||||
const eventValue = event.target.value;
|
||||
const itemID = this.requestInfo.id;
|
||||
|
||||
const apiData = {
|
||||
type: this.requestInfo.type,
|
||||
status: eventValue,
|
||||
}
|
||||
|
||||
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/request/' + itemID, 'PUT', apiData)
|
||||
.then((response) => {
|
||||
console.log('Response, updateRequestStatus: ', response)
|
||||
this.props.updateHandler()
|
||||
})
|
||||
}
|
||||
|
||||
generateStatusIndicator(status) {
|
||||
switch (status) {
|
||||
case 'requested':
|
||||
// Yellow
|
||||
return 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 10px, #fff 4px, #fff 100%) no-repeat'
|
||||
case 'downloading':
|
||||
// Blue
|
||||
return 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 10px, #fff 4px, #fff 100%) no-repeat'
|
||||
case 'downloaded':
|
||||
// Green
|
||||
return 'linear-gradient(to right, #39aa56 0, #39aa56 10px, #fff 4px, #fff 100%) no-repeat'
|
||||
default:
|
||||
return 'linear-gradient(to right, grey 0, grey 10px, #fff 4px, #fff 100%) no-repeat'
|
||||
}
|
||||
}
|
||||
|
||||
generateTypeIcon(type) {
|
||||
if (type === 'show')
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="7" width="20" height="15" rx="2" ry="2"></rect><polyline points="17 2 12 7 7 2"></polyline></svg>
|
||||
)
|
||||
else if (type === 'movie')
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect><line x1="7" y1="2" x2="7" y2="22"></line><line x1="17" y1="2" x2="17" y2="22"></line><line x1="2" y1="12" x2="22" y2="12"></line><line x1="2" y1="7" x2="7" y2="7"></line><line x1="2" y1="17" x2="7" y2="17"></line><line x1="17" y1="17" x2="22" y2="17"></line><line x1="17" y1="7" x2="22" y2="7"></line></svg>
|
||||
)
|
||||
else
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12" y2="16"></line></svg>
|
||||
)
|
||||
}
|
||||
|
||||
toggleSummmaryLength() {
|
||||
this.setState({
|
||||
expandedSummary: !this.state.expandedSummary
|
||||
})
|
||||
}
|
||||
|
||||
generateSummary() {
|
||||
// { this.state.movieInfo != undefined ? this.state.movieInfo.summary : 'Loading...' }
|
||||
const info = this.state.movieInfo;
|
||||
if (info !== undefined) {
|
||||
const summary = this.state.movieInfo.summary
|
||||
const summary_short = summary.slice(0, 180);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span><b>Matched: </b> {String(info.matchedInPlex)}</span> <br/>
|
||||
<span><b>Rating: </b> {info.rating}</span> <br/>
|
||||
<span><b>Popularity: </b> {info.popularity}</span> <br/>
|
||||
{
|
||||
(summary.length > 180 && this.state.expandedSummary === false) ?
|
||||
<span><b>Summary: </b> { summary_short }<span onClick = {() => this.toggleSummmaryLength()}>... <span style={{color: 'blue', cursor: 'pointer'}}>Show more</span></span></span>
|
||||
:
|
||||
<span><b>Summary: </b> { summary }<span onClick = {() => this.toggleSummmaryLength()}><span style={{color: 'blue', cursor: 'pointer'}}> Show less</span></span></span>
|
||||
|
||||
}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return <span>Loading...</span>
|
||||
}
|
||||
}
|
||||
|
||||
requested_by_user(request_user) {
|
||||
if (request_user === 'NULL')
|
||||
return undefined
|
||||
|
||||
return (
|
||||
<span><b>Requested by:</b> {request_user}</span>
|
||||
)
|
||||
}
|
||||
|
||||
fetchIteminfo() {
|
||||
const itemID = this.requestInfo.id;
|
||||
const type = this.requestInfo.type;
|
||||
|
||||
fetchJSON('https://apollo.kevinmidboe.com/api/v1/tmdb/' + itemID +'&type='+type, 'GET')
|
||||
.then((response) => {
|
||||
console.log('Response, getInfo:', response)
|
||||
this.setState({
|
||||
movieInfo: response
|
||||
});
|
||||
console.log(this.state.movieInfo)
|
||||
})
|
||||
}
|
||||
|
||||
displayInfo() {
|
||||
const request = this.props.selectedRequest;
|
||||
|
||||
if (request) {
|
||||
requestInfoCSS.info.background = this.generateStatusIndicator(request.status);
|
||||
|
||||
return (
|
||||
<div style={requestInfoCSS.wrapper}>
|
||||
|
||||
<div style={requestInfoCSS.stick}>
|
||||
<span style={requestInfoCSS.title}> {request.title} {request.year}</span>
|
||||
<span style={{marginLeft: '2em'}}>
|
||||
<span style={requestInfoCSS.type_icon}>{this.generateTypeIcon(request.type)}</span>
|
||||
{/*<span style={style.type_text}>{request.type.capitalize()}</span> <br />*/}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={requestInfoCSS.info}>
|
||||
<div style={requestInfoCSS.info_poster}>
|
||||
<img src={'https://image.tmdb.org/t/p/w185' + request.poster_path} style={requestInfoCSS.image} alt='Movie poster image'></img>
|
||||
</div>
|
||||
|
||||
<div style={requestInfoCSS.info_request}>
|
||||
<h3 style={requestInfoCSS.info_request_header}>Request info</h3>
|
||||
|
||||
<span><b>status:</b>{ request.status }</span><br />
|
||||
<span><b>ip:</b>{ request.ip }</span><br />
|
||||
<span><b>user_agent:</b>{ this.userAgent(request.user_agent) }</span><br />
|
||||
<span><b>request_date:</b>{ request.requested_date}</span><br />
|
||||
{ this.requested_by_user(request.requested_by) }<br />
|
||||
{ this.generateStatusDropdown() }<br />
|
||||
</div>
|
||||
|
||||
<div style={requestInfoCSS.info_movie}>
|
||||
<h3 style={requestInfoCSS.info_movie_header}>Movie info</h3>
|
||||
|
||||
{ this.generateSummary() }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PirateSearch style={requestInfoCSS.search} name={request.title} />
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>{this.displayInfo()}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminRequestInfo;
|
||||
66
.archive/client/app/components/admin/LoginForm/LoginForm.jsx
Normal file
66
.archive/client/app/components/admin/LoginForm/LoginForm.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { login } from '../../redux/reducer.jsx';
|
||||
|
||||
class LoginForm extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
let {email, password} = this.state;
|
||||
let {isLoginPending, isLoginSuccess, loginError} = this.props;
|
||||
return (
|
||||
<form name="loginForm" onSubmit={this.onSubmit}>
|
||||
<div className="form-group-collection">
|
||||
<div className="form-group">
|
||||
<label>Email:</label>
|
||||
<input type="" name="email" onChange={e => this.setState({email: e.target.value})} value={email}/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Password:</label>
|
||||
<input type="password" name="password" onChange={e => this.setState({password: e.target.value})} value={password}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Login" />
|
||||
|
||||
<div className="message">
|
||||
{ isLoginPending && <div>Please wait...</div> }
|
||||
{ isLoginSuccess && <div>Success.</div> }
|
||||
{ loginError && <div>{loginError.message}</div> }
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
onSubmit(e) {
|
||||
e.preventDefault();
|
||||
let { email, password } = this.state;
|
||||
this.props.login(email, password);
|
||||
this.setState({
|
||||
email: '',
|
||||
password: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
isLoginPending: state.isLoginPending,
|
||||
isLoginSuccess: state.isLoginSuccess,
|
||||
loginError: state.loginError
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
login: (email, password) => dispatch(login(email, password))
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
|
||||
95
.archive/client/app/components/admin/PirateSearch.jsx
Normal file
95
.archive/client/app/components/admin/PirateSearch.jsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { Component } from 'react';
|
||||
import { fetchJSON } from '../http.jsx';
|
||||
|
||||
// Components
|
||||
import TorrentTable from './TorrentTable.jsx'
|
||||
|
||||
// Stylesheets
|
||||
import btnStylesheet from '../styles/buttons.jsx';
|
||||
|
||||
// Interactive button
|
||||
import Interactive from 'react-interactive';
|
||||
|
||||
import Loading from '../images/loading.jsx'
|
||||
|
||||
class PirateSearch extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
torrentResponse: undefined,
|
||||
name: '',
|
||||
loading: null,
|
||||
showButton: true,
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (props.name != this.state.name) {
|
||||
this.setState({
|
||||
torrentResponse: undefined,
|
||||
showButton: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
searchTheBay() {
|
||||
const query = this.props.name;
|
||||
const type = this.props.type;
|
||||
|
||||
this.setState({
|
||||
showButton: false,
|
||||
loading: <Loading />,
|
||||
})
|
||||
|
||||
fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/search?query='+query+'&type='+type, 'GET')
|
||||
// fetchJSON('http://localhost:31459/api/v1/pirate/search?query='+query+'&type='+type, 'GET')
|
||||
.then((response) => {
|
||||
console.log('this is the first response: ', response)
|
||||
if (response.success === true) {
|
||||
this.setState({
|
||||
torrentResponse: response.torrents,
|
||||
loading: null,
|
||||
})
|
||||
}
|
||||
else {
|
||||
console.error(response.message)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
showButton: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
btnStylesheet.submit.top = '50%'
|
||||
btnStylesheet.submit.position = 'absolute'
|
||||
btnStylesheet.submit.marginLeft = '-75px'
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ this.state.showButton ?
|
||||
<div style={{textAlign:'center'}}>
|
||||
<Interactive
|
||||
as='button'
|
||||
onClick={() => {this.searchTheBay()}}
|
||||
style={btnStylesheet.submit}
|
||||
focus={btnStylesheet.submit_hover}
|
||||
hover={btnStylesheet.submit_hover}>
|
||||
|
||||
<span style={{whiteSpace: 'nowrap'}}>Search for torrents</span>
|
||||
</Interactive>
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{ this.state.loading }
|
||||
|
||||
<TorrentTable response={this.state.torrentResponse} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default PirateSearch
|
||||
247
.archive/client/app/components/admin/Sidebar.jsx
Normal file
247
.archive/client/app/components/admin/Sidebar.jsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Interactive from 'react-interactive';
|
||||
|
||||
import sidebarCSS from '../styles/adminSidebar.jsx'
|
||||
|
||||
class SidebarComponent extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props)
|
||||
// Constructor with states holding the search query and the element of reponse.
|
||||
this.state = {
|
||||
filterValue: '',
|
||||
filterQuery: '',
|
||||
requestItemsToBeDisplayed: [],
|
||||
listItemSelected: '',
|
||||
height: '0',
|
||||
}
|
||||
|
||||
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
|
||||
}
|
||||
|
||||
// Where we wait for api response to be delivered from parent through props
|
||||
componentWillReceiveProps(props) {
|
||||
this.state.listItemSelected = props.listItemSelected;
|
||||
this.displayRequestedElementsInfo(props.requested_objects);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateWindowDimensions();
|
||||
window.addEventListener('resize', this.updateWindowDimensions);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.updateWindowDimensions);
|
||||
}
|
||||
|
||||
updateWindowDimensions() {
|
||||
this.setState({ height: window.innerHeight });
|
||||
}
|
||||
|
||||
// Inputs a date and returns a text string that matches how long it was since
|
||||
convertDateToDaysSince(date) {
|
||||
var oneDay = 24*60*60*1000;
|
||||
var firstDate = new Date(date);
|
||||
var secondDate = new Date();
|
||||
|
||||
var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay));
|
||||
|
||||
switch (diffDays) {
|
||||
case 0:
|
||||
return 'Today';
|
||||
case 1:
|
||||
return '1 day ago'
|
||||
default:
|
||||
return diffDays + ' days ago'
|
||||
}
|
||||
}
|
||||
|
||||
// Called from our dropdown, receives a filter string and checks it with status field
|
||||
// of our request objects.
|
||||
filterItems(filterValue) {
|
||||
let filteredRequestElements = this.props.requested_objects.map((item, index) => {
|
||||
if (item.status === filterValue || filterValue === 'all')
|
||||
return this.generateListElements(index, item);
|
||||
})
|
||||
|
||||
this.setState({
|
||||
requestItemsToBeDisplayed: filteredRequestElements,
|
||||
filterValue: filterValue,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Updates the internal state of the query filter and updates the list to only
|
||||
// display names matching the query. This is real-time filtering.
|
||||
updateFilterQuery(event) {
|
||||
const query = event.target.value;
|
||||
|
||||
let filteredByQuery = this.props.requested_objects.map((item, index) => {
|
||||
if (item.title.toLowerCase().indexOf(query.toLowerCase()) != -1)
|
||||
return this.generateListElements(index, item);
|
||||
})
|
||||
|
||||
this.setState({
|
||||
requestItemsToBeDisplayed: filteredByQuery,
|
||||
filterQuery: query,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
generateFilterSearch() {
|
||||
return (
|
||||
<div style={sidebarCSS.searchSidebar}>
|
||||
<div style={sidebarCSS.searchInner}>
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
style={sidebarCSS.searchTextField}
|
||||
placeholder="Search requested items"
|
||||
onChange={event => this.updateFilterQuery(event)}
|
||||
value={this.state.filterQuery}/>
|
||||
<span>
|
||||
<svg id="icon-search" style={sidebarCSS.searchIcon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15">
|
||||
<g id="search">
|
||||
<circle style={sidebarCSS.searchSVGIcon} cx="6.055" cy="5.805" r="5.305"></circle>
|
||||
<path style={sidebarCSS.searchSVGIcon} d="M9.847 9.727l4.166 4.773"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
generateNav() {
|
||||
let filterValue = this.state.filterValue;
|
||||
|
||||
return (
|
||||
<nav style={sidebarCSS.sidebar_navbar_underline}>
|
||||
<ul style={sidebarCSS.ulFilterSelectors}>
|
||||
<li>
|
||||
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('all') }>All</span>
|
||||
{ (filterValue === 'all' || filterValue === '') && <span style={sidebarCSS.spanFilterSelectors}></span> }
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('requested') }>Requested</span>
|
||||
{ filterValue === 'requested' && <span style={sidebarCSS.spanFilterSelectors}></span> }
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('downloading') }>Downloading</span>
|
||||
{ filterValue === 'downloading' && <span style={sidebarCSS.spanFilterSelectors}></span> }
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('downloaded') }>Downloaded</span>
|
||||
{ filterValue === 'downloaded' && <span style={sidebarCSS.spanFilterSelectors}></span> }
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
generateBody(cards) {
|
||||
let style = sidebarCSS.ulCard;
|
||||
style.maxHeight = this.state.height - 160;
|
||||
|
||||
return (
|
||||
<ul style={style}>
|
||||
{ cards }
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
generateListElements(index, item) {
|
||||
let statusBar;
|
||||
|
||||
switch (item.status) {
|
||||
case 'requested':
|
||||
// Yellow
|
||||
statusBar = { background: 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 4px, #fff 4px, #fff 100%) no-repeat' }
|
||||
break;
|
||||
case 'downloading':
|
||||
// Blue
|
||||
statusBar = { background: 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 4px, #fff 4px, #fff 100%) no-repeat' }
|
||||
break;
|
||||
case 'downloaded':
|
||||
// Green
|
||||
statusBar = { background: 'linear-gradient(to right, #39aa56 0, #39aa56 4px, #fff 4px, #fff 100%) no-repeat' }
|
||||
break;
|
||||
default:
|
||||
statusBar = { background: 'linear-gradient(to right, grey 0, grey 4px, #fff 4px, #fff 100%) no-repeat' }
|
||||
}
|
||||
|
||||
statusBar.listStyleType = 'none';
|
||||
|
||||
return (
|
||||
<Link style={sidebarCSS.link} to={{ pathname: '/admin/'+String(index)}} key={index}>
|
||||
<li style={statusBar}>
|
||||
<Interactive
|
||||
as='div'
|
||||
style={ (index != this.state.listItemSelected) ? sidebarCSS.card : sidebarCSS.cardSelected }
|
||||
hover={sidebarCSS.cardSelected}
|
||||
focus={sidebarCSS.cardSelected}
|
||||
active={sidebarCSS.cardSelected}>
|
||||
|
||||
<h2 style={sidebarCSS.titleCard}>
|
||||
<span>{ item.title }</span>
|
||||
</h2>
|
||||
|
||||
<p style={sidebarCSS.pCard}>
|
||||
<span>Requested:
|
||||
<time>
|
||||
{ this.convertDateToDaysSince(item.requested_date) }
|
||||
</time>
|
||||
</span>
|
||||
</p>
|
||||
</Interactive>
|
||||
</li>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
// This is our main loader that gets called when we receive api response through props from parent
|
||||
displayRequestedElementsInfo(requested_objects) {
|
||||
let requestedElement = requested_objects.map((item, index) => {
|
||||
if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) {
|
||||
if (item.status === this.state.filterValue){
|
||||
return this.generateListElements(index, item);
|
||||
}
|
||||
}
|
||||
else if (this.state.filterQuery !== '') {
|
||||
if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1)
|
||||
return this.generateListElements(index, item);
|
||||
}
|
||||
else
|
||||
return this.generateListElements(index, item);
|
||||
})
|
||||
|
||||
this.setState({
|
||||
requestItemsToBeDisplayed: this.generateBody(requestedElement)
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
// if (typeof InstallTrigger !== 'undefined')
|
||||
// bodyCSS.width = '-moz-min-content';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={sidebarCSS.header}>Requested items</h1>
|
||||
{ this.generateFilterSearch() }
|
||||
{ this.generateNav() }
|
||||
|
||||
<div key='requestedTable' style={sidebarCSS.body}>
|
||||
{ this.state.requestItemsToBeDisplayed }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SidebarComponent;
|
||||
209
.archive/client/app/components/admin/TorrentTable.jsx
Normal file
209
.archive/client/app/components/admin/TorrentTable.jsx
Normal file
@@ -0,0 +1,209 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { fetchJSON } from '../http.jsx';
|
||||
|
||||
import torrentTableCSS from '../styles/adminTorrentTable.jsx';
|
||||
|
||||
class TorrentTable extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
torrentResponse: [],
|
||||
listElements: undefined,
|
||||
showTable: false,
|
||||
filterQuery: '',
|
||||
sortValue: 'name',
|
||||
sortDesc: true,
|
||||
}
|
||||
|
||||
this.UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (props.response !== undefined && props.response !== this.state.torrentResponse) {
|
||||
console.log('not called', props)
|
||||
this.setState({
|
||||
torrentResponse: props.response,
|
||||
showTable: true,
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
showTable: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// BORROWED FROM GITHUB user sindresorhus
|
||||
// Link to repo: https://github.com/sindresorhus/pretty-bytes
|
||||
convertSizeToHumanSize(num) {
|
||||
if (!Number.isFinite(num)) {
|
||||
return num
|
||||
// throw new TypeError(`Expected a finite number, got ${typeof num}: ${num}`);
|
||||
}
|
||||
const neg = num < 0;
|
||||
|
||||
if (neg) {
|
||||
num = -num;
|
||||
}
|
||||
|
||||
if (num < 1) {
|
||||
return (neg ? '-' : '') + num + ' B';
|
||||
}
|
||||
|
||||
const exponent = Math.min(Math.floor(Math.log10(num) / 3), this.UNITS.length - 1);
|
||||
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
|
||||
const unit = this.UNITS[exponent];
|
||||
|
||||
return (neg ? '-' : '') + numStr + ' ' + unit;
|
||||
}
|
||||
|
||||
convertHumanSizeToBytes(string) {
|
||||
const [numStr, unit] = string.split(' ');
|
||||
if (this.UNITS.indexOf(unit) === -1) {
|
||||
return string
|
||||
}
|
||||
|
||||
const exponent = this.UNITS.indexOf(unit) * 3
|
||||
|
||||
return numStr * (Math.pow(10, exponent))
|
||||
}
|
||||
|
||||
sendToDownload(magnet) {
|
||||
const apiData = {
|
||||
magnet: magnet,
|
||||
}
|
||||
|
||||
fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', apiData)
|
||||
// fetchJSON('http://localhost:31459/api/v1/pirate/add', 'POST', apiData)
|
||||
.then((response) => {
|
||||
console.log('Response, addMagnet: ', response)
|
||||
// TODO Display the feedback in a notification component (text, status)
|
||||
})
|
||||
}
|
||||
|
||||
// Updates the internal state of the query filter and updates the list to only
|
||||
// display names matching the query. This is real-time filtering.
|
||||
updateFilterQuery(event) {
|
||||
const query = event.target.value;
|
||||
|
||||
let filteredByQuery = this.props.response.map((item, index) => {
|
||||
if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1)
|
||||
return item
|
||||
})
|
||||
|
||||
this.setState({
|
||||
torrentResponse: filteredByQuery,
|
||||
filterQuery: query,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
sortTable(col) {
|
||||
let direction = this.state.sortDesc;
|
||||
if (col === this.state.sortValue)
|
||||
direction = !direction;
|
||||
else
|
||||
direction = true
|
||||
|
||||
let sortedItems = this.state.torrentResponse.sort((a,b) => {
|
||||
// This is so we also can sort string that only contain numbers
|
||||
let valueA = isNaN(a[col]) ? a[col] : parseInt(a[col])
|
||||
let valueB = isNaN(b[col]) ? b[col] : parseInt(b[col])
|
||||
|
||||
valueA = (col == 'size') ? this.convertHumanSizeToBytes(valueA) : valueA
|
||||
valueB = (col == 'size') ? this.convertHumanSizeToBytes(valueB) : valueB
|
||||
|
||||
if (direction)
|
||||
return valueA<valueB? 1:valueA>valueB?-1:0;
|
||||
else
|
||||
return valueA>valueB? 1:valueA<valueB?-1:0;
|
||||
})
|
||||
|
||||
this.setState({
|
||||
torrentResponse: sortedItems,
|
||||
sortDesc: direction,
|
||||
sortValue: col,
|
||||
})
|
||||
}
|
||||
|
||||
generateFilterSearch() {
|
||||
return (
|
||||
<div style={torrentTableCSS.searchSidebar}>
|
||||
<div style={torrentTableCSS.searchInner}>
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
style={torrentTableCSS.searchTextField}
|
||||
placeholder="Filter torrents by query"
|
||||
onChange={event => this.updateFilterQuery(event)}
|
||||
value={this.state.filterQuery}/>
|
||||
<span>
|
||||
<svg id="icon-search" style={torrentTableCSS.searchIcon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15">
|
||||
<g id="search">
|
||||
<circle style={torrentTableCSS.searchSVGIcon} cx="6.055" cy="5.805" r="5.305"></circle>
|
||||
<path style={torrentTableCSS.searchSVGIcon} d="M9.847 9.727l4.166 4.773"></path>
|
||||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
generateListElements() {
|
||||
let listElements = this.state.torrentResponse.map((item, index) => {
|
||||
if (item !== undefined) {
|
||||
let title = item.name
|
||||
let size = this.convertSizeToHumanSize(item.size)
|
||||
|
||||
return (
|
||||
<tr key={index} style={torrentTableCSS.bodyCol}>
|
||||
<td>{ item.name }</td>
|
||||
<td>{ item.uploader }</td>
|
||||
<td>{ size }</td>
|
||||
<td>{ item.seed }</td>
|
||||
<td><button onClick = { event => this.sendToDownload(item.magnet) }>Send to download</button></td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
})
|
||||
return listElements
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style= { this.state.showTable ? null : {display: 'none'}}>
|
||||
{ this.generateFilterSearch() }
|
||||
<table style={torrentTableCSS.table} cellSpacing="0" cellPadding="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('name') }>
|
||||
Title
|
||||
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'name' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
|
||||
</th>
|
||||
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('uploader') }>
|
||||
Uploader
|
||||
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'uploader' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
|
||||
</th>
|
||||
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('size') }>
|
||||
Size
|
||||
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'size' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
|
||||
</th>
|
||||
<th style={torrentTableCSS.col} onClick = {event => this.sortTable('seed') }>
|
||||
Seeds
|
||||
<svg style={ ( this.state.sortDesc && this.state.sortValue == 'seed' ) ? null : {transform: 'rotate(180deg)'} } height="15" viewBox="0 3.5 10 13" version="1.1" width="25" aria-hidden="true"><path fillRule="evenodd" d="M10 10l-1.5 1.5L5 7.75 1.5 11.5 0 10l5-5z"></path></svg>
|
||||
</th>
|
||||
<th style={torrentTableCSS.col}>Magnet</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.generateListElements()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TorrentTable;
|
||||
52
.archive/client/app/components/buttons/InfoButton.jsx
Normal file
52
.archive/client/app/components/buttons/InfoButton.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { Component } from 'react';
|
||||
import Interactive from 'react-interactive';
|
||||
|
||||
import buttonsCSS from '../styles/buttons.jsx';
|
||||
|
||||
|
||||
class InfoButton extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (props) {
|
||||
this.state = {
|
||||
id: props.id,
|
||||
type: props.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
this.setState({
|
||||
id: props.id,
|
||||
type: props.type,
|
||||
})
|
||||
}
|
||||
|
||||
getTMDBLink() {
|
||||
const id = this.state.id;
|
||||
const type = this.state.type;
|
||||
|
||||
if (type === 'movie')
|
||||
return 'https://www.themoviedb.org/movie/' + id
|
||||
else if (type === 'show')
|
||||
return 'https://www.themoviedb.org/tv/' + id
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<a href={this.getTMDBLink()}>
|
||||
<Interactive
|
||||
as='button'
|
||||
hover={buttonsCSS.info_hover}
|
||||
focus={buttonsCSS.info_hover}
|
||||
style={buttonsCSS.info}>
|
||||
|
||||
<span>More info</span>
|
||||
</Interactive>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InfoButton;
|
||||
22
.archive/client/app/components/buttons/request_button.jsx
Normal file
22
.archive/client/app/components/buttons/request_button.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
class RequestButton extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {textColor: 'white'};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Text
|
||||
style={{color: this.state.textColor}}
|
||||
onEnter={() => this.setState({textColor: 'red'})}
|
||||
onExit={() => this.setState({textColor: 'white'})}>
|
||||
This text will turn red when you look at it.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RequestButton;
|
||||
53
.archive/client/app/components/http.jsx
Normal file
53
.archive/client/app/components/http.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getCookie } from './Cookie.jsx';
|
||||
|
||||
// class http {
|
||||
// dispatch(obj) {
|
||||
// console.log(obj);
|
||||
// }
|
||||
|
||||
function checkStatus(response) {
|
||||
const hasError = (response.status < 200 || response.status >= 300)
|
||||
if (hasError) {
|
||||
throw response.text();
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
function parseJSON(response) { return response.json(); }
|
||||
|
||||
|
||||
|
||||
// *
|
||||
// * Retrieve search results from tmdb with added seasoned information.
|
||||
// * @param {String} uri query you want to search for
|
||||
// * @param {Number} page representing pagination of results
|
||||
// * @returns {Promise} succeeds if results were found
|
||||
|
||||
// fetchSearch(uri) {
|
||||
// fetch(uri, {
|
||||
// method: 'GET',
|
||||
// headers: {
|
||||
// 'authorization': getCookie('token')
|
||||
// },
|
||||
// })
|
||||
// .then(response => {
|
||||
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// export default http;
|
||||
|
||||
export function fetchJSON(url, method, data) {
|
||||
return fetch(url, {
|
||||
method: method,
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'authorization': getCookie('token'),
|
||||
'loggedinuser': getCookie('loggedInUser'),
|
||||
}),
|
||||
body: JSON.stringify(data)
|
||||
}).then(checkStatus).then(parseJSON);
|
||||
}
|
||||
34
.archive/client/app/components/images/loading.jsx
Normal file
34
.archive/client/app/components/images/loading.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
function Loading() {
|
||||
return (
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<svg version="1.1"
|
||||
style={{height: '75px'}}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 80 80">
|
||||
<path
|
||||
fill="#e9a131"
|
||||
d="M40,72C22.4,72,8,57.6,8,40C8,22.4,
|
||||
22.4,8,40,8c17.6,0,32,14.4,32,32c0,1.1-0.9,2-2,2
|
||||
s-2-0.9-2-2c0-15.4-12.6-28-28-28S12,24.6,12,40s12.6,
|
||||
28,28,28c1.1,0,2,0.9,2,2S41.1,72,40,72z">
|
||||
|
||||
<animateTransform
|
||||
attributeType="xml"
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 40 40"
|
||||
to="360 40 40"
|
||||
dur="1.0s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default Loading;
|
||||
109
.archive/client/app/components/redux/reducer.jsx
Normal file
109
.archive/client/app/components/redux/reducer.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
import { setCookie } from '../Cookie.jsx';
|
||||
|
||||
const SET_LOGIN_PENDING = 'SET_LOGIN_PENDING';
|
||||
const SET_LOGIN_SUCCESS = 'SET_LOGIN_SUCCESS';
|
||||
const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR';
|
||||
|
||||
export function login(email, password) {
|
||||
return dispatch => {
|
||||
dispatch(setLoginPending(true));
|
||||
dispatch(setLoginSuccess(false));
|
||||
dispatch(setLoginError(null));
|
||||
|
||||
callLoginApi(email, password, error => {
|
||||
dispatch(setLoginPending(false));
|
||||
if (!error) {
|
||||
dispatch(setLoginSuccess(true));
|
||||
} else {
|
||||
dispatch(setLoginError(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setLoginPending(isLoginPending) {
|
||||
return {
|
||||
type: SET_LOGIN_PENDING,
|
||||
isLoginPending
|
||||
};
|
||||
}
|
||||
|
||||
function setLoginSuccess(isLoginSuccess) {
|
||||
return {
|
||||
type: SET_LOGIN_SUCCESS,
|
||||
isLoginSuccess
|
||||
};
|
||||
}
|
||||
|
||||
function setLoginError(loginError) {
|
||||
return {
|
||||
type: SET_LOGIN_ERROR,
|
||||
loginError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function callLoginApi(username, password, callback) {
|
||||
|
||||
Promise.resolve()
|
||||
fetch('https://apollo.kevinmidboe.com/api/v1/user/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password,
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
switch (response.status) {
|
||||
case 200:
|
||||
response.json()
|
||||
.then((data) => {
|
||||
if (data.success === true) {
|
||||
let token = data.token;
|
||||
setCookie('token', token, 10);
|
||||
setCookie('logged_in', true, 10);
|
||||
setCookie('loggedInUser', username, 10);
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
return callback(null);
|
||||
})
|
||||
|
||||
case 401:
|
||||
return callback(new Error(response.statusText));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
return callback(new Error('Invalid username and password'));
|
||||
});
|
||||
}
|
||||
|
||||
export default function reducer(state = {
|
||||
isLoginSuccess: false,
|
||||
isLoginPending: false,
|
||||
loginError: null
|
||||
}, action) {
|
||||
switch (action.type) {
|
||||
case SET_LOGIN_PENDING:
|
||||
return Object.assign({}, state, {
|
||||
isLoginPending: action.isLoginPending
|
||||
});
|
||||
|
||||
case SET_LOGIN_SUCCESS:
|
||||
return Object.assign({}, state, {
|
||||
isLoginSuccess: action.isLoginSuccess
|
||||
});
|
||||
|
||||
case SET_LOGIN_ERROR:
|
||||
return Object.assign({}, state, {
|
||||
loginError: action.loginError
|
||||
});
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
7
.archive/client/app/components/redux/store.jsx
Normal file
7
.archive/client/app/components/redux/store.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import logger from 'redux-logger';
|
||||
import reducer from './reducer.jsx';
|
||||
|
||||
const store = createStore(reducer, {}, applyMiddleware(thunk, logger));
|
||||
export default store;
|
||||
16
.archive/client/app/components/styles/adminComponent.jsx
Normal file
16
.archive/client/app/components/styles/adminComponent.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export default {
|
||||
sidebar: {
|
||||
float: 'left',
|
||||
width: '18%',
|
||||
minWidth: '250px',
|
||||
fontFamily: '"Open Sans", sans-serif',
|
||||
fontSize: '14px',
|
||||
borderRight: '2px solid #f2f2f2',
|
||||
},
|
||||
selectedObjectPanel: {
|
||||
width: '80%',
|
||||
float: 'right',
|
||||
fontFamily: '"Open Sans", sans-serif',
|
||||
marginTop: '1em',
|
||||
}
|
||||
}
|
||||
58
.archive/client/app/components/styles/adminRequestInfo.jsx
Normal file
58
.archive/client/app/components/styles/adminRequestInfo.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
export default {
|
||||
wrapper: {
|
||||
width: '100%',
|
||||
},
|
||||
stick: {
|
||||
marginBottom: '1em',
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: '2em',
|
||||
},
|
||||
image: {
|
||||
width: '105px',
|
||||
borderRadius: '4px',
|
||||
},
|
||||
|
||||
info: {
|
||||
paddingTop: '1em',
|
||||
paddingBottom: '0.5em',
|
||||
marginRight: '2em',
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid #d0d0d0',
|
||||
borderRadius: '2px',
|
||||
display: 'flex',
|
||||
},
|
||||
|
||||
type_icon: {
|
||||
marginLeft: '-0.2em',
|
||||
marginRight: '0.7em',
|
||||
},
|
||||
type_text: {
|
||||
verticalAlign: 'super',
|
||||
},
|
||||
|
||||
|
||||
info_poster: {
|
||||
marginLeft: '2em',
|
||||
flex: '0 1 10%'
|
||||
},
|
||||
|
||||
info_request: {
|
||||
flex: '0 1 auto'
|
||||
},
|
||||
info_request_header: {
|
||||
margin: '0',
|
||||
marginBottom: '0.5em',
|
||||
},
|
||||
|
||||
info_movie: {
|
||||
maxWidth: '70%',
|
||||
marginLeft: '1em',
|
||||
flex: '0 1 auto',
|
||||
},
|
||||
info_movie_header: {
|
||||
margin: '0',
|
||||
marginBottom: '0.5em',
|
||||
}
|
||||
}
|
||||
153
.archive/client/app/components/styles/adminSidebar.jsx
Normal file
153
.archive/client/app/components/styles/adminSidebar.jsx
Normal file
@@ -0,0 +1,153 @@
|
||||
export default {
|
||||
header: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
body: {
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
|
||||
parentElement: {
|
||||
display: 'inline-block',
|
||||
width: '100%',
|
||||
border: '1px solid grey',
|
||||
borderRadius: '2px',
|
||||
padding: '4px',
|
||||
margin: '4px',
|
||||
marginLeft: '4px',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
|
||||
parentElement_hover: {
|
||||
backgroundColor: '#f8f8f8',
|
||||
pointer: 'hand',
|
||||
},
|
||||
|
||||
parentElement_active: {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
|
||||
parentElement_selected: {
|
||||
display: 'inline-block',
|
||||
width: '100%',
|
||||
border: '1px solid grey',
|
||||
borderRadius: '2px',
|
||||
padding: '4px',
|
||||
margin: '4px 0px 4px 4px',
|
||||
marginLeft: '10px',
|
||||
backgroundColor: 'white',
|
||||
},
|
||||
|
||||
title: {
|
||||
maxWidth: '65%',
|
||||
display: 'inline-flex',
|
||||
},
|
||||
|
||||
link: {
|
||||
color: 'black',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
|
||||
rightContainer: {
|
||||
float: 'right',
|
||||
},
|
||||
|
||||
|
||||
|
||||
searchSidebar: {
|
||||
height: '4em',
|
||||
},
|
||||
searchInner: {
|
||||
top: '0',
|
||||
right: '0',
|
||||
left: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
width: '90%',
|
||||
minWidth: '280px',
|
||||
height: '30px',
|
||||
border: '1px solid #d0d0d0',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden'
|
||||
},
|
||||
searchTextField: {
|
||||
display: 'inline-block',
|
||||
width: '90%',
|
||||
padding: '.3em',
|
||||
verticalAlign: 'middle',
|
||||
border: 'none',
|
||||
background: '#fff',
|
||||
fontSize: '14px',
|
||||
marginTop: '-7px',
|
||||
},
|
||||
searchIcon: {
|
||||
width: '15px',
|
||||
height: '16px',
|
||||
marginRight: '4px',
|
||||
marginTop: '7px',
|
||||
},
|
||||
searchSVGIcon: {
|
||||
fill: 'none',
|
||||
stroke: '#9d9d9d',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
strokeMiterlimit: '10',
|
||||
},
|
||||
|
||||
|
||||
ulFilterSelectors: {
|
||||
borderBottom: '2px solid #f1f1f1',
|
||||
display: 'flex',
|
||||
padding: '0',
|
||||
margin: '0',
|
||||
listStyle: 'none',
|
||||
justifyContent: 'space-evenly',
|
||||
},
|
||||
aFilterSelectors: {
|
||||
color: '#3eaaaf',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
spanFilterSelectors: {
|
||||
content: '""',
|
||||
bottom: '-2px',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '2px',
|
||||
backgroundColor: '#3eaaaa',
|
||||
},
|
||||
|
||||
|
||||
ulCard: {
|
||||
margin: '1em 0 0 0',
|
||||
padding: '0',
|
||||
listStyle: 'none',
|
||||
borderBottom: '.46rem solid #f1f1f',
|
||||
backgroundColor: '#f1f1f1',
|
||||
overflow: 'scroll',
|
||||
},
|
||||
|
||||
|
||||
card: {
|
||||
padding: '.1em .5em .8em 1.5em',
|
||||
marginBottom: '.26rem',
|
||||
height: 'auto',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
cardSelected: {
|
||||
padding: '.1em .5em .8em 1.5em',
|
||||
marginBottom: '.26rem',
|
||||
height: 'auto',
|
||||
cursor: 'pointer',
|
||||
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
titleCard: {
|
||||
fontSize: '15px',
|
||||
fontWeight: '400',
|
||||
whiteSpace: 'no-wrap',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
pCard: {
|
||||
margin: '0',
|
||||
},
|
||||
}
|
||||
59
.archive/client/app/components/styles/adminTorrentTable.jsx
Normal file
59
.archive/client/app/components/styles/adminTorrentTable.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
table: {
|
||||
width: '80%',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
tableHeader: {
|
||||
},
|
||||
col: {
|
||||
cursor: 'pointer',
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
paddingBottom: '0.5em',
|
||||
textAlign: 'left',
|
||||
},
|
||||
bodyCol: {
|
||||
marginTop: '0.5em',
|
||||
},
|
||||
|
||||
searchSidebar: {
|
||||
height: '4em',
|
||||
marginTop: '1em',
|
||||
},
|
||||
searchInner: {
|
||||
top: '0',
|
||||
right: '0',
|
||||
left: '0',
|
||||
bottom: '0',
|
||||
margin: 'auto',
|
||||
width: '50%',
|
||||
minWidth: '280px',
|
||||
height: '30px',
|
||||
border: '1px solid #d0d0d0',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden'
|
||||
},
|
||||
searchTextField: {
|
||||
display: 'inline-block',
|
||||
width: '95%',
|
||||
padding: '.3em',
|
||||
verticalAlign: 'middle',
|
||||
border: 'none',
|
||||
background: '#fff',
|
||||
fontSize: '14px',
|
||||
marginTop: '-7px',
|
||||
},
|
||||
searchIcon: {
|
||||
width: '15px',
|
||||
height: '16px',
|
||||
marginRight: '4px',
|
||||
marginTop: '7px',
|
||||
},
|
||||
searchSVGIcon: {
|
||||
fill: 'none',
|
||||
stroke: '#9d9d9d',
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
strokeMiterlimit: '10',
|
||||
},
|
||||
}
|
||||
80
.archive/client/app/components/styles/buttons.jsx
Normal file
80
.archive/client/app/components/styles/buttons.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
export default {
|
||||
|
||||
submit: {
|
||||
color: '#e9a131',
|
||||
marginRight: '10px',
|
||||
backgroundColor: 'white',
|
||||
border: '#e9a131 2px solid',
|
||||
borderColor: '#e9a131',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'center',
|
||||
padding: '10px',
|
||||
minWidth: '100px',
|
||||
float: 'left',
|
||||
fontSize: '13px',
|
||||
fontWeight: '800',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
|
||||
submit_hover: {
|
||||
backgroundColor: '#e9a131',
|
||||
color: 'white',
|
||||
},
|
||||
|
||||
info: {
|
||||
color: '#00d17c',
|
||||
marginRight: '10px',
|
||||
backgroundColor: 'white',
|
||||
border: '#00d17c 2px solid',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'center',
|
||||
padding: '10px',
|
||||
minWidth: '100px',
|
||||
float: 'left',
|
||||
fontSize: '13px',
|
||||
fontWeight: '800',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
|
||||
info_hover: {
|
||||
backgroundColor: '#00d17c',
|
||||
color: 'white',
|
||||
},
|
||||
|
||||
edit: {
|
||||
color: '#4a95da',
|
||||
marginRight: '10px',
|
||||
backgroundColor: 'white',
|
||||
border: '#4a95da 2px solid',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'center',
|
||||
padding: '10px',
|
||||
minWidth: '100px',
|
||||
float: 'left',
|
||||
fontSize: '13px',
|
||||
fontWeight: '800',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
|
||||
edit_small: {
|
||||
color: '#4a95da',
|
||||
marginRight: '10px',
|
||||
backgroundColor: 'white',
|
||||
border: '#4a95da 2px solid',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'center',
|
||||
padding: '4px',
|
||||
minWidth: '50px',
|
||||
float: 'left',
|
||||
fontSize: '13px',
|
||||
fontWeight: '800',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
|
||||
edit_hover: {
|
||||
backgroundColor: '#4a95da',
|
||||
color: 'white',
|
||||
},
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
export default {
|
||||
bodyDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
flexFlow: 'row wrap',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
|
||||
wrappingDiv: {
|
||||
|
||||
},
|
||||
|
||||
requestPoster: {
|
||||
height: '150px',
|
||||
},
|
||||
|
||||
infoDiv: {
|
||||
marginTop: 0,
|
||||
marginLeft: '10px',
|
||||
float: 'right',
|
||||
},
|
||||
}
|
||||
62
.archive/client/app/components/styles/searchObject.jsx
Normal file
62
.archive/client/app/components/styles/searchObject.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
export default {
|
||||
container: {
|
||||
maxWidth: '95%',
|
||||
margin: '0 auto',
|
||||
minHeight: '230px'
|
||||
},
|
||||
|
||||
title_large: {
|
||||
color: 'black',
|
||||
fontSize: '2em',
|
||||
},
|
||||
|
||||
title_small: {
|
||||
color: 'black',
|
||||
fontSize: '22px',
|
||||
},
|
||||
|
||||
stats_large: {
|
||||
fontSize: '0.8em'
|
||||
},
|
||||
|
||||
stats_small: {
|
||||
marginTop: '5px',
|
||||
fontSize: '0.8em'
|
||||
},
|
||||
|
||||
posterContainer: {
|
||||
float: 'left',
|
||||
zIndex: '3',
|
||||
position: 'relative',
|
||||
marginRight: '30px'
|
||||
},
|
||||
|
||||
posterImage: {
|
||||
border: '2px none',
|
||||
borderRadius: '2px',
|
||||
width: '150px'
|
||||
},
|
||||
|
||||
backgroundImage: {
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
buttons: {
|
||||
paddingTop: '20px',
|
||||
},
|
||||
|
||||
summary: {
|
||||
fontSize: '15px',
|
||||
},
|
||||
|
||||
dividerRow: {
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
itemDivider: {
|
||||
width: '90%',
|
||||
borderBottom: '1px solid grey',
|
||||
margin: '2rem auto'
|
||||
}
|
||||
}
|
||||
177
.archive/client/app/components/styles/searchRequestStyle.jsx
Normal file
177
.archive/client/app/components/styles/searchRequestStyle.jsx
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
export default {
|
||||
body: {
|
||||
fontFamily: "'Open Sans', sans-serif",
|
||||
backgroundColor: '#f7f7f7',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
minHeight: '100%',
|
||||
},
|
||||
|
||||
backgroundLargeHeader: {
|
||||
width: '100%',
|
||||
minHeight: '180px',
|
||||
backgroundColor: 'rgb(1, 28, 35)',
|
||||
// backgroundImage: 'radial-gradient(circle, #004c67 0, #005771 120%)',
|
||||
zIndex: 1,
|
||||
marginBottom: '70px'
|
||||
},
|
||||
|
||||
backgroundSmallHeader: {
|
||||
width: '100%',
|
||||
minHeight: '120px',
|
||||
backgroundColor: '#011c23',
|
||||
zIndex: 1,
|
||||
marginBottom: '40px'
|
||||
},
|
||||
|
||||
requestWrapper: {
|
||||
maxWidth: '1200px',
|
||||
margin: 'auto',
|
||||
paddingTop: '10px',
|
||||
backgroundColor: 'white',
|
||||
position: 'relative',
|
||||
zIndex: '10',
|
||||
boxShadow: '0 1px 2px grey',
|
||||
},
|
||||
|
||||
pageTitle: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
},
|
||||
|
||||
pageTitleLargeSpan: {
|
||||
color: 'white',
|
||||
fontSize: '3em',
|
||||
marginTop: '4vh',
|
||||
marginBottom: '6vh'
|
||||
},
|
||||
|
||||
pageTitleSmallSpan: {
|
||||
color: 'white',
|
||||
fontSize: '2em',
|
||||
marginTop: '3vh',
|
||||
marginBottom: '3vh'
|
||||
},
|
||||
|
||||
searchLargeContainer: {
|
||||
height: '52px',
|
||||
width: '77%',
|
||||
paddingLeft: '23%',
|
||||
backgroundColor: 'white',
|
||||
boxShadow: 'grey 0px 1px 2px',
|
||||
},
|
||||
|
||||
searchSmallContainer: {
|
||||
},
|
||||
|
||||
searchIcon: {
|
||||
position: 'absolute',
|
||||
fontSize: '1.6em',
|
||||
marginTop: '7px',
|
||||
color: '#4f5b66',
|
||||
display: 'block',
|
||||
},
|
||||
|
||||
searchLargeBar: {
|
||||
width: '50%',
|
||||
height: '50px',
|
||||
background: '#ffffff',
|
||||
border: 'none',
|
||||
fontSize: '12pt',
|
||||
float: 'left',
|
||||
color: '#63717f',
|
||||
paddingLeft: '40px',
|
||||
},
|
||||
|
||||
searchSmallBar: {
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
background: '#ffffff',
|
||||
border: 'none',
|
||||
fontSize: '11pt',
|
||||
float: 'left',
|
||||
color: '#63717f',
|
||||
paddingLeft: '65px',
|
||||
marginLeft: '-25px',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
|
||||
|
||||
// Dropdown for selecting tmdb lists
|
||||
controls: {
|
||||
textAlign: 'left',
|
||||
paddingTop: '8px',
|
||||
width: '33.3333%',
|
||||
marginLeft: '0',
|
||||
marginRight: '0',
|
||||
},
|
||||
|
||||
withData: {
|
||||
boxSizing: 'border-box',
|
||||
marginBottom: '0',
|
||||
display: 'block',
|
||||
padding: '0',
|
||||
verticalAlign: 'baseline',
|
||||
font: 'inherit',
|
||||
textAlign: 'left',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
|
||||
sortOptions: {
|
||||
border: '1px solid #000',
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
lineHeight: 'normal',
|
||||
textAlign: 'left',
|
||||
padding: '4px 12px',
|
||||
paddingRight: '2rem',
|
||||
backgroundImage: 'url("data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOCAxOCI+CiAgPHRpdGxlPmFycm93LWRvd24tbWljcm88L3RpdGxlPgogIDxwb2x5bGluZSBwb2ludHM9IjE0IDQuNjcgOSAxMy4zMyA0IDQuNjciIHN0eWxlPSJmaWxsOiBub25lO3N0cm9rZTogIzAwMDtzdHJva2UtbWl0ZXJsaW1pdDogMTA7c3Ryb2tlLXdpZHRoOiAycHgiLz4KPC9zdmc+Cg==")',
|
||||
backgroundSize: '18px 18px',
|
||||
backgroundPosition: 'right 8px center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
width: 'auto',
|
||||
display: 'inline-block',
|
||||
outline: 'none',
|
||||
boxSizing: 'border-box',
|
||||
fontSize: '15px',
|
||||
WebkitAppearance: 'none',
|
||||
MozAppearance: 'none',
|
||||
appearance: 'none',
|
||||
},
|
||||
|
||||
|
||||
searchFilterActive: {
|
||||
color: '#00d17c',
|
||||
fontSize: '1em',
|
||||
marginLeft: '10px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
searchFilterNotActive: {
|
||||
color: 'white',
|
||||
fontSize: '1em',
|
||||
marginLeft: '10px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
filter: {
|
||||
color: 'white',
|
||||
paddingLeft: '40px',
|
||||
width: '60%',
|
||||
},
|
||||
|
||||
resultLargeHeader: {
|
||||
color: 'black',
|
||||
fontSize: '1.6em',
|
||||
width: '20%',
|
||||
},
|
||||
|
||||
resultSmallHeader: {
|
||||
paddingLeft: '12px',
|
||||
color: 'black',
|
||||
fontSize: '1.4em',
|
||||
},
|
||||
}
|
||||
15
.archive/client/app/index.html
Normal file
15
.archive/client/app/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0, user-scalable=0">
|
||||
<title>seasoned Shows</title>
|
||||
</head>
|
||||
<body style='margin: 0'>
|
||||
<div id="root">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
20
.archive/client/app/index.js
Normal file
20
.archive/client/app/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* @Author: KevinMidboe
|
||||
* @Date: 2017-06-01 21:08:55
|
||||
* @Last Modified by: KevinMidboe
|
||||
* @Last Modified time: 2017-10-20 19:24:52
|
||||
|
||||
./client/index.js
|
||||
which is the webpack entry file
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import Root from './Root.jsx';
|
||||
|
||||
render((
|
||||
<HashRouter>
|
||||
<Root />
|
||||
</HashRouter>
|
||||
), document.getElementById('root'));
|
||||
44
.archive/client/package.json
Normal file
44
.archive/client/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "seasoned",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"repository": "https://github.com/KevinMidboe/seasonedShows",
|
||||
"author": "Kevin Midboe",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --open --config webpack.dev.js",
|
||||
"build": "NODE_ENV=production webpack --config webpack.prod.js",
|
||||
"build_dev": "webpack --config webpack.dev.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"clean-webpack-plugin": "^0.1.17",
|
||||
"css-loader": "^1.0.0",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"path": "^0.12.7",
|
||||
"react": "^15.6.1",
|
||||
"react-burger-menu": "^2.1.6",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-infinite-scroller": "^1.0.15",
|
||||
"react-interactive": "^0.8.1",
|
||||
"react-notify-toast": "^0.3.2",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-responsive": "^1.3.4",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"redux": "^3.7.2",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"urijs": "^1.18.12",
|
||||
"webfontloader": "^1.6.28",
|
||||
"webpack": "^4.0.0",
|
||||
"webpack-dev-server": "^3.1.11",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1"
|
||||
}
|
||||
}
|
||||
33
.archive/client/webpack.common.js
Normal file
33
.archive/client/webpack.common.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @Author: KevinMidboe
|
||||
* @Date: 2017-06-01 19:09:16
|
||||
* @Last Modified by: KevinMidboe
|
||||
* @Last Modified time: 2017-10-24 21:55:41
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './app/index.js',
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(['dist']),
|
||||
new HtmlWebpackPlugin({
|
||||
template: './app/index.html',
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ },
|
||||
]
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: '[name].bundle.js',
|
||||
path: path.resolve(__dirname, 'dist')
|
||||
}
|
||||
};
|
||||
|
||||
17
.archive/client/webpack.dev.js
Normal file
17
.archive/client/webpack.dev.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* @Author: KevinMidboe
|
||||
* @Date: 2017-06-01 19:09:16
|
||||
* @Last Modified by: KevinMidboe
|
||||
* @Last Modified time: 2017-10-24 22:12:52
|
||||
*/
|
||||
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
devtool: 'inline-source-map',
|
||||
devServer: {
|
||||
contentBase: './dist',
|
||||
headers: {'Access-Control-Allow-Origin': '*'}
|
||||
}
|
||||
});;
|
||||
28
.archive/client/webpack.prod.js
Normal file
28
.archive/client/webpack.prod.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author: KevinMidboe
|
||||
* @Date: 2017-06-01 19:09:16
|
||||
* @Last Modified by: KevinMidboe
|
||||
* @Last Modified time: 2017-10-24 22:26:29
|
||||
*/
|
||||
|
||||
const merge = require('webpack-merge');
|
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const common = require('./webpack.common.js');
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = merge(common, {
|
||||
plugins: [
|
||||
new UglifyJSPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: './app/index.html',
|
||||
title: 'Caching'
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
filename: '[name].[chunkhash].js',
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user