Merge pull request #66 from KevinMidboe/styling

Styling
This commit is contained in:
2017-12-02 13:19:54 +01:00
committed by GitHub
19 changed files with 768 additions and 464 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.DS_Store
env
shows.db
yarn.lock
*/yarn.lock

View File

@@ -1,124 +0,0 @@
import React from 'react';
import Notifications, {notify} from 'react-notify-toast';
// StyleComponents
import movieStyle from './styles/movieObjectStyle.jsx';
var MediaQuery = require('react-responsive');
import RequestButton from './buttons/request_button.jsx';
import { fetchJSON } from './http.jsx';
class MovieObject {
constructor(object) {
this.id = object.id;
this.title = object.title;
this.year = object.year;
this.type = object.type;
// Check if object.poster != undefined
this.rating = object.rating;
this.poster = object.poster;
this.background = object.background;
this.matchedInPlex = object.matchedInPlex;
this.summary = object.summary;
}
requestExisting(movie) {
console.log('Exists', movie);
}
requestMovie() {
// fetch('http://localhost:31459/api/v1/plex/request/' + this.id + '?type='+this.type, {
// fetch('https://apollo.kevinmidboe.com/api/v1/plex/request/' + this.id + '?type='+this.type, {
// method: 'POST'
// });
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);
}
getElement(index) {
const element_key = index + this.id;
// TODO set the poster image async by updating the dom after this is returned
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/w300' + this.poster;
}
var backgroundPath = 'https://image.tmdb.org/t/p/w640_and_h360_bestv2/' + this.background;
var foundInPlex;
if (this.matchedInPlex) {
foundInPlex = <button onClick={() => {this.requestExisting(this)}}
style={movieStyle.requestButton}><span>Request Anyway</span></button>;
} else {
foundInPlex = <button onClick={() => {this.requestMovie()}}
style={movieStyle.requestButton}><span>&#x0002B; Request</span></button>;
}
if (this.type === 'movie')
var themoviedbLink = 'https://www.themoviedb.org/movie/' + this.id
else if (this.type === 'show')
var themoviedbLink = 'https://www.themoviedb.org/tv/' + this.id
// TODO add request button class
return (
<div key={element_key}>
<Notifications />
<div style={movieStyle.resultItem} key={this.id}>
<MediaQuery minWidth={600}>
<div style={movieStyle.resultPoster}>
<img style={movieStyle.resultPosterImg} id='poster' src={posterPath}></img>
</div>
</MediaQuery>
<div>
<MediaQuery minWidth={600}>
<span style={movieStyle.resultTitleLarge}>{this.title}</span>
<br></br>
<span style={movieStyle.yearRatingLarge}>Released: { this.year } | Rating: {this.rating}</span>
<br></br>
<span style={movieStyle.summary}>{this.summary}</span>
<br></br>
</MediaQuery>
<MediaQuery maxWidth={600}>
<img src={ backgroundPath } style={movieStyle.background}></img>
<span style={movieStyle.resultTitleSmall}>{this.title}</span>
<br></br>
<span style={movieStyle.yearRatingSmall}>Released: {this.year} | Rating: {this.rating}</span>
</MediaQuery>
<span className='imdbLogo'>
</span>
<div style={movieStyle.buttons}>
{foundInPlex}
<a href={themoviedbLink}>
<button style={movieStyle.tmdbButton}><span>Info</span></button>
</a>
</div>
</div>
</div>
<MediaQuery maxWidth={600}>
<br></br>
</MediaQuery>
<div style={movieStyle.row}>
<div style={movieStyle.itemDivider}></div>
</div>
</div>)
}
}
export default MovieObject;

View File

@@ -0,0 +1,137 @@
import React from 'react';
import Notifications, {notify} from 'react-notify-toast';
// StyleComponents
import searchObjectCSS from './styles/searchObject.jsx';
import buttonsCSS from './styles/buttons.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;
this.background = object.background;
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/w300' + 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>&#x0002B; Request</span>
</Interactive>;
}
if (this.type === 'movie')
var themoviedbLink = 'https://www.themoviedb.org/movie/' + this.id
else if (this.type === 'show')
var themoviedbLink = 'https://www.themoviedb.org/tv/' + this.id
// 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}</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}
<a href={themoviedbLink}>
<Interactive
as='button'
hover={buttonsCSS.info_hover}
focus={buttonsCSS.info_hover}
style={buttonsCSS.info}>
<span>Info</span>
</Interactive>
</a>
</div>
</div>
<MediaQuery maxWidth={600}>
<br />
</MediaQuery>
<div style={searchObjectCSS.dividerRow}>
<div style={searchObjectCSS.itemDivider}></div>
</div>
</div>
)
}
}
export default SearchObject;

View File

@@ -1,14 +1,14 @@
import React from 'react';
import MovieObject from './MovieObject.jsx';
// StyleComponents
import searchStyle from './styles/searchRequestStyle.jsx';
import movieStyle from './styles/movieObjectStyle.jsx';
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';
@@ -29,7 +29,8 @@ class SearchRequest extends React.Component {
page: 1,
resultHeader: '',
loadResults: false,
scrollHasMore: true
scrollHasMore: true,
loading: false,
}
this.allowedListTypes = ['discover', 'popular', 'nowplaying', 'upcoming']
@@ -88,9 +89,9 @@ class SearchRequest extends React.Component {
this.state.page = 1;
}
writeLoading() {
setLoading(value) {
this.setState({
responseMovieList: 'Loading...'
loading: value
});
}
@@ -122,10 +123,7 @@ class SearchRequest extends React.Component {
// 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) {
// Write loading animation
// this.writeLoading();
callSearchFillMovieList(uri) {
Promise.resolve()
.then(() => this.callURI(uri, 'GET'))
.then(response => {
@@ -152,7 +150,7 @@ class SearchRequest extends React.Component {
}
// Convert to json and update the state of responseMovieList with the results of the api call
// mapped as a movieObject.
// mapped as a SearchObject.
response.json()
.then(responseData => {
if (this.state.page === 1) {
@@ -180,7 +178,6 @@ class SearchRequest extends React.Component {
callListFillMovieList(uri) {
// Write loading animation
// this.writeLoading();
Promise.resolve()
.then(() => this.callURI(uri, 'GET', undefined))
@@ -198,7 +195,7 @@ class SearchRequest extends React.Component {
}
// Convert to json and update the state of responseMovieList with the results of the api call
// mapped as a movieObject.
// mapped as a SearchObject.
response.json()
.then(responseData => {
if (this.state.page === 1) {
@@ -218,6 +215,7 @@ class SearchRequest extends React.Component {
})
.catch((error) => {
console.log('Something went wrong when fetching query.', error)
})
}
@@ -287,10 +285,10 @@ class SearchRequest extends React.Component {
}
}
// When called passes the variable to MovieObject and calls it's interal function for
// When called passes the variable to SearchObject and calls it's interal function for
// generating the wanted HTML
createMovieObjects(item, index) {
let movie = new MovieObject(item);
let movie = new SearchObject(item);
return movie.getElement(index);
}
@@ -345,12 +343,12 @@ class SearchRequest extends React.Component {
movieToggle() {
if (this.state.movieFilter)
return <span style={searchStyle.searchFilterActive}
return <span style={searchRequestCSS.searchFilterActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('movies')}}
id="category_active">Movies</span>
else
return <span style={searchStyle.searchFilterNotActive}
return <span style={searchRequestCSS.searchFilterNotActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('movies')}}
id="category_active">Movies</span>
@@ -358,12 +356,12 @@ class SearchRequest extends React.Component {
showToggle() {
if (this.state.showFilter)
return <span style={searchStyle.searchFilterActive}
return <span style={searchRequestCSS.searchFilterActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('shows')}}
id="category_active">TV Shows</span>
else
return <span style={searchStyle.searchFilterNotActive}
return <span style={searchRequestCSS.searchFilterNotActive}
className="search_category hvrUnderlineFromCenter"
onClick={() => {this.toggleFilter('shows')}}
id="category_active">TV Shows</span>
@@ -379,21 +377,21 @@ class SearchRequest extends React.Component {
pageStart={0}
loadMore={this.pageForwards.bind(this)}
hasMore={this.state.scrollHasMore}
loader={loader}
loader={<Loading />}
initialLoad={this.state.loadResults}>
<MediaQuery minWidth={600}>
<div style={searchStyle.body}>
<div className='backgroundHeader' style={searchStyle.backgroundLargeHeader}>
<div className='pageTitle' style={searchStyle.pageTitle}>
<span style={searchStyle.pageTitleLargeSpan}>Request new content</span>
<div style={searchRequestCSS.body}>
<div className='backgroundHeader' style={searchRequestCSS.backgroundLargeHeader}>
<div className='pageTitle' style={searchRequestCSS.pageTitle}>
<span style={searchRequestCSS.pageTitleLargeSpan}>Request new content</span>
</div>
<div className='box' style={searchStyle.box}>
<div style={searchStyle.searchLargeContainer}>
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
<div className='box' style={searchRequestCSS.box}>
<div style={searchRequestCSS.searchLargeContainer}>
<span style={searchRequestCSS.searchIcon}><i className="fa fa-search"></i></span>
<input style={searchStyle.searchLargeBar} type="text" id="search" placeholder="Search for new content..."
<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}/>
@@ -402,27 +400,27 @@ class SearchRequest extends React.Component {
</div>
</div>
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
<span style={searchStyle.resultLargeHeader}>{this.state.resultHeader}</span>
<br></br><br></br>
{this.state.responseMovieList}
<div id='requestMovieList' ref='requestMovieList' style={searchRequestCSS.requestWrapper}>
<span style={searchRequestCSS.resultLargeHeader}>{this.state.resultHeader}</span>
<br></br><br></br>
{this.state.responseMovieList}
</div>
</div>
</MediaQuery>
<MediaQuery maxWidth={600}>
<div style={searchStyle.body}>
<div className='backgroundHeader' style={searchStyle.backgroundSmallHeader}>
<div className='pageTitle' style={searchStyle.pageTitle}>
<span style={searchStyle.pageTitleSmallSpan}>Request new content</span>
<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={searchStyle.box}>
<div style={searchStyle.searchSmallContainer}>
<span style={searchStyle.searchIcon}><i className="fa fa-search"></i></span>
<div className='box' style={searchRequestCSS.box}>
<div style={searchRequestCSS.searchSmallContainer}>
<span style={searchRequestCSS.searchIcon}><i className="fa fa-search"></i></span>
<input style={searchStyle.searchSmallBar} type="text" id="search" placeholder="Search for new content..."
<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}/>
@@ -431,11 +429,11 @@ class SearchRequest extends React.Component {
</div>
</div>
<div id='requestMovieList' ref='requestMovieList' style={searchStyle.requestWrapper}>
<span style={searchStyle.resultSmallHeader}>{this.state.resultHeader}</span>
<div id='requestMovieList' ref='requestMovieList' style={searchRequestCSS.requestWrapper}>
<span style={searchRequestCSS.resultSmallHeader}>{this.state.resultHeader}</span>
<br></br><br></br>
{this.state.responseMovieList}
{this.state.responseMovieList}
</div>
</div>
</MediaQuery>
@@ -446,4 +444,4 @@ class SearchRequest extends React.Component {
}
export default SearchRequest;
export default SearchRequest;

View File

@@ -1,10 +1,5 @@
/*
./app/components/App.jsx
<FetchData url={"https://apollo.kevinmidboe.com/api/v1/plex/playing"} />
*/
import React from 'react';
import { HashRouter as Router, Route, Switch, IndexRoute } from 'react-router-dom';
import LoginForm from './LoginForm/LoginForm.jsx';
import { Provider } from 'react-redux';
@@ -16,6 +11,8 @@ 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) {
@@ -25,6 +22,7 @@ class AdminComponent extends React.Component {
}
}
// Fetches all requested elements and updates the state with response
componentWillMount() {
fetchJSON('https://apollo.kevinmidboe.com/api/v1/plex/requests/all', 'GET')
.then(result => {
@@ -34,16 +32,9 @@ class AdminComponent extends React.Component {
})
}
// Displays loginform if not logged in and passes response from
// api call to sidebar and infoPanel through props
verifyLoggedIn() {
let adminComponentStyle = {
sidebar: {
float: 'left',
},
selectedObjectPanel: {
float: 'left',
}
}
const logged_in = getCookie('logged_in');
if (!logged_in) {
return <LoginForm />
@@ -53,20 +44,21 @@ class AdminComponent extends React.Component {
let listItemSelected = undefined;
const requestParam = this.props.match.params.request;
if (requestParam && this.state.requested_objects !== '') {
selectedRequest = this.state.requested_objects[requestParam]
listItemSelected = requestParam
listItemSelected = requestParam;
}
return (
<div>
<div style={adminComponentStyle.sidebar}>
<div style={adminCSS.sidebar}>
<Sidebar
requested_objects={this.state.requested_objects}
listItemSelected={listItemSelected}
style={adminComponentStyle.sidebar} />
/>
</div>
<div style={adminComponentStyle.selectedObjectPanel}>
<div style={adminCSS.selectedObjectPanel}>
<AdminRequestInfo
selectedRequest={selectedRequest}
listItemSelected={listItemSelected}

View File

@@ -1,10 +1,31 @@
import React, { Component } from 'react';
import { fetchJSON } from '../http.jsx';
import PirateSearch from './PirateSearch.jsx'
// Stylesheets
import requestInfoCSS from '../styles/adminRequestInfo.jsx'
import buttonsCSS from '../styles/buttons.jsx';
// Interactive button
import Interactive from 'react-interactive';
class AdminRequestInfo extends Component {
constructor() {
super();
this.state = {
statusValue: '',
}
this.requestInfo = '';
}
componentWillReceiveProps(props) {
this.requestInfo = props.selectedRequest;
this.state.statusValue = this.requestInfo.status;
}
userAgent(agent) {
@@ -19,6 +40,35 @@ class AdminRequestInfo extends Component {
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.setState({
statusValue: eventValue
})
}
requested_by_user(request_user) {
if (request_user === 'NULL')
return undefined
@@ -28,57 +78,52 @@ class AdminRequestInfo extends Component {
)
}
displayInfo() {
let adminIndexStyle = {
wrapper: {
width: '100%',
},
headerWrapper: {
width: '100%',
},
poster: {
float: 'left',
minHeight: '450px',
},
info: {
float: 'left',
minHeight: '450px',
}
}
const request = this.props.selectedRequest;
displayInfo() {
const request = this.props.selectedRequest;
if (request) {
return (
<div style={adminIndexStyle.wrapper}>
<div style={adminIndexStyle.headerWrapper}>
<span>{request.name} </span>
<span>{request.year}</span>
</div>
<div style={adminIndexStyle.poster}>
<img src={'https://image.tmdb.org/t/p/w300/' + request.image_path} />
</div>
<div style={adminIndexStyle.info}>
<span>type: {request.type}</span><br />
<span>status: {request.status}</span><br />
<span>ip: {request.ip}</span><br />
<span>user_agent: {this.userAgent(request.user_agent)}</span><br />
<span>request_date: {request.requested_date}</span><br />
{ this.requested_by_user(request.requested_by) }
</div>
<PirateSearch
name={request.name} />
</div>
)
}
}
if (request) {
return (
<div style={requestInfoCSS.wrapper}>
<div style={requestInfoCSS.headerWrapper}>
<span>{request.name} </span>
<span>{request.year}</span>
</div>
render() {
return (
<div>{this.displayInfo()}</div>
);
}
<div style={requestInfoCSS.info}>
<span>type: {request.type}</span><br />
{this.generateStatusDropdown()}<br />
<span>status: {request.status}</span><br />
<span>ip: {request.ip}</span><br />
<span>user_agent: {this.userAgent(request.user_agent)}</span><br />
<span>request_date: {request.requested_date}</span><br />
{ this.requested_by_user(request.requested_by) }
</div>
<div>
<Interactive
as='button'
onClick={() => {}}
style={buttonsCSS.edit}
focus={buttonsCSS.edit_hover}
hover={buttonsCSS.edit_hover}>
<span>Show info</span>
</Interactive>
<PirateSearch name={request.name} />
</div>
</div>
)
}
}
render() {
return (
<div>{this.displayInfo()}</div>
);
}
}
export default AdminRequestInfo;

View File

@@ -1,18 +1,25 @@
import React, { Component } from 'react';
import { fetchJSON } from '../http.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 = {
response: [],
name: '',
loading: '',
}
}
sendToDownload(torrent) {
console.log(torrent.magnet)
let data = {magnet: torrent.magnet}
fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/add', 'POST', data)
.then((response) => {
@@ -24,10 +31,15 @@ class PirateSearch extends Component {
const query = this.props.name;
const type = this.props.type;
this.setState({
loading: <Loading />
})
fetchJSON('https://apollo.kevinmidboe.com/api/v1/pirate/search?query='+query+'&type='+type, 'GET')
.then((response) => {
console.log(response.torrents)
this.setState({
loading: '',
response: response.torrents.map((torrent, index) => {
return (
<div key={index}>
@@ -46,8 +58,20 @@ class PirateSearch extends Component {
render() {
return (
<div>
<span>{this.props.name}</span>
<button onClick={() => {this.searchTheBay(this)}}>Load shit</button>
<div>
<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>
{ this.state.loading }
<span>{this.state.response}</span>
</div>
)

View File

@@ -1,57 +1,213 @@
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 {
generateListElements(index, item) {
if (index == this.props.listItemSelected)
return (
<td>{item.name}</td>
)
else
return (
<td><Link to={{ pathname: '/admin/'+String(index)}}>{item.name}</Link></td>
)
}
displayRequestedElementsInfo() {
if (this.props.requested_objects) {
let requestedElement = this.props.requested_objects.map((item, index) => {
return (
<tr key={index}>
{ this.generateListElements(index, item) }
<td>{item.status}</td>
<td>{item.requested_date}</td>
</tr>
)
})
return (
<table key='requestedTable'>
<thead>
<tr>
<th><b>Name</b></th>
<th><b>Status</b></th>
<th><b>Date</b></th>
</tr>
</thead>
<tbody>
{requestedElement}
</tbody>
</table>
)
constructor(props){
super(props)
// Constructor with states holding the search query and the element of reponse.
this.state = {
filterValue: '',
filterQuery: '',
requestItemsToBeDisplayed: [],
listItemSelected: '',
}
}
render() {
console.log('sidebar: ', this.props.requested_objects)
return (
<div>
<h1>Hello from the sidebar: </h1>
<span>{ this.displayRequestedElementsInfo() }</span>
</div>
);
}
// 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);
}
// 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.name.toLowerCase().indexOf(query.toLowerCase()) != -1)
return this.generateListElements(index, item);
})
this.setState({
requestItemsToBeDisplayed: filteredByQuery,
filterQuery: query,
});
}
generateFilterDropdown() {
return (
<select onChange={ event => this.filterItems(event.target.value) } value={this.state.filterValue}>
<option value='all'>All</option>
<option value='requested'>Requested</option>
<option value='downloading'>Downloading</option>
<option value='downloaded'>Downloaded</option>
</select>
)
}
generateFilterSearchbar() {
return (
<input
type="text"
id="search"
placeholder="Filter by name..."
onChange={event => this.updateFilterQuery(event)}
value={this.state.filterQuery}/>
)
}
// A colored bar indicating the status of a item by color.
generateRequestIndicator(status) {
let statusColor;
switch (status) {
case 'requested':
// Yellow
statusColor = '#ffe14d';
break;
case 'downloading':
// Blue
statusColor = '#3fc3f3';
break;
case 'downloaded':
// Green
statusColor = '#6be682';
break;
default:
statusColor = 'grey';
}
const indicatorCSS = {
width: '100%',
height: '4px',
marginTop: '3px',
backgroundColor: statusColor,
}
return (
<div style={indicatorCSS}></div>
)
}
generateListElements(index, item) {
if (index == this.state.listItemSelected) {
return (
<div style={sidebarCSS.parentElement_selected}>
<div style={sidebarCSS.contentContainer}>
<span style={sidebarCSS.title}> {item.name } </span>
<div style={sidebarCSS.rightContainer}>
<span>{ this.convertDateToDaysSince(item.requested_date) }</span>
</div>
</div>
<span>Status: { item.status }</span>
<br/>
<span>Matches found: 0</span>
{ this.generateRequestIndicator(item.status) }
</div>
)
}
else
return (
<Link style={sidebarCSS.link} to={{ pathname: '/admin/'+String(index)}}>
<Interactive
key={index}
style={sidebarCSS.parentElement}
as='div'
hover={sidebarCSS.parentElement_hover}
focus={sidebarCSS.parentElement_hover}
active={sidebarCSS.parentElement_active}>
<span style={sidebarCSS.title}> {item.name } </span>
<div style={sidebarCSS.rightContainer}>
<span>{ this.convertDateToDaysSince(item.requested_date) }</span>
</div>
<br/>
{ this.generateRequestIndicator(item.status) }
</Interactive>
</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: requestedElement,
})
}
render() {
let bodyCSS = sidebarCSS.body;
if (typeof InstallTrigger !== 'undefined')
bodyCSS.width = '-moz-min-content';
return (
<div>
<h1>Hello from the sidebar: </h1>
{ this.generateFilterDropdown() }
{ this.generateFilterSearchbar() }
<div key='requestedTable' style={bodyCSS}>
{ this.state.requestItemsToBeDisplayed }
</div>
</div>
);
}
}
export default SidebarComponent;

View 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;

View File

@@ -0,0 +1,8 @@
export default {
sidebar: {
float: 'left',
},
selectedObjectPanel: {
float: 'left',
}
}

View File

@@ -0,0 +1,11 @@
export default {
wrapper: {
width: '100%',
},
headerWrapper: {
width: '100%',
},
poster: {
minHeight: '450px',
},
}

View File

@@ -0,0 +1,50 @@
export default {
body: {
backgroundColor: 'white',
width: 'min-content',
},
parentElement: {
display: 'inline-block',
width: '300px',
border: '1px solid black',
borderRadius: '2px',
padding: '4px',
margin: '4px',
marginLeft: '4px',
backgroundColor: 'white',
},
parentElement_hover: {
marginLeft: '10px',
},
parentElement_active: {
textDecoration: 'none',
},
parentElement_selected: {
display: 'inline-block',
width: '300px',
border: '1px solid black',
borderRadius: '2px',
padding: '4px',
margin: '4px 0px 4px 4px',
marginLeft: '10px',
backgroundColor: 'white',
},
title: {
maxWidth: '70%',
display: 'inline-flex',
},
link: {
color: 'black',
textDecoration: 'none',
},
rightContainer: {
float: 'right',
},
}

View 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',
},
}

View File

@@ -1,101 +0,0 @@
export default {
resultItem: {
maxWidth: '95%',
margin: '0 auto',
minHeight: '230px'
},
movie_content: {
marginLeft: '15px'
},
resultTitleLarge: {
color: 'black',
fontSize: '2em',
},
resultTitleSmall: {
color: 'black',
fontSize: '22px',
},
yearRatingLarge: {
fontSize: '0.8em'
},
resultPoster: {
float: 'left',
zIndex: '3',
position: 'relative',
marginRight: '30px'
},
background: {
width: '100%'
},
yearRatingSmall: {
marginTop: '5px',
fontSize: '0.8em'
},
resultPosterImg: {
border: '2px none',
borderRadius: '2px',
width: '150px'
},
cornerRibbon: {
position: 'absolute',
width: '450px',
},
summary: {
fontSize: '15px',
},
buttons: {
paddingTop: '20px'
},
requestButton: {
color: '#e9a131',
marginRight: '10px',
background: 'white',
border: '#e9a131 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer'
},
tmdbButton: {
color: '#00d17c',
marginRight: '10px',
background: 'white',
border: '#00d17c 2px solid',
borderRadius: '4px',
textAlign: 'center',
padding: '10px',
minWidth: '100px',
float: 'left',
fontSize: '13px',
fontWeight: '800',
cursor: 'pointer'
},
row: {
width: '100%'
},
itemDivider: {
width: '90%',
borderBottom: '1px solid grey',
margin: '2rem auto'
}
}

View File

@@ -0,0 +1,58 @@
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%'
},
summary: {
fontSize: '15px',
},
dividerRow: {
width: '100%'
},
itemDivider: {
width: '90%',
borderBottom: '1px solid grey',
margin: '2rem auto'
}
}

View File

@@ -11,12 +11,13 @@ export default {
backgroundLargeHeader: {
width: '100%',
minHeight: '400px',
backgroundColor: '#011c23',
backgroundColor: 'rgb(1, 28, 35)',
// backgroundImage: 'radial-gradient(circle, #004c67 0, #005771 120%)',
zIndex: 1,
marginBottom: '-100px'
},
backgroundSmallHeader: {
backgroundSmallHeader: {
width: '100%',
minHeight: '300px',
backgroundColor: '#011c23',
@@ -31,7 +32,7 @@ export default {
backgroundColor: 'white',
position: 'relative',
zIndex: '10',
boxShadow: '0 4px 2px black'
boxShadow: '0 1px 2px grey',
},
pageTitle: {
@@ -43,7 +44,7 @@ export default {
pageTitleLargeSpan: {
color: 'white',
fontSize: '3em',
fontSize: '4em',
marginTop: '4vh',
marginBottom: '6vh'
},
@@ -133,70 +134,4 @@ export default {
color: 'black',
fontSize: '1.4em',
},
row: {
width: '100%'
},
itemDivider: {
width: '90%',
borderBottom: '1px solid grey',
margin: '1rem auto'
},
pageNavigationBar: {
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
pageNavigationButton: {
margin: '0 auto',
},
hvrUnderlineFromCenter: {
color: 'white',
fontSize: '1em',
paddingTop: '12px',
marginBottom: '12px',
marginLeft: '10px',
cursor: 'pointer',
display: 'inline-block',
verticalAlign: 'middle',
WebkitTransform: 'perspective(1px) translateZ(0)',
transform: 'perspective(1px) translateZ(0)',
boxShadow: '0 0 1px transparent',
position: 'relative',
overflow: 'hidden',
':before': {
content: "",
position: 'absolute',
zIndex: '-1',
left: '50%',
right: '50%',
bottom: '0',
background: '#00d17c',
height: '2px',
WebkitTransitionProperty: 'left, right',
transitionProperty: 'left, right',
WebkitTransitionDuration: '0.3s',
transitionDuration: '0.3s',
WebkitTransitionTimingFunction: 'ease-out',
transitionTimingFunction: 'ease-out'
},
':hover:before': {
left: 0,
right: 0
},
'focus:before': {
left: 0,
right: 0
},
'active:before': {
left: 0,
right: 0
}
}
}

View File

@@ -4,7 +4,7 @@
<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">
<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'>

View File

@@ -19,6 +19,7 @@
"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",

View File

@@ -21,8 +21,7 @@ module.exports = {
],
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /node_modules/ },
]
},