Featured content
This commit is contained in:
parent
cb5067dce3
commit
393aa9129c
9 changed files with 219 additions and 274 deletions
|
@ -2,6 +2,29 @@ import * as types from 'constants/action_types'
|
|||
import lbry from 'lbry'
|
||||
import lbryio from 'lbryio';
|
||||
|
||||
export function doResolveUri(dispatch, uri) {
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_STARTED,
|
||||
data: { uri }
|
||||
})
|
||||
|
||||
lbry.resolve({uri: uri}).then((resolutionInfo) => {
|
||||
const {
|
||||
claim,
|
||||
certificate,
|
||||
} = resolutionInfo
|
||||
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
claim,
|
||||
certificate,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function doFetchFeaturedContent() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
|
@ -18,6 +41,10 @@ export function doFetchFeaturedContent() {
|
|||
uris: Uris,
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(Uris).forEach((category) => {
|
||||
Uris[category].forEach((uri) => doResolveUri(dispatch, uri))
|
||||
})
|
||||
}
|
||||
|
||||
const failure = () => {
|
||||
|
|
7
ui/js/component/fileCardStream/index.js
Normal file
7
ui/js/component/fileCardStream/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import React from 'react'
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import FileCardStream from './view'
|
||||
|
||||
export default connect()(FileCardStream)
|
110
ui/js/component/fileCardStream/view.jsx
Normal file
110
ui/js/component/fileCardStream/view.jsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
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';
|
||||
|
||||
const FileCardStream = React.createClass({
|
||||
_fileInfoSubscribeId: null,
|
||||
_isMounted: null,
|
||||
_metadata: null,
|
||||
|
||||
|
||||
propTypes: {
|
||||
uri: React.PropTypes.string,
|
||||
claimInfo: React.PropTypes.object,
|
||||
outpoint: React.PropTypes.string,
|
||||
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() {
|
||||
this.setState({
|
||||
hovered: true,
|
||||
});
|
||||
},
|
||||
handleMouseOut: function() {
|
||||
this.setState({
|
||||
hovered: 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;
|
||||
const primaryUrl = '?show=' + uri;
|
||||
return (
|
||||
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<div className="card__inner">
|
||||
<a href={primaryUrl} className="card__link">
|
||||
<div className="card__title-identity">
|
||||
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
|
||||
<div className="card__subtitle">
|
||||
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span> : null}
|
||||
<UriIndicator uri={uri} metadata={metadata} contentType={this.props.contentType}
|
||||
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card__media" style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}></div>
|
||||
<div className="card__content card__subtext card__subtext--two-lines">
|
||||
<TruncatedText lines={2}>
|
||||
{isConfirmed
|
||||
? metadata.description
|
||||
: <span className="empty">This file is pending confirmation.</span>}
|
||||
</TruncatedText>
|
||||
</div>
|
||||
</a>
|
||||
{this.state.showNsfwHelp && this.state.hovered
|
||||
? <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}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default FileCardStream
|
|
@ -2,9 +2,13 @@ import React from 'react'
|
|||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
selectResolvedUris,
|
||||
} from 'selectors/content'
|
||||
import FileTile from './view'
|
||||
|
||||
const select = (state) => ({
|
||||
resolvedUris: selectResolvedUris(state),
|
||||
})
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -2,283 +2,30 @@ 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';
|
||||
import FileCardStream from 'component/fileCardStream'
|
||||
import FileTileStream from 'component/fileTileStream'
|
||||
import FileActions from 'component/fileActions';
|
||||
|
||||
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
||||
export let FileTileStream = React.createClass({
|
||||
_fileInfoSubscribeId: null,
|
||||
_isMounted: null,
|
||||
class FileTile extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
displayStyle,
|
||||
uri,
|
||||
claim,
|
||||
} = this.props
|
||||
|
||||
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,
|
||||
if(!claim) {
|
||||
if (displayStyle == 'card') {
|
||||
return <FileCardStream uri={uri} />
|
||||
}
|
||||
},
|
||||
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;
|
||||
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;
|
||||
const primaryUrl = "?show=" + uri;
|
||||
return (
|
||||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<a href={primaryUrl} className="card__link">
|
||||
<div className={"card__inner file-tile__row"}>
|
||||
<div className="card__media"
|
||||
style={{ backgroundImage: "url('" + (metadata && metadata.thumbnail ? metadata.thumbnail : lbry.imagePath('default-thumb.svg')) + "')" }}>
|
||||
</div>
|
||||
<div className="file-tile__content">
|
||||
<div className="card__title-primary">
|
||||
{ !this.props.hidePrice
|
||||
? <FilePrice uri={this.props.uri} />
|
||||
: null}
|
||||
<div className="meta">{uri}</div>
|
||||
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
|
||||
</div>
|
||||
<div className="card__content card__subtext">
|
||||
<TruncatedText lines={3}>
|
||||
{isConfirmed
|
||||
? metadata.description
|
||||
: <span className="empty">This file is pending confirmation.</span>}
|
||||
</TruncatedText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{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>
|
||||
);
|
||||
return displayStyle == 'card' ?
|
||||
<FileCardStream uri={uri} />
|
||||
:
|
||||
<FileTileStream uri={uri} key={uri} />
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export let FileCardStream = React.createClass({
|
||||
_fileInfoSubscribeId: null,
|
||||
_isMounted: null,
|
||||
_metadata: null,
|
||||
|
||||
|
||||
propTypes: {
|
||||
uri: React.PropTypes.string,
|
||||
claimInfo: React.PropTypes.object,
|
||||
outpoint: React.PropTypes.string,
|
||||
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() {
|
||||
this.setState({
|
||||
hovered: true,
|
||||
});
|
||||
},
|
||||
handleMouseOut: function() {
|
||||
this.setState({
|
||||
hovered: 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;
|
||||
const primaryUrl = '?show=' + uri;
|
||||
return (
|
||||
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<div className="card__inner">
|
||||
<a href={primaryUrl} className="card__link">
|
||||
<div className="card__title-identity">
|
||||
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
|
||||
<div className="card__subtitle">
|
||||
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span> : null}
|
||||
<UriIndicator uri={uri} metadata={metadata} contentType={this.props.contentType}
|
||||
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card__media" style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}></div>
|
||||
<div className="card__content card__subtext card__subtext--two-lines">
|
||||
<TruncatedText lines={2}>
|
||||
{isConfirmed
|
||||
? metadata.description
|
||||
: <span className="empty">This file is pending confirmation.</span>}
|
||||
</TruncatedText>
|
||||
</div>
|
||||
</a>
|
||||
{this.state.showNsfwHelp && this.state.hovered
|
||||
? <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}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let FileTile = React.createClass({
|
||||
_isMounted: false,
|
||||
_isResolvePending: false,
|
||||
|
||||
propTypes: {
|
||||
uri: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
outpoint: null,
|
||||
claimInfo: null
|
||||
}
|
||||
},
|
||||
resolve: function(uri) {
|
||||
this._isResolvePending = true;
|
||||
lbry.resolve({uri: uri}).then((resolutionInfo) => {
|
||||
this._isResolvePending = false;
|
||||
if (this._isMounted && resolutionInfo && resolutionInfo.claim && resolutionInfo.claim.value &&
|
||||
resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) {
|
||||
// In case of a failed lookup, metadata will be null, in which case the component will never display
|
||||
this.setState({
|
||||
claimInfo: resolutionInfo.claim,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.uri != this.props.uri) {
|
||||
this.setState(this.getInitialState());
|
||||
this.resolve(nextProps.uri);
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this._isMounted = true;
|
||||
this.resolve(this.props.uri);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
this._isMounted = false;
|
||||
},
|
||||
render: function() {
|
||||
if (!this.state.claimInfo) {
|
||||
if (this.props.displayStyle == 'card') {
|
||||
return <FileCardStream outpoint={null} metadata={{title: this.props.uri, description: "Loading..."}} contentType={null} hidePrice={true}
|
||||
hasSignature={false} signatureIsValid={false} uri={this.props.uri} />
|
||||
}
|
||||
if (this.props.showEmpty)
|
||||
{
|
||||
return this._isResolvePending ?
|
||||
<BusyMessage message="Loading magic decentralized data" /> :
|
||||
<div className="empty">{lbryuri.normalize(this.props.uri)} is unclaimed. <Link label="Put something here" href="?publish" /></div>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const {txid, nout, has_signature, signature_is_valid,
|
||||
value: {stream: {metadata, source: {contentType}}}} = this.state.claimInfo;
|
||||
|
||||
return this.props.displayStyle == 'card' ?
|
||||
<FileCardStream outpoint={txid + ':' + nout} metadata={metadata} contentType={contentType}
|
||||
hasSignature={has_signature} signatureIsValid={signature_is_valid} {... this.props}/> :
|
||||
<FileTileStream outpoint={txid + ':' + nout} metadata={metadata} contentType={contentType}
|
||||
hasSignature={has_signature} signatureIsValid={signature_is_valid} {... this.props} />;
|
||||
}
|
||||
});
|
||||
export default FileTile
|
|
@ -37,3 +37,5 @@ 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'
|
||||
export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED'
|
||||
export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED'
|
||||
|
|
|
@ -11,6 +11,7 @@ const communityCategoryToolTipText = ('Community Content is a public space where
|
|||
const FeaturedCategory = (props) => {
|
||||
const {
|
||||
category,
|
||||
resolvedUris,
|
||||
names,
|
||||
} = props
|
||||
|
||||
|
|
|
@ -24,6 +24,48 @@ reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) {
|
|||
})
|
||||
}
|
||||
|
||||
reducers[types.RESOLVE_URI_STARTED] = function(state, action) {
|
||||
const {
|
||||
uri
|
||||
} = action.data
|
||||
|
||||
const oldResolving = state.resolvingUris || []
|
||||
const newResolving = Object.assign([], oldResolving)
|
||||
if (newResolving.indexOf(uri) == -1) newResolving.push(uri)
|
||||
|
||||
return Object.assign({}, state, {
|
||||
resolvingUris: newResolving
|
||||
})
|
||||
}
|
||||
|
||||
reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) {
|
||||
const {
|
||||
uri,
|
||||
claim,
|
||||
certificate,
|
||||
} = action.data
|
||||
const resolvedUris = Object.assign({}, state.resolvedUris)
|
||||
const resolvingUris = state.resolvingUris
|
||||
const index = state.resolvingUris.indexOf(uri)
|
||||
const newResolvingUris = [
|
||||
...resolvingUris.slice(0, index),
|
||||
...resolvingUris.slice(index + 1)
|
||||
]
|
||||
|
||||
resolvedUris[uri] = {
|
||||
claim: claim,
|
||||
certificate: certificate,
|
||||
}
|
||||
|
||||
|
||||
const newState = Object.assign({}, state, {
|
||||
resolvedUris: resolvedUris,
|
||||
resolvingUris: newResolvingUris,
|
||||
})
|
||||
|
||||
return Object.assign({}, state, newState)
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
|
|
|
@ -35,3 +35,8 @@ export const shouldFetchFeaturedContent = createSelector(
|
|||
return true
|
||||
}
|
||||
)
|
||||
|
||||
export const selectResolvedUris = createSelector(
|
||||
_selectState,
|
||||
(state) => state.resolvedUris || {}
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue