Redux #115
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 {FileListDownloaded, FileListPublished} from './page/file-list.js';
|
||||||
// import Header from './component/header.js';
|
// import Header from './component/header.js';
|
||||||
// import {Modal, ExpandableModal} from './component/modal.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');
|
// const {remote, ipcRenderer, shell} = require('electron');
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from "react";
|
||||||
import lbryio from "../lbryio.js";
|
import lbryio from "../lbryio.js";
|
||||||
import Modal from "./modal.js";
|
import Modal from "./modal.js";
|
||||||
import ModalPage from "./modal-page.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 {FormRow} from "../component/form.js";
|
||||||
import {CreditAmount, Address} from "../component/common.js";
|
import {CreditAmount, Address} from "../component/common.js";
|
||||||
import {getLocal, getSession, setSession, setLocal} from '../utils.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 React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from 'lbry.js';
|
||||||
import lbryuri from '../lbryuri.js';
|
import lbryuri from 'lbryuri.js';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
import {FileActions} from '../component/file-actions.js';
|
import {FileActions} from 'component/file-actions.js';
|
||||||
import {BusyMessage, TruncatedText, FilePrice} from '../component/common.js';
|
import {Thumbnail, TruncatedText, FilePrice} from 'component/common.js';
|
||||||
import UriIndicator from '../component/channel-indicator.js';
|
import UriIndicator from 'component/channel-indicator.js';
|
||||||
|
|
||||||
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
||||||
export let FileTileStream = React.createClass({
|
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,
|
_isMounted: false,
|
||||||
_isResolvePending: 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 React from 'react';
|
||||||
import lbryuri from '../lbryuri.js';
|
import lbryuri from 'lbryuri.js';
|
||||||
import {Icon, CreditAmount} from './common.js';
|
import {Icon, CreditAmount} from 'component/common.js';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
|
|
||||||
let Header = React.createClass({
|
let Header = React.createClass({
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SettingsPage from 'page/settings.js';
|
import SettingsPage from 'page/settings.js';
|
||||||
import HelpPage from 'page/help';
|
import HelpPage from 'page/help';
|
||||||
import WatchPage from 'page/watch.js';
|
|
||||||
import ReportPage from 'page/report.js';
|
import ReportPage from 'page/report.js';
|
||||||
import StartPage from 'page/start.js';
|
import StartPage from 'page/start.js';
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from 'page/wallet';
|
||||||
import DetailPage from 'page/show.js';
|
import DetailPage from 'page/show.js';
|
||||||
import PublishPage from 'page/publish.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 SplashScreen from 'component/splash.js';
|
||||||
import DeveloperPage from 'page/developer.js';
|
import DeveloperPage from 'page/developer.js';
|
||||||
import {
|
import {
|
||||||
|
@ -30,7 +29,6 @@ const Router = (props) => {
|
||||||
return route(currentPage, {
|
return route(currentPage, {
|
||||||
'settings': <SettingsPage {...props} />,
|
'settings': <SettingsPage {...props} />,
|
||||||
'help': <HelpPage {...props} />,
|
'help': <HelpPage {...props} />,
|
||||||
'watch': <WatchPage {...props} />,
|
|
||||||
'report': <ReportPage {...props} />,
|
'report': <ReportPage {...props} />,
|
||||||
'downloaded': <FileListDownloaded {...props} />,
|
'downloaded': <FileListDownloaded {...props} />,
|
||||||
'published': <FileListPublished {...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_STARTED = 'SEND_TRANSACTION_STARTED'
|
||||||
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'
|
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'
|
||||||
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED'
|
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 React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbryio from 'lbryio.js';
|
||||||
import lighthouse from '../lighthouse.js';
|
import FileTile from 'component/fileTile';
|
||||||
import {FileTile} from '../component/file-tile.js';
|
import { FileTileStream } from 'component/fileTileStream'
|
||||||
import Link from 'component/link';
|
import {ToolTip} from 'component/tooltip.js';
|
||||||
import {ToolTip} from '../component/tooltip.js';
|
|
||||||
|
|
||||||
const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' +
|
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 ' +
|
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
|
||||||
'"five" to put your content here!');
|
'"five" to put your content here!');
|
||||||
|
|
||||||
let FeaturedCategory = React.createClass({
|
const FeaturedCategory = (props) => {
|
||||||
render: function() {
|
const {
|
||||||
return (<div className="card-row card-row--small">
|
category,
|
||||||
{ this.props.category ?
|
names,
|
||||||
<h3 className="card-row__header">{this.props.category}
|
} = props
|
||||||
{ this.props.category.match(/^community/i) ?
|
|
||||||
<ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header"/>
|
return <div className="card-row card-row--small">
|
||||||
: '' }</h3>
|
<h3 className="card-row__header">{category}
|
||||||
: '' }
|
{category && category.match(/^community/i) && <ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header" />}
|
||||||
{ this.props.names.map((name) => { return <FileTile key={name} displayStyle="card" uri={name} /> }) }
|
</h3>
|
||||||
</div>)
|
{names.map(name => <FileTile key={name} displayStyle="card" uri={name} />)}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
let DiscoverPage = React.createClass({
|
let DiscoverPage = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
|
@ -1,14 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from 'lbry.js';
|
||||||
import lbryuri from '../lbryuri.js';
|
import lbryuri from 'lbryuri.js';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
import FormField from '../component/form.js';
|
import {FormField} from 'component/form.js';
|
||||||
import {SubHeader} from '../component/header.js';
|
import SubHeader from '../component/sub-header';
|
||||||
import {FileTileStream} from '../component/file-tile.js';
|
import {FileTileStream} from 'component/fileTile';
|
||||||
import rewards from '../rewards.js';
|
import rewards from 'rewards.js';
|
||||||
import lbryio from '../lbryio.js';
|
import lbryio from 'lbryio.js';
|
||||||
import {BusyMessage, Thumbnail} from '../component/common.js';
|
import {BusyMessage, Thumbnail} from 'component/common.js';
|
||||||
|
|
||||||
|
|
||||||
export let FileListNav = React.createClass({
|
export let FileListNav = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbryio from '../lbryio.js';
|
import lbryio from '../lbryio.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link';
|
||||||
import Notice from '../component/notice.js';
|
import Notice from '../component/notice.js';
|
||||||
import {CreditAmount} from '../component/common.js';
|
import {CreditAmount} from '../component/common.js';
|
||||||
//
|
//
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {CreditAmount, Icon} from '../component/common.js';
|
||||||
import rewards from '../rewards.js';
|
import rewards from '../rewards.js';
|
||||||
import Modal from '../component/modal.js';
|
import Modal from '../component/modal.js';
|
||||||
import {WalletNav} from './wallet.js';
|
import {WalletNav} from './wallet.js';
|
||||||
import {RewardLink} from '../component/link.js';
|
import {RewardLink} from '../component/link';
|
||||||
|
|
||||||
const RewardTile = React.createClass({
|
const RewardTile = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import lbryio from '../lbryio.js';
|
||||||
import lbryuri from '../lbryuri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import lighthouse from '../lighthouse.js';
|
import lighthouse from '../lighthouse.js';
|
||||||
import {FileTile, FileTileStream} from '../component/file-tile.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 {ToolTip} from '../component/tooltip.js';
|
||||||
import {BusyMessage} from '../component/common.js';
|
import {BusyMessage} from '../component/common.js';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormField, FormRow} from '../component/form.js';
|
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';
|
import lbry from '../lbry.js';
|
||||||
|
|
||||||
export let SettingsNav = React.createClass({
|
export let SettingsNav = React.createClass({
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import lighthouse from '../lighthouse.js';
|
import lighthouse from '../lighthouse.js';
|
||||||
import lbryuri from '../lbryuri.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 {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js';
|
||||||
import {FileActions} from '../component/file-actions.js';
|
import {FileActions} from '../component/file-actions.js';
|
||||||
import UriIndicator from '../component/channel-indicator.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
|
createLogger
|
||||||
} from 'redux-logger'
|
} from 'redux-logger'
|
||||||
import appReducer from 'reducers/app';
|
import appReducer from 'reducers/app';
|
||||||
|
import contentReducer from 'reducers/content';
|
||||||
import walletReducer from 'reducers/wallet'
|
import walletReducer from 'reducers/wallet'
|
||||||
|
|
||||||
function isFunction(object) {
|
function isFunction(object) {
|
||||||
|
@ -18,6 +19,7 @@ function isNotFunction(object) {
|
||||||
|
|
||||||
const reducers = redux.combineReducers({
|
const reducers = redux.combineReducers({
|
||||||
app: appReducer,
|
app: appReducer,
|
||||||
|
content: contentReducer,
|
||||||
wallet: walletReducer,
|
wallet: walletReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,16 @@ import {
|
||||||
shouldFetchTransactions,
|
shouldFetchTransactions,
|
||||||
shouldGetReceiveAddress,
|
shouldGetReceiveAddress,
|
||||||
} from 'selectors/wallet'
|
} from 'selectors/wallet'
|
||||||
|
import {
|
||||||
|
shouldFetchFeaturedContent,
|
||||||
|
} from 'selectors/content'
|
||||||
import {
|
import {
|
||||||
doFetchTransactions,
|
doFetchTransactions,
|
||||||
doGetNewAddress,
|
doGetNewAddress,
|
||||||
} from 'actions/wallet'
|
} from 'actions/wallet'
|
||||||
|
import {
|
||||||
|
doFetchFeaturedContent,
|
||||||
|
} from 'actions/content'
|
||||||
|
|
||||||
const triggers = []
|
const triggers = []
|
||||||
|
|
||||||
|
@ -19,6 +25,11 @@ triggers.push({
|
||||||
action: doGetNewAddress
|
action: doGetNewAddress
|
||||||
})
|
})
|
||||||
|
|
||||||
|
triggers.push({
|
||||||
|
selector: shouldFetchFeaturedContent,
|
||||||
|
action: doFetchFeaturedContent,
|
||||||
|
})
|
||||||
|
|
||||||
const runTriggers = function() {
|
const runTriggers = function() {
|
||||||
triggers.forEach(function(trigger) {
|
triggers.forEach(function(trigger) {
|
||||||
const state = app.store.getState();
|
const state = app.store.getState();
|
||||||
|
|
Loading…
Reference in a new issue