Progress on featured content
This commit is contained in:
parent
4fe3fe5dcb
commit
ecd2063fed
24 changed files with 500 additions and 46 deletions
29
ui/js/actions/content.js
Normal file
29
ui/js/actions/content.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import * as types from 'constants/action_types'
|
||||
import lbry from 'lbry'
|
||||
import lbryio from 'lbryio';
|
||||
|
||||
export function doFetchFeaturedContent() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_STARTED,
|
||||
})
|
||||
|
||||
const success = ({ Categories, Uris }) => {
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
categories: Categories,
|
||||
uris: Uris,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const failure = () => {
|
||||
}
|
||||
|
||||
lbryio.call('discover', 'list', { version: "early-access" } )
|
||||
.then(success, failure)
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ module.exports = app;
|
|||
// import {FileListDownloaded, FileListPublished} from './page/file-list.js';
|
||||
// import Header from './component/header.js';
|
||||
// import {Modal, ExpandableModal} from './component/modal.js';
|
||||
// import {Link} from './component/link.js';
|
||||
// import {Link} from './component/link';
|
||||
//
|
||||
//
|
||||
// const {remote, ipcRenderer, shell} = require('electron');
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import lbryio from "../lbryio.js";
|
||||
import Modal from "./modal.js";
|
||||
import ModalPage from "./modal-page.js";
|
||||
import {Link, RewardLink} from "../component/link.js";
|
||||
import {Link, RewardLink} from "../component/link";
|
||||
import {FormRow} from "../component/form.js";
|
||||
import {CreditAmount, Address} from "../component/common.js";
|
||||
import {getLocal, getSession, setSession, setLocal} from '../utils.js';
|
||||
|
|
13
ui/js/component/fileTile/index.js
Normal file
13
ui/js/component/fileTile/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import FileTile from './view'
|
||||
|
||||
const select = (state) => ({
|
||||
})
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
})
|
||||
|
||||
export default connect(select, perform)(FileTile)
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import lbry from '../lbry.js';
|
||||
import lbryuri from '../lbryuri.js';
|
||||
import lbry from 'lbry.js';
|
||||
import lbryuri from 'lbryuri.js';
|
||||
import Link from 'component/link';
|
||||
import {FileActions} from '../component/file-actions.js';
|
||||
import {BusyMessage, TruncatedText, FilePrice} from '../component/common.js';
|
||||
import UriIndicator from '../component/channel-indicator.js';
|
||||
import {FileActions} from 'component/file-actions.js';
|
||||
import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js';
|
||||
import UriIndicator from 'component/channel-indicator.js';
|
||||
|
||||
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
||||
export let FileTileStream = React.createClass({
|
||||
|
@ -217,7 +217,7 @@ export let FileCardStream = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
export let FileTile = React.createClass({
|
||||
let FileTile = React.createClass({
|
||||
_isMounted: false,
|
||||
_isResolvePending: false,
|
||||
|
13
ui/js/component/fileTileStream/index.js
Normal file
13
ui/js/component/fileTileStream/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import FileTileStream from './view'
|
||||
|
||||
const select = (state) => ({
|
||||
})
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
})
|
||||
|
||||
export default connect(select, perform)(FileTileStream)
|
129
ui/js/component/fileTileStream/view.jsx
Normal file
129
ui/js/component/fileTileStream/view.jsx
Normal file
|
@ -0,0 +1,129 @@
|
|||
import React from 'react';
|
||||
import lbry from 'lbry.js';
|
||||
import lbryuri from 'lbryuri.js';
|
||||
import Link from 'component/link';
|
||||
import {
|
||||
FileActions
|
||||
} from 'component/file-actions.js';
|
||||
import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js';
|
||||
import UriIndicator from 'component/channel-indicator.js';
|
||||
|
||||
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
||||
const FileTileStream = React.createClass({
|
||||
_fileInfoSubscribeId: null,
|
||||
_isMounted: null,
|
||||
|
||||
propTypes: {
|
||||
uri: React.PropTypes.string,
|
||||
metadata: React.PropTypes.object,
|
||||
contentType: React.PropTypes.string.isRequired,
|
||||
outpoint: React.PropTypes.string,
|
||||
hasSignature: React.PropTypes.bool,
|
||||
signatureIsValid: React.PropTypes.bool,
|
||||
hideOnRemove: React.PropTypes.bool,
|
||||
hidePrice: React.PropTypes.bool,
|
||||
obscureNsfw: React.PropTypes.bool
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showNsfwHelp: false,
|
||||
isHidden: false,
|
||||
}
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
obscureNsfw: !lbry.getClientSetting('showNsfw'),
|
||||
hidePrice: false,
|
||||
hasSignature: false,
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this._isMounted = true;
|
||||
if (this.props.hideOnRemove) {
|
||||
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
|
||||
}
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
if (this._fileInfoSubscribeId) {
|
||||
lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
|
||||
}
|
||||
},
|
||||
onFileInfoUpdate: function(fileInfo) {
|
||||
if (!fileInfo && this._isMounted && this.props.hideOnRemove) {
|
||||
this.setState({
|
||||
isHidden: true
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseOver: function() {
|
||||
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
|
||||
this.setState({
|
||||
showNsfwHelp: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseOut: function() {
|
||||
if (this.state.showNsfwHelp) {
|
||||
this.setState({
|
||||
showNsfwHelp: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
if (this.state.isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const uri = lbryuri.normalize(this.props.uri);
|
||||
const metadata = this.props.metadata;
|
||||
const isConfirmed = !!metadata;
|
||||
const title = isConfirmed ? metadata.title : uri;
|
||||
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
||||
return (
|
||||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<div className={"row-fluid card__inner file-tile__row"}>
|
||||
<div className="span3 file-tile__thumbnail-container">
|
||||
<a href={'?show=' + uri}><Thumbnail className="file-tile__thumbnail" {... metadata && metadata.thumbnail ? {src: metadata.thumbnail} : {}} alt={'Photo for ' + this.props.uri} /></a>
|
||||
</div>
|
||||
<div className="span9">
|
||||
<div className="card__title-primary">
|
||||
{ !this.props.hidePrice
|
||||
? <FilePrice uri={this.props.uri} />
|
||||
: null}
|
||||
<div className="meta"><a href={'?show=' + this.props.uri}>{uri}</a></div>
|
||||
<h3>
|
||||
<a href={'?show=' + uri} title={title}>
|
||||
<TruncatedText lines={1}>
|
||||
{title}
|
||||
</TruncatedText>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<p className="file-tile__description">
|
||||
<TruncatedText lines={2}>
|
||||
{isConfirmed
|
||||
? metadata.description
|
||||
: <span className="empty">This file is pending confirmation.</span>}
|
||||
</TruncatedText>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.showNsfwHelp
|
||||
? <div className='card-overlay'>
|
||||
<p>
|
||||
This content is Not Safe For Work.
|
||||
To view adult content, please change your <Link className="button-text" href="?settings" label="Settings" />.
|
||||
</p>
|
||||
</div>
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default FileTileStream
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import lbryuri from '../lbryuri.js';
|
||||
import {Icon, CreditAmount} from './common.js';
|
||||
import lbryuri from 'lbryuri.js';
|
||||
import {Icon, CreditAmount} from 'component/common.js';
|
||||
import Link from 'component/link';
|
||||
|
||||
let Header = React.createClass({
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React from 'react';
|
||||
import SettingsPage from 'page/settings.js';
|
||||
import HelpPage from 'page/help';
|
||||
import WatchPage from 'page/watch.js';
|
||||
import ReportPage from 'page/report.js';
|
||||
import StartPage from 'page/start.js';
|
||||
import WalletPage from 'page/wallet';
|
||||
import DetailPage from 'page/show.js';
|
||||
import PublishPage from 'page/publish.js';
|
||||
import DiscoverPage from 'page/discover.js';
|
||||
import DiscoverPage from 'page/discover';
|
||||
import SplashScreen from 'component/splash.js';
|
||||
import DeveloperPage from 'page/developer.js';
|
||||
import {
|
||||
|
@ -30,7 +29,6 @@ const Router = (props) => {
|
|||
return route(currentPage, {
|
||||
'settings': <SettingsPage {...props} />,
|
||||
'help': <HelpPage {...props} />,
|
||||
'watch': <WatchPage {...props} />,
|
||||
'report': <ReportPage {...props} />,
|
||||
'downloaded': <FileListDownloaded {...props} />,
|
||||
'published': <FileListPublished {...props} />,
|
||||
|
|
42
ui/js/component/video/index.js
Normal file
42
ui/js/component/video/index.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect,
|
||||
} from 'react-redux'
|
||||
import {
|
||||
doCloseModal,
|
||||
} from 'actions/app'
|
||||
import {
|
||||
selectCurrentModal,
|
||||
} from 'selectors/app'
|
||||
import {
|
||||
doWatchVideo,
|
||||
doLoadVideo,
|
||||
} from 'actions/content'
|
||||
import {
|
||||
selectLoadingCurrentUri,
|
||||
selectCurrentUriFileReadyToPlay,
|
||||
selectCurrentUriIsPlaying,
|
||||
selectCurrentUriFileInfo,
|
||||
selectDownloadingCurrentUri,
|
||||
} from 'selectors/file_info'
|
||||
import {
|
||||
selectCurrentUriCostInfo,
|
||||
} from 'selectors/cost_info'
|
||||
import Video from './view'
|
||||
|
||||
const select = (state) => ({
|
||||
costInfo: selectCurrentUriCostInfo(state),
|
||||
fileInfo: selectCurrentUriFileInfo(state),
|
||||
modal: selectCurrentModal(state),
|
||||
isLoading: selectLoadingCurrentUri(state),
|
||||
readyToPlay: selectCurrentUriFileReadyToPlay(state),
|
||||
isDownloading: selectDownloadingCurrentUri(state),
|
||||
})
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
loadVideo: () => dispatch(doLoadVideo()),
|
||||
watchVideo: (elem) => dispatch(doWatchVideo()),
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
})
|
||||
|
||||
export default connect(select, perform)(Video)
|
130
ui/js/component/video/view.jsx
Normal file
130
ui/js/component/video/view.jsx
Normal file
|
@ -0,0 +1,130 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Icon,
|
||||
Thumbnail,
|
||||
} from 'component/common';
|
||||
import FilePrice from 'component/filePrice'
|
||||
import Link from 'component/link';
|
||||
import Modal from 'component/modal';
|
||||
|
||||
const plyr = require('plyr')
|
||||
|
||||
class Video extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
// TODO none of this mouse handling stuff seems to actually do anything?
|
||||
this._controlsHideDelay = 3000 // Note: this needs to be shorter than the built-in delay in Electron, or Electron will hide the controls before us
|
||||
this._controlsHideTimeout = null
|
||||
this.state = {}
|
||||
}
|
||||
handleMouseMove() {
|
||||
if (this._controlsTimeout) {
|
||||
clearTimeout(this._controlsTimeout);
|
||||
}
|
||||
|
||||
if (!this.state.controlsShown) {
|
||||
this.setState({
|
||||
controlsShown: true,
|
||||
});
|
||||
}
|
||||
this._controlsTimeout = setTimeout(() => {
|
||||
if (!this.isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
controlsShown: false,
|
||||
});
|
||||
}, this._controlsHideDelay);
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
if (this._controlsTimeout) {
|
||||
clearTimeout(this._controlsTimeout);
|
||||
}
|
||||
|
||||
if (this.state.controlsShown) {
|
||||
this.setState({
|
||||
controlsShown: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onWatchClick() {
|
||||
this.props.watchVideo().then(() => {
|
||||
if (!this.props.modal) {
|
||||
this.setState({
|
||||
isPlaying: true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
startPlaying() {
|
||||
this.setState({
|
||||
isPlaying: true
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
readyToPlay = false,
|
||||
thumbnail,
|
||||
metadata,
|
||||
isLoading,
|
||||
isDownloading,
|
||||
fileInfo,
|
||||
} = this.props
|
||||
const {
|
||||
isPlaying = false,
|
||||
} = this.state
|
||||
|
||||
let loadStatusMessage = ''
|
||||
|
||||
if (isLoading) {
|
||||
loadStatusMessage = "Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it"
|
||||
} else if (isDownloading) {
|
||||
loadStatusMessage = "Downloading stream... not long left now!"
|
||||
}
|
||||
|
||||
return (
|
||||
<div onMouseMove={this.handleMouseMove.bind(this)} onMouseLeave={this.handleMouseLeave.bind(this)} className={"video " + this.props.className + (isPlaying && readyToPlay ? " video--active" : " video--hidden")}>{
|
||||
isPlaying ?
|
||||
!readyToPlay ?
|
||||
<span>this is the world's worst loading screen and we shipped our software with it anyway... <br /><br />{loadStatusMessage}</span> :
|
||||
<VideoPlayer downloadPath={fileInfo.download_path} /> :
|
||||
<div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}>
|
||||
<WatchLink icon="icon-play" onWatchClick={this.onWatchClick.bind(this)} startPlaying={this.startPlaying.bind(this)} {...this.props}></WatchLink>
|
||||
</div>
|
||||
}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayer extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
const elem = this.refs.video
|
||||
const {
|
||||
downloadPath,
|
||||
contentType,
|
||||
} = this.props
|
||||
const players = plyr.setup(elem)
|
||||
players[0].play()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
downloadPath,
|
||||
contentType,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<video controls id="video" ref="video">
|
||||
<source src={downloadPath} type={contentType} />
|
||||
</video>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Video
|
|
@ -33,3 +33,7 @@ export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS'
|
|||
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED'
|
||||
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'
|
||||
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED'
|
||||
|
||||
// Content
|
||||
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED'
|
||||
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED'
|
||||
|
|
17
ui/js/page/discover/index.js
Normal file
17
ui/js/page/discover/index.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
selectFeaturedContentByCategory
|
||||
} from 'selectors/content'
|
||||
import DiscoverPage from './view'
|
||||
|
||||
const select = (state) => ({
|
||||
featuredContentByCategory: selectFeaturedContentByCategory(state),
|
||||
})
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
})
|
||||
|
||||
export default connect(select, perform)(DiscoverPage)
|
|
@ -1,27 +1,26 @@
|
|||
import React from 'react';
|
||||
import lbry from '../lbry.js';
|
||||
import lighthouse from '../lighthouse.js';
|
||||
import {FileTile} from '../component/file-tile.js';
|
||||
import Link from 'component/link';
|
||||
import {ToolTip} from '../component/tooltip.js';
|
||||
import lbryio from 'lbryio.js';
|
||||
import FileTile from 'component/fileTile';
|
||||
import { FileTileStream } from 'component/fileTileStream'
|
||||
import {ToolTip} from 'component/tooltip.js';
|
||||
|
||||
const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' +
|
||||
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
|
||||
'"five" to put your content here!');
|
||||
|
||||
let FeaturedCategory = React.createClass({
|
||||
render: function() {
|
||||
return (<div className="card-row card-row--small">
|
||||
{ this.props.category ?
|
||||
<h3 className="card-row__header">{this.props.category}
|
||||
{ this.props.category.match(/^community/i) ?
|
||||
<ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header"/>
|
||||
: '' }</h3>
|
||||
: '' }
|
||||
{ this.props.names.map((name) => { return <FileTile key={name} displayStyle="card" uri={name} /> }) }
|
||||
</div>)
|
||||
const FeaturedCategory = (props) => {
|
||||
const {
|
||||
category,
|
||||
names,
|
||||
} = props
|
||||
|
||||
return <div className="card-row card-row--small">
|
||||
<h3 className="card-row__header">{category}
|
||||
{category && category.match(/^community/i) && <ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header" />}
|
||||
</h3>
|
||||
{names.map(name => <FileTile key={name} displayStyle="card" uri={name} />)}
|
||||
</div>
|
||||
}
|
||||
})
|
||||
|
||||
let DiscoverPage = React.createClass({
|
||||
getInitialState: function() {
|
|
@ -1,14 +1,13 @@
|
|||
import React from 'react';
|
||||
import lbry from '../lbry.js';
|
||||
import lbryuri from '../lbryuri.js';
|
||||
import lbry from 'lbry.js';
|
||||
import lbryuri from 'lbryuri.js';
|
||||
import Link from 'component/link';
|
||||
import FormField from '../component/form.js';
|
||||
import {SubHeader} from '../component/header.js';
|
||||
import {FileTileStream} from '../component/file-tile.js';
|
||||
import rewards from '../rewards.js';
|
||||
import lbryio from '../lbryio.js';
|
||||
import {BusyMessage, Thumbnail} from '../component/common.js';
|
||||
|
||||
import {FormField} from 'component/form.js';
|
||||
import SubHeader from '../component/sub-header';
|
||||
import {FileTileStream} from 'component/fileTile';
|
||||
import rewards from 'rewards.js';
|
||||
import lbryio from 'lbryio.js';
|
||||
import {BusyMessage, Thumbnail} from 'component/common.js';
|
||||
|
||||
export let FileListNav = React.createClass({
|
||||
render: function() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import lbryio from '../lbryio.js';
|
||||
import {Link} from '../component/link.js';
|
||||
import {Link} from '../component/link';
|
||||
import Notice from '../component/notice.js';
|
||||
import {CreditAmount} from '../component/common.js';
|
||||
//
|
||||
|
|
|
@ -5,7 +5,7 @@ import {CreditAmount, Icon} from '../component/common.js';
|
|||
import rewards from '../rewards.js';
|
||||
import Modal from '../component/modal.js';
|
||||
import {WalletNav} from './wallet.js';
|
||||
import {RewardLink} from '../component/link.js';
|
||||
import {RewardLink} from '../component/link';
|
||||
|
||||
const RewardTile = React.createClass({
|
||||
propTypes: {
|
||||
|
|
|
@ -4,7 +4,7 @@ import lbryio from '../lbryio.js';
|
|||
import lbryuri from '../lbryuri.js';
|
||||
import lighthouse from '../lighthouse.js';
|
||||
import {FileTile, FileTileStream} from '../component/file-tile.js';
|
||||
import {Link} from '../component/link.js';
|
||||
import {Link} from '../component/link';
|
||||
import {ToolTip} from '../component/tooltip.js';
|
||||
import {BusyMessage} from '../component/common.js';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import {FormField, FormRow} from '../component/form.js';
|
||||
import {SubHeader} from '../component/header.js';
|
||||
import {SubHeader} from '../component/sub-header.js';
|
||||
import lbry from '../lbry.js';
|
||||
|
||||
export let SettingsNav = React.createClass({
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import lbry from '../lbry.js';
|
||||
import lighthouse from '../lighthouse.js';
|
||||
import lbryuri from '../lbryuri.js';
|
||||
import {Video} from '../page/watch.js'
|
||||
import Video from 'component/video'
|
||||
import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js';
|
||||
import {FileActions} from '../component/file-actions.js';
|
||||
import UriIndicator from '../component/channel-indicator.js';
|
||||
|
|
31
ui/js/reducers/content.js
Normal file
31
ui/js/reducers/content.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import * as types from 'constants/action_types'
|
||||
|
||||
const reducers = {}
|
||||
const defaultState = {
|
||||
}
|
||||
|
||||
reducers[types.FETCH_FEATURED_CONTENT_STARTED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
fetchingFeaturedContent: true
|
||||
})
|
||||
}
|
||||
|
||||
reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) {
|
||||
const {
|
||||
uris
|
||||
} = action.data
|
||||
const newFeaturedContent = Object.assign({}, state.featuredContent, {
|
||||
byCategory: uris,
|
||||
})
|
||||
|
||||
return Object.assign({}, state, {
|
||||
fetchingFeaturedContent: false,
|
||||
featuredContent: newFeaturedContent
|
||||
})
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
37
ui/js/selectors/content.js
Normal file
37
ui/js/selectors/content.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { createSelector } from 'reselect'
|
||||
import {
|
||||
selectDaemonReady,
|
||||
selectCurrentPage,
|
||||
} from 'selectors/app'
|
||||
|
||||
export const _selectState = state => state.content || {}
|
||||
|
||||
export const selectFeaturedContent = createSelector(
|
||||
_selectState,
|
||||
(state) => state.featuredContent || {}
|
||||
)
|
||||
|
||||
export const selectFeaturedContentByCategory = createSelector(
|
||||
selectFeaturedContent,
|
||||
(featuredContent) => featuredContent.byCategory || {}
|
||||
)
|
||||
|
||||
export const selectFetchingFeaturedContent = createSelector(
|
||||
_selectState,
|
||||
(state) => !!state.fetchingFeaturedContent
|
||||
)
|
||||
|
||||
export const shouldFetchFeaturedContent = createSelector(
|
||||
selectDaemonReady,
|
||||
selectCurrentPage,
|
||||
selectFetchingFeaturedContent,
|
||||
selectFeaturedContentByCategory,
|
||||
(daemonReady, page, fetching, byCategory) => {
|
||||
if (!daemonReady) return false
|
||||
if (page != 'discover') return false
|
||||
if (fetching) return false
|
||||
if (Object.keys(byCategory).length != 0) return false
|
||||
|
||||
return true
|
||||
}
|
||||
)
|
|
@ -6,6 +6,7 @@ import {
|
|||
createLogger
|
||||
} from 'redux-logger'
|
||||
import appReducer from 'reducers/app';
|
||||
import contentReducer from 'reducers/content';
|
||||
import walletReducer from 'reducers/wallet'
|
||||
|
||||
function isFunction(object) {
|
||||
|
@ -18,6 +19,7 @@ function isNotFunction(object) {
|
|||
|
||||
const reducers = redux.combineReducers({
|
||||
app: appReducer,
|
||||
content: contentReducer,
|
||||
wallet: walletReducer,
|
||||
});
|
||||
|
||||
|
|
|
@ -2,10 +2,16 @@ import {
|
|||
shouldFetchTransactions,
|
||||
shouldGetReceiveAddress,
|
||||
} from 'selectors/wallet'
|
||||
import {
|
||||
shouldFetchFeaturedContent,
|
||||
} from 'selectors/content'
|
||||
import {
|
||||
doFetchTransactions,
|
||||
doGetNewAddress,
|
||||
} from 'actions/wallet'
|
||||
import {
|
||||
doFetchFeaturedContent,
|
||||
} from 'actions/content'
|
||||
|
||||
const triggers = []
|
||||
|
||||
|
@ -19,6 +25,11 @@ triggers.push({
|
|||
action: doGetNewAddress
|
||||
})
|
||||
|
||||
triggers.push({
|
||||
selector: shouldFetchFeaturedContent,
|
||||
action: doFetchFeaturedContent,
|
||||
})
|
||||
|
||||
const runTriggers = function() {
|
||||
triggers.forEach(function(trigger) {
|
||||
const state = app.store.getState();
|
||||
|
|
Loading…
Reference in a new issue