Merge branch 'redux' into es6-classes

This commit is contained in:
Jeremy Kauffman 2017-05-19 09:17:41 -04:00
commit 209db5e3d0
42 changed files with 574 additions and 812 deletions

View file

@ -23,8 +23,8 @@ Web UI version numbers should always match the corresponding version of LBRY App
* Enable windows code signing of binary
### Changed
*
*
* Use directory selector instead of ugly text box on Settings page
* Use Electron's native file selector everywhere instead of WebKit file selector
### Fixed
* Error modals now display full screen properly

View file

@ -43,7 +43,6 @@ export function doChangePath(path) {
path,
}
})
}
}
@ -58,6 +57,7 @@ export function doHistoryPush(params, title, relativeUrl) {
let pathParts = window.location.pathname.split('/')
pathParts[pathParts.length - 1] = relativeUrl.replace(/^\//, '')
const url = pathParts.join('/')
title += " - LBRY"
history.pushState(params, title, url)
window.document.title = title
}

View file

@ -8,7 +8,7 @@ import {
} from 'selectors/wallet'
import {
selectFileInfoForUri,
selectDownloadingByUri,
selectUrisDownloading,
} from 'selectors/file_info'
import {
selectResolvingUris
@ -57,66 +57,9 @@ export function doResolveUri(uri) {
export function doCancelResolveUri(uri) {
return function(dispatch, getState) {
lbry.cancelResolve({ uri })
}
}
export function doFetchDownloadedContent() {
return function(dispatch, getState) {
const state = getState()
dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_STARTED,
})
lbry.claim_list_mine().then((myClaimInfos) => {
lbry.file_list().then((fileInfos) => {
const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout);
fileInfos.forEach(fileInfo => {
const uri = lbryuri.build({
channelName: fileInfo.channel_name,
contentName: fileInfo.name,
})
const claim = selectClaimsByUri(state)[uri]
if (!claim) dispatch(doResolveUri(uri))
})
dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED,
data: {
fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)),
}
})
});
});
}
}
export function doFetchPublishedContent() {
return function(dispatch, getState) {
const state = getState()
dispatch({
type: types.FETCH_PUBLISHED_CONTENT_STARTED,
})
lbry.claim_list_mine().then((claimInfos) => {
dispatch({
type: types.FETCH_MY_CLAIMS_COMPLETED,
data: {
claims: claimInfos,
}
})
lbry.file_list().then((fileInfos) => {
const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout)
dispatch({
type: types.FETCH_PUBLISHED_CONTENT_COMPLETED,
data: {
fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)),
}
})
})
type: types.RESOLVE_URI_CANCELED,
data: { uri }
})
}
}
@ -138,6 +81,14 @@ export function doFetchFeaturedUris() {
featuredUris[category] = Uris[category]
}
})
//
// dispatch({
// type: types.FETCH_FEATURED_CONTENT_COMPLETED,
// data: {
// categories: ["FOO"],
// uris: { FOO: ["lbry://gtasoc"]},
// }
// })
dispatch({
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
@ -182,6 +133,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
type: types.DOWNLOADING_COMPLETED,
data: {
uri,
outpoint,
fileInfo,
}
})
@ -197,6 +149,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
type: types.DOWNLOADING_PROGRESSED,
data: {
uri,
outpoint,
fileInfo,
progress,
}
@ -216,6 +169,7 @@ export function doDownloadFile(uri, streamInfo) {
type: types.DOWNLOADING_STARTED,
data: {
uri,
outpoint: streamInfo.outpoint,
fileInfo,
}
})
@ -265,7 +219,7 @@ export function doPurchaseUri(uri) {
const balance = selectBalance(state)
const fileInfo = selectFileInfoForUri(state, { uri })
const costInfo = selectCostInfoForUri(state, { uri })
const downloadingByUri = selectDownloadingByUri(state)
const downloadingByUri = selectUrisDownloading(state)
const alreadyDownloading = !!downloadingByUri[uri]
const { cost } = costInfo
@ -314,18 +268,28 @@ export function doFetchClaimsByChannel(uri) {
} = resolutionInfo ? resolutionInfo : { claims_in_channel: [] }
dispatch({
type: types.FETCH_CHANNEL_CLAIMS_STARTED,
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
data: {
uri,
claims: claims_in_channel
}
})
}).catch(() => {
})
}
}
export function doClaimListMine() {
return function(dispatch, getState) {
dispatch({
type: types.FETC,
type: types.CLAIM_LIST_MINE_STARTED
})
lbry.claim_list_mine().then((claims) => {
dispatch({
type: types.CLAIM_LIST_MINE_COMPLETED,
data: {
uri,
claims: []
claims
}
})
})

View file

@ -1,6 +1,12 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import lbryio from 'lbryio'
import {
doResolveUri
} from 'actions/content'
import {
selectResolvingUris,
} from 'selectors/content'
import {
selectClaimsByUri
} from 'selectors/claims'
@ -12,21 +18,23 @@ export function doFetchCostInfoForUri(uri) {
return function(dispatch, getState) {
const state = getState(),
claim = selectClaimsByUri(state)[uri],
isResolving = selectResolvingUris(state).indexOf(uri) !== -1,
isGenerous = selectSettingsIsGenerous(state)
//
// function getCostGenerous(uri) {
// console.log('get cost generous: ' + uri)
// // If generous is on, the calculation is simple enough that we might as well do it here in the front end
// lbry.resolve({uri: uri}).then((resolutionInfo) => {
// console.log('resolve inside getCostGenerous ' + uri)
// console.log(resolutionInfo)
// if (!resolutionInfo) {
// return reject(new Error("Unused URI"));
// }
if (claim === null) { //claim doesn't exist, nothing to fetch a cost for
return
}
if (!claim) {
setTimeout(() => {
dispatch(doFetchCostInfoForUri(uri))
}, 1000)
if (!isResolving) {
dispatch(doResolveUri(uri))
}
return
}
// });
// }
function begin() {
dispatch({

View file

@ -1,10 +1,16 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import {
doClaimListMine
} from 'actions/content'
import {
selectClaimsByUri,
selectClaimListMineIsPending,
} from 'selectors/claims'
import {
selectLoadingByUri,
selectFileListIsPending,
selectAllFileInfos,
selectUrisLoading,
} from 'selectors/file_info'
import {
doCloseModal,
@ -19,23 +25,45 @@ export function doFetchFileInfo(uri) {
const state = getState()
const claim = selectClaimsByUri(state)[uri]
const outpoint = claim ? `${claim.txid}:${claim.nout}` : null
const alreadyFetching = !!selectLoadingByUri(state)[uri]
const alreadyFetching = !!selectUrisLoading(state)[uri]
if (!alreadyFetching) {
dispatch({
type: types.FETCH_FILE_INFO_STARTED,
data: {
uri,
outpoint,
}
})
lbry.file_list({outpoint: outpoint, full_status: true}).then(([fileInfo]) => {
lbry.file_list({outpoint: outpoint, full_status: true}).then(fileInfos => {
dispatch({
type: types.FETCH_FILE_INFO_COMPLETED,
data: {
uri,
fileInfo,
outpoint,
fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null,
}
})
})
}
}
}
export function doFileList() {
return function(dispatch, getState) {
const state = getState()
const isPending = selectFileListIsPending(state)
if (!isPending) {
dispatch({
type: types.FILE_LIST_STARTED,
})
lbry.file_list().then((fileInfos) => {
dispatch({
type: types.FILE_LIST_COMPLETED,
data: {
fileInfos,
}
})
})
@ -55,51 +83,39 @@ export function doOpenFileInFolder(fileInfo) {
}
}
export function doDeleteFile(uri, fileInfo, deleteFromComputer) {
export function doDeleteFile(outpoint, deleteFromComputer) {
return function(dispatch, getState) {
dispatch({
type: types.DELETE_FILE_STARTED,
type: types.FILE_DELETE,
data: {
uri,
fileInfo,
deleteFromComputer,
outpoint
}
})
const successCallback = () => {
dispatch({
type: types.DELETE_FILE_COMPLETED,
data: {
uri,
}
lbry.file_delete({
outpoint: outpoint,
delete_target_file: deleteFromComputer,
})
dispatch(doCloseModal())
}
lbry.removeFile(fileInfo.outpoint, deleteFromComputer, successCallback)
}
}
export function doFetchDownloadedContent() {
export function doFetchFileInfosAndPublishedClaims() {
return function(dispatch, getState) {
const state = getState()
const state = getState(),
isClaimListMinePending = selectClaimListMineIsPending(state),
isFileInfoListPending = selectFileListIsPending(state)
dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_STARTED,
})
lbry.claim_list_mine().then((myClaimInfos) => {
lbry.file_list().then((fileInfos) => {
const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout);
dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED,
data: {
fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)),
if (isClaimListMinePending === undefined) {
dispatch(doClaimListMine())
}
if (isFileInfoListPending === undefined) {
dispatch(doFileList())
}
})
});
});
}
}

View file

@ -217,7 +217,7 @@ class CodeRequiredStage extends React.Component {
})
if (!this.state.address) {
lbry.getUnusedAddress((address) => {
lbry.wallet_unused_address().then((address) => {
setLocal('wallet_address', address);
this.setState({ address: address });
});

View file

@ -0,0 +1,58 @@
import React from 'react';
const {remote} = require('electron');
class FileSelector extends React.Component {
static propTypes = {
type: React.PropTypes.oneOf(['file', 'directory']),
initPath: React.PropTypes.string,
onFileChosen: React.PropTypes.func,
}
static defaultProps = {
type: 'file',
}
componentWillMount() {
this.setState({
path: this.props.initPath || null,
});
}
handleButtonClick() {
remote.dialog.showOpenDialog({
properties: [this.props.type == 'file' ? 'openFile' : 'openDirectory'],
}, (paths) => {
if (!paths) { // User hit cancel, so do nothing
return;
}
const path = paths[0];
this.setState({
path: path,
});
if (this.props.onFileChosen) {
this.props.onFileChosen(path);
}
});
}
render() {
return (
<div className="file-selector">
<button type="button" className="file-selector__choose-button" onClick={() => this.handleButtonClick()}>
{this.props.type == 'file' ?
'Choose File' :
'Choose Directory'}
</button>
{' '}
<span className="file-selector__path">
{this.state.path ?
this.state.path :
'No File Chosen'}
</span>
</div>
);
}
};
export default FileSelector;

View file

@ -19,6 +19,7 @@ import {
import {
doCloseModal,
doOpenModal,
doHistoryBack,
} from 'actions/app'
import {
doFetchAvailability
@ -55,7 +56,10 @@ const perform = (dispatch) => ({
closeModal: () => dispatch(doCloseModal()),
openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)),
openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)),
deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)),
deleteFile: (fileInfo, deleteFromComputer) => {
dispatch(doHistoryBack())
dispatch(doDeleteFile(fileInfo, deleteFromComputer))
},
openModal: (modal) => dispatch(doOpenModal(modal)),
startDownload: (uri) => dispatch(doPurchaseUri(uri)),
loadVideo: (uri) => dispatch(doLoadVideo(uri))

View file

@ -72,10 +72,19 @@ class FileActions extends React.Component {
let content
console.log('file actions render')
console.log(this.props)
if (downloading) {
if (!fileInfo && isAvailable === undefined) {
const
progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
content = <div className="faux-button-block file-actions__download-status-bar button-set-item">
<div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div>
{labelWithIcon}
</div>
} else if (!fileInfo && isAvailable === undefined) {
content = <BusyMessage message="Checking availability" />
@ -93,18 +102,6 @@ class FileActions extends React.Component {
content = <Link button="text" label="Download" icon="icon-download" onClick={() => { startDownload(uri) } } />;
} else if (downloading) {
const
progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
content = <div className="faux-button-block file-actions__download-status-bar button-set-item">
<div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div>
{labelWithIcon}
</div>
} else if (fileInfo && fileInfo.download_path) {
content = <Link label="Open" button="text" icon="icon-folder-open" onClick={() => openInShell(fileInfo)} />;
} else {
@ -136,7 +133,7 @@ class FileActions extends React.Component {
contentLabel="Not enough credits"
type="confirm"
confirmButtonLabel="Remove"
onConfirmed={() => deleteFile(uri, fileInfo, deleteChecked)}
onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
onAborted={closeModal}>
<p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p>

View file

@ -34,7 +34,6 @@ const makeSelect = () => {
claim: selectClaimForUri(state, props),
fileInfo: selectFileInfoForUri(state, props),
obscureNsfw: selectObscureNsfw(state),
hasSignature: false,
metadata: selectMetadataForUri(state, props),
isResolvingUri: selectResolvingUri(state, props),
})

View file

@ -2,12 +2,12 @@ import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {Thumbnail, TruncatedText,} from 'component/common';
import {Thumbnail, TruncatedText, Icon} from 'component/common';
import FilePrice from 'component/filePrice'
import UriIndicator from 'component/uriIndicator';
class FileCard extends React.Component {
componentDidMount() {
componentWillMount() {
this.resolve(this.props)
}
@ -29,7 +29,15 @@ class FileCard extends React.Component {
}
componentWillUnmount() {
this.props.cancelResolveUri(this.props.uri)
const {
isResolvingUri,
cancelResolveUri,
uri
} = this.props
if (isResolvingUri) {
cancelResolveUri(uri)
}
}
handleMouseOver() {
@ -47,6 +55,8 @@ class FileCard extends React.Component {
render() {
const {
claim,
fileInfo,
metadata,
isResolvingUri,
navigate,
@ -61,6 +71,8 @@ class FileCard extends React.Component {
description = "Loading..."
} else if (metadata && metadata.description) {
description = metadata.description
} else if (claim === null) {
description = 'This address contains no content.'
}
return (
@ -70,7 +82,10 @@ class FileCard extends React.Component {
<div className="card__title-identity">
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
<div className="card__subtitle">
{ !isResolvingUri && <span style={{float: "right"}}><FilePrice uri={uri} /></span> }
<span style={{float: "right"}}>
<FilePrice uri={uri} />
{ fileInfo ? <span>{' '}<Icon fixed icon="icon-folder" /></span> : '' }
</span>
<UriIndicator uri={uri} />
</div>
</div>

View file

@ -60,7 +60,6 @@ class FileList extends React.Component {
handleSortChanged,
fetching,
fileInfos,
hidePrices,
} = this.props
const {
sortBy,
@ -72,7 +71,7 @@ class FileList extends React.Component {
contentName: fileInfo.name,
channelName: fileInfo.channel_name,
})
content.push(<FileTile key={uri} uri={uri} hidePrice={hidePrices} hideOnRemove={true} showEmpty={""} />)
content.push(<FileTile key={uri} uri={uri} hidePrice={true} showEmpty={this.props.fileTileShowEmpty} />)
})
return (
<section className="file-list__header">

View file

@ -12,6 +12,7 @@ import FilePrice from './view'
const makeSelect = () => {
const selectCostInfoForUri = makeSelectCostInfoForUri()
const select = (state, props) => ({
costInfo: selectCostInfoForUri(state, props),
})

View file

@ -4,7 +4,7 @@ import {
} from 'component/common'
class FilePrice extends React.Component{
componentDidMount() {
componentWillMount() {
this.fetchCost(this.props)
}

View file

@ -13,11 +13,8 @@ class FileTile extends React.Component {
constructor(props) {
super(props)
this._fileInfoSubscribeId = null
this._isMounted = null
this.state = {
showNsfwHelp: false,
isHidden: false,
}
}
@ -29,31 +26,11 @@ class FileTile extends React.Component {
uri,
} = this.props
this._isMounted = true;
if (this.props.hideOnRemove) {
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
}
if(!isResolvingUri && !claim && uri) {
resolveUri(uri)
}
}
componentWillUnmount() {
if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
}
}
onFileInfoUpdate(fileInfo) {
if (!fileInfo && this._isMounted && this.props.hideOnRemove) {
this.setState({
isHidden: true
});
}
}
handleMouseOver() {
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
this.setState({
@ -71,10 +48,6 @@ class FileTile extends React.Component {
}
render() {
if (this.state.isHidden) {
return null;
}
const {
claim,
metadata,

View file

@ -1,7 +1,9 @@
import React from 'react';
import FileSelector from './file-selector.js';
import {Icon} from './common.js';
var formFieldCounter = 0,
formFieldFileSelectorTypes = ['file', 'directory'],
formFieldNestedLabelTypes = ['radio', 'checkbox'];
function formFieldId() {
@ -36,12 +38,34 @@ export class FormField extends React.Component {
} else if (this.props.type == 'text-number') {
this._element = 'input';
this._type = 'text';
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
this._element = 'input';
this._type = 'hidden';
} else {
// Non <input> field, e.g. <select>, <textarea>
this._element = this.props.type;
}
}
componentDidMount() {
/**
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
* https://github.com/facebook/react/issues/3468
*/
if (this.props.type == 'directory') {
this.refs.field.webkitdirectory = true;
}
}
handleFileChosen(path) {
this.refs.field.value = path;
if (this.props.onChange) { // Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event('change', {bubbles: true})
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
this.props.onChange(event);
}
}
showError(text) {
this.setState({
isError: true,
@ -56,9 +80,6 @@ export class FormField extends React.Component {
getValue() {
if (this.props.type == 'checkbox') {
return this.refs.field.checked;
} else if (this.props.type == 'file') {
return this.refs.field.files.length && this.refs.field.files[0].path ?
this.refs.field.files[0].path : null;
} else {
return this.refs.field.value;
}
@ -96,6 +117,10 @@ export class FormField extends React.Component {
{this.props.label}
</label> :
element }
{ formFieldFileSelectorTypes.includes(this.props.type) ?
<FileSelector type={this.props.type} onFileChosen={this.handleFileChosen}
{... this.props.defaultValue ? {initPath: this.props.defaultValue} : {}} /> :
null }
{ this.props.postfix ? <span className="form-field__postfix">{this.props.postfix}</span> : '' }
{ isError && this.state.errorMessage ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
</div>

View file

@ -4,14 +4,13 @@ import Link from 'component/link';
import Modal from 'component/modal';
class VideoPlayButton extends React.Component {
confirmPurchaseClick() {
onPurchaseConfirmed() {
this.props.closeModal()
this.props.startPlaying()
this.props.loadVideo(this.props.uri)
}
onWatchClick() {
console.log(this.props)
this.props.purchaseUri(this.props.uri).then(() => {
if (!this.props.modal) {
this.props.startPlaying()
@ -24,7 +23,6 @@ class VideoPlayButton extends React.Component {
button,
label,
className,
onWatchClick,
metadata,
metadata: {
title,
@ -51,7 +49,7 @@ class VideoPlayButton extends React.Component {
label={label ? label : ""}
className="video__play-button"
icon="icon-play"
onClick={() => { this.onWatchClick() }} />
onClick={this.onWatchClick.bind(this)} />
{modal}
<Modal contentLabel="Not enough credits" isOpen={modal == 'notEnoughCredits'} onConfirmed={() => { this.closeModal() }}>
You don't have enough LBRY credits to pay for this stream.
@ -60,7 +58,7 @@ class VideoPlayButton extends React.Component {
type="confirm"
isOpen={modal == 'affirmPurchase'}
contentLabel="Confirm Purchase"
onConfirmed={this.confirmPurchaseClick.bind(this)}
onConfirmed={this.onPurchaseConfirmed.bind(this)}
onAborted={closeModal}>
This will purchase <strong>{title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits.
</Modal>
@ -108,8 +106,8 @@ class Video extends React.Component {
}
return (
<div className={"video " + this.props.className + (isPlaying || isReadyToPlay ? " video--active" : " video--hidden")}>{
isPlaying || isReadyToPlay ?
<div className={"video " + this.props.className + (isPlaying ? " video--active" : " video--hidden")}>{
isPlaying || isLoading ?
(!isReadyToPlay ?
<span>this is the world's worst loading screen and we shipped our software with it anyway... <br /><br />{loadStatusMessage}</span> :
<VideoPlayer poster={metadata.thumbnail} autoplay={isPlaying} downloadPath={fileInfo.download_path} />) :
@ -142,11 +140,9 @@ class VideoPlayer extends React.Component {
poster,
} = this.props
//<source src={downloadPath} type={contentType} />
return (
<video controls id="video" ref="video" style={{backgroundImage: "url('" + poster + "')"}} >
<source src={downloadPath} type={contentType} />
</video>
)
}

View file

@ -35,12 +35,13 @@ 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'
export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED'
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'
export const FETCH_DOWNLOADED_CONTENT_STARTED = 'FETCH_DOWNLOADED_CONTENT_STARTED'
export const FETCH_DOWNLOADED_CONTENT_COMPLETED = 'FETCH_DOWNLOADED_CONTENT_COMPLETED'
export const FETCH_PUBLISHED_CONTENT_STARTED = 'FETCH_PUBLISHED_CONTENT_STARTED'
export const FETCH_PUBLISHED_CONTENT_COMPLETED = 'FETCH_PUBLISHED_CONTENT_COMPLETED'
export const CLAIM_LIST_MINE_STARTED = 'CLAIM_LIST_MINE_STARTED'
export const CLAIM_LIST_MINE_COMPLETED = 'CLAIM_LIST_MINE_COMPLETED'
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED'
export const FILE_LIST_COMPLETED = 'FILE_LIST_COMPLETED'
export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED'
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'
@ -54,9 +55,7 @@ export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'
export const DELETE_FILE_STARTED = 'DELETE_FILE_STARTED'
export const DELETE_FILE_COMPLETED = 'DELETE_FILE_COMPLETED'
export const FETCH_MY_CLAIMS_COMPLETED = 'FETCH_MY_CLAIMS_COMPLETED'
export const FILE_DELETE = 'FILE_DELETE'
// Search
export const SEARCH_STARTED = 'SEARCH_STARTED'

View file

@ -7,6 +7,25 @@ import {getLocal, getSession, setSession, setLocal} from './utils.js';
const {remote, ipcRenderer} = require('electron');
const menu = remote.require('./menu/main-menu');
let lbry = {
isConnected: false,
daemonConnectionString: 'http://localhost:5279/lbryapi',
webUiUri: 'http://localhost:5279',
peerListTimeout: 6000,
pendingPublishTimeout: 20 * 60 * 1000,
colors: {
primary: '#155B4A'
},
defaultClientSettings: {
showNsfw: false,
showUnavailable: true,
debug: false,
useCustomLighthouseServers: false,
customLighthouseServers: [],
showDeveloperMenu: false,
}
};
/**
* Records a publish attempt in local storage. Returns a dictionary with all the data needed to
* needed to make a dummy claim or file info object.
@ -40,14 +59,14 @@ function removePendingPublishIfNeeded({name, channel_name, outpoint}) {
return pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name));
}
setLocal('pendingPublishes', getPendingPublishes().filter(pub => !pubMatches(pub)));
setLocal('pendingPublishes', lbry.getPendingPublishes().filter(pub => !pubMatches(pub)));
}
/**
* Gets the current list of pending publish attempts. Filters out any that have timed out and
* removes them from the list.
*/
function getPendingPublishes() {
lbry.getPendingPublishes = function() {
const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout);
setLocal('pendingPublishes', newPendingPublishes);
@ -59,7 +78,7 @@ function getPendingPublishes() {
* provided along withe the name. If no pending publish is found, returns null.
*/
function getPendingPublish({name, channel_name, outpoint}) {
const pendingPublishes = getPendingPublishes();
const pendingPublishes = lbry.getPendingPublishes();
return pendingPublishes.find(
pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name))
) || null;
@ -74,26 +93,6 @@ function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) {
}
window.pptdfi = pendingPublishToDummyFileInfo;
let lbry = {
isConnected: false,
rootPath: '.',
daemonConnectionString: 'http://localhost:5279/lbryapi',
webUiUri: 'http://localhost:5279',
peerListTimeout: 6000,
pendingPublishTimeout: 20 * 60 * 1000,
colors: {
primary: '#155B4A'
},
defaultClientSettings: {
showNsfw: false,
showUnavailable: true,
debug: false,
useCustomLighthouseServers: false,
customLighthouseServers: [],
showDeveloperMenu: false,
}
};
lbry.call = function (method, params, callback, errorCallback, connectFailedCallback) {
return jsonrpc.call(lbry.daemonConnectionString, method, params, callback, errorCallback, connectFailedCallback);
}
@ -151,14 +150,6 @@ lbry.isDaemonAcceptingConnections = function (callback) {
lbry.call('status', {}, () => callback(true), null, () => callback(false))
};
lbry.checkFirstRun = function(callback) {
lbry.call('is_first_run', {}, callback);
}
lbry.getUnusedAddress = function(callback) {
lbry.call('wallet_unused_address', {}, callback);
}
lbry.checkAddressIsMine = function(address, callback) {
lbry.call('address_is_mine', {address: address}, callback);
}
@ -167,108 +158,6 @@ lbry.sendToAddress = function(amount, address, callback, errorCallback) {
lbry.call("send_amount_to_address", { "amount" : amount, "address": address }, callback, errorCallback);
}
lbry.getClaimInfo = function(name, callback) {
if (!name) {
throw new Error(`Name required.`);
}
lbry.call('get_claim_info', { name: name }, callback);
}
lbry.getMyClaim = function(name, callback) {
lbry.call('claim_list_mine', {}, (claims) => {
callback(claims.find((claim) => claim.name == name) || null);
});
}
lbry.getPeersForBlobHash = function(blobHash, callback) {
let timedOut = false;
const timeout = setTimeout(() => {
timedOut = true;
callback([]);
}, lbry.peerListTimeout);
lbry.call('peer_list', { blob_hash: blobHash }, function(peers) {
if (!timedOut) {
clearTimeout(timeout);
callback(peers);
}
});
}
//
// lbry.costPromiseCache = {}
// lbry.getCostInfo = function(uri) {
// if (lbry.costPromiseCache[uri] === undefined) {
// lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
// const COST_INFO_CACHE_KEY = 'cost_info_cache';
// let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
//
// function cacheAndResolve(cost, includesData) {
// console.log('getCostInfo cacheAndResolve ' + uri)
// console.log(cost)
// costInfoCache[uri] = {cost, includesData};
// setSession(COST_INFO_CACHE_KEY, costInfoCache);
// resolve({cost, includesData});
// }
//
// if (!uri) {
// return reject(new Error(`URI required.`));
// }
//
// if (costInfoCache[uri] && costInfoCache[uri].cost) {
// return resolve(costInfoCache[uri])
// }
//
// function getCost(uri, size) {
// lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
// cacheAndResolve(cost, size !== null);
// }, reject);
// }
//
// function getCostGenerous(uri) {
// console.log('get cost generous: ' + uri)
// // If generous is on, the calculation is simple enough that we might as well do it here in the front end
// lbry.resolve({uri: uri}).then((resolutionInfo) => {
// console.log('resolve inside getCostGenerous ' + uri)
// console.log(resolutionInfo)
// if (!resolutionInfo) {
// return reject(new Error("Unused URI"));
// }
// const fee = resolutionInfo.claim.value.stream.metadata.fee;
// if (fee === undefined) {
// cacheAndResolve(0, true);
// } else if (fee.currency == 'LBC') {
// cacheAndResolve(fee.amount, true);
// } else {
// lbryio.getExchangeRates().then(({lbc_usd}) => {
// cacheAndResolve(fee.amount / lbc_usd, true);
// });
// }
// });
// }
//
// const uriObj = lbryuri.parse(uri);
// const name = uriObj.path || uriObj.name;
//
// lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
// if (is_generous_host) {
// return getCostGenerous(uri);
// }
//
// lighthouse.get_size_for_name(name).then((size) => {
// if (size) {
// getCost(name, size);
// }
// else {
// getCost(name, null);
// }
// }, () => {
// getCost(name, null);
// });
// });
// });
// }
// return lbry.costPromiseCache[uri];
// }
/**
* Takes a LBRY URI; will first try and calculate a total cost using
* Lighthouse. If Lighthouse can't be reached, it just retrives the
@ -287,8 +176,6 @@ lbry.getCostInfo = function(uri) {
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
function cacheAndResolve(cost, includesData) {
console.log('getCostInfo cacheAndResolve ' + uri)
console.log(cost)
costInfoCache[uri] = {cost, includesData};
setSession(COST_INFO_CACHE_KEY, costInfoCache);
resolve({cost, includesData});
@ -324,40 +211,6 @@ lbry.getCostInfo = function(uri) {
return lbry.costPromiseCache[uri];
}
lbry.getMyClaims = function(callback) {
lbry.call('get_name_claims', {}, callback);
}
lbry.removeFile = function(outpoint, deleteTargetFile=true, callback) {
this._removedFiles.push(outpoint);
// this._updateFileInfoSubscribers(outpoint);
lbry.file_delete({
outpoint: outpoint,
delete_target_file: deleteTargetFile,
}).then(callback);
}
lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) {
function scheduleNextCheckOrTimeout() {
if (timeoutCallback && tryNum > 200) {
timeoutCallback();
} else {
setTimeout(() => lbry.getFileInfoWhenListed(name, callback, timeoutCallback, tryNum + 1), 250);
}
}
// Calls callback with file info when it appears in the lbrynet file manager.
// If timeoutCallback is provided, it will be called if the file fails to appear.
lbry.file_list({name: name}).then(([fileInfo]) => {
if (fileInfo) {
callback(fileInfo);
} else {
scheduleNextCheckOrTimeout();
}
}, () => scheduleNextCheckOrTimeout());
}
/**
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
* lbry.file_list() during the publish process.
@ -398,10 +251,6 @@ lbry.publish = function(params, fileListedCallback, publishedCallback, errorCall
fileListedCallback(true);
}
}, 2000);
//lbry.getFileInfoWhenListed(params.name, function(fileInfo) {
// fileListedCallback(fileInfo);
//});
}
@ -456,29 +305,10 @@ lbry.formatName = function(name) {
return name;
}
lbry.nameIsValid = function(name, checkCase=true) {
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
return regexp.test(name);
}
lbry.loadJs = function(src, type, onload)
{
var lbryScriptTag = document.getElementById('lbry'),
newScriptTag = document.createElement('script'),
type = type || 'text/javascript';
newScriptTag.src = src;
newScriptTag.type = type;
if (onload)
{
newScriptTag.onload = onload;
}
lbryScriptTag.parentNode.insertBefore(newScriptTag, lbryScriptTag);
}
lbry.imagePath = function(file)
{
return lbry.rootPath + '/img/' + file;
return 'img/' + file;
}
lbry.getMediaType = function(contentType, fileName) {
@ -509,71 +339,9 @@ lbry.stop = function(callback) {
lbry.call('stop', {}, callback);
};
lbry.fileInfo = {};
lbry._subscribeIdCount = 0;
lbry._fileInfoSubscribeCallbacks = {};
lbry._fileInfoSubscribeInterval = 500000;
lbry._balanceSubscribeCallbacks = {};
lbry._balanceSubscribeInterval = 5000;
lbry._removedFiles = [];
lbry._claimIdOwnershipCache = {};
lbry._updateClaimOwnershipCache = function(claimId) {
lbry.getMyClaims((claimInfos) => {
lbry._claimIdOwnershipCache[claimId] = !!claimInfos.reduce(function(match, claimInfo) {
return match || claimInfo.claim_id == claimId;
}, false);
});
};
lbry._updateFileInfoSubscribers = function(outpoint) {
const callSubscribedCallbacks = (outpoint, fileInfo) => {
for (let callback of Object.values(this._fileInfoSubscribeCallbacks[outpoint])) {
callback(fileInfo);
}
}
if (lbry._removedFiles.includes(outpoint)) {
callSubscribedCallbacks(outpoint, false);
} else {
lbry.file_list({
outpoint: outpoint,
full_status: true,
}).then(([fileInfo]) => {
if (fileInfo) {
if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) {
this._updateClaimOwnershipCache(fileInfo.claim_id);
}
fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id];
}
callSubscribedCallbacks(outpoint, fileInfo);
});
}
if (Object.keys(this._fileInfoSubscribeCallbacks[outpoint]).length) {
setTimeout(() => {
this._updateFileInfoSubscribers(outpoint);
}, lbry._fileInfoSubscribeInterval);
}
}
lbry.fileInfoSubscribe = function(outpoint, callback) {
if (!lbry._fileInfoSubscribeCallbacks[outpoint])
{
lbry._fileInfoSubscribeCallbacks[outpoint] = {};
}
const subscribeId = ++lbry._subscribeIdCount;
lbry._fileInfoSubscribeCallbacks[outpoint][subscribeId] = callback;
lbry._updateFileInfoSubscribers(outpoint);
return subscribeId;
}
lbry.fileInfoUnsubscribe = function(outpoint, subscribeId) {
delete lbry._fileInfoSubscribeCallbacks[outpoint][subscribeId];
}
lbry._balanceUpdateInterval = null;
lbry._updateBalanceSubscribers = function() {
@ -650,7 +418,7 @@ lbry.file_list = function(params={}) {
lbry.call('file_list', params, (fileInfos) => {
removePendingPublishIfNeeded({name, channel_name, outpoint});
const dummyFileInfos = getPendingPublishes().map(pendingPublishToDummyFileInfo);
const dummyFileInfos = lbry.getPendingPublishes().map(pendingPublishToDummyFileInfo);
resolve([...fileInfos, ...dummyFileInfos]);
}, reject, reject);
});
@ -663,7 +431,7 @@ lbry.claim_list_mine = function(params={}) {
removePendingPublishIfNeeded({name, channel_name, outpoint: txid + ':' + nout});
}
const dummyClaims = getPendingPublishes().map(pendingPublishToDummyClaim);
const dummyClaims = lbry.getPendingPublishes().map(pendingPublishToDummyClaim);
resolve([...claims, ...dummyClaims]);
}, reject, reject)
});

View file

@ -175,6 +175,11 @@ lbryuri.isValid = function(uri) {
return parts && parts.name;
}
lbryuri.isValidName = function(name, checkCase=true) {
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
return regexp.test(name);
}
lbryuri.isClaimable = function(uri) {
let parts
try {

View file

@ -17,6 +17,9 @@ import {
import {
doFetchDaemonSettings
} from 'actions/settings'
import {
doFileList
} from 'actions/file_info'
import parseQueryParams from 'util/query_params'
const {remote, ipcRenderer} = require('electron');
@ -31,13 +34,15 @@ window.addEventListener('contextmenu', (event) => {
event.preventDefault();
});
window.addEventListener('popstate', (event) => {
window.addEventListener('popstate', (event, param) => {
const queryString = document.location.search
const pathParts = document.location.pathname.split('/')
const route = '/' + pathParts[pathParts.length - 1]
if (route.match(/html$/)) return
console.log('title should be set here, but it is not in popstate? TODO')
app.store.dispatch(doChangePath(`${route}${queryString}`))
})
@ -56,6 +61,7 @@ var init = function() {
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
app.store.dispatch(doHistoryPush({}, "Discover", "/discover"))
app.store.dispatch(doFetchDaemonSettings())
app.store.dispatch(doFileList())
ReactDOM.render(<Provider store={store}><div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div></Provider>, canvas)
}

View file

@ -6,18 +6,28 @@ import {
doFetchClaimsByChannel
} from 'actions/content'
import {
makeSelectClaimsForChannel
makeSelectClaimForUri,
makeSelectClaimsInChannelForUri
} from 'selectors/claims'
import ChannelPage from './view'
//
// const select = (state) => ({
// uri: selectCurrentUri(state),
// claim: selectCurrentUriClaim(state),
// claims: selectCurrentUriClaims(state)
// })
import FilePage from './view'
const makeSelect = () => {
const selectClaim = makeSelectClaimForUri(),
selectClaimsInChannel = makeSelectClaimsInChannelForUri()
const select = (state, props) => ({
claim: selectClaim(state, props),
claimsInChannel: selectClaimsInChannel(state, props)
})
return select
}
const perform = (dispatch) => ({
// fetchClaims: () => { console.log('fetch claims') }
fetchClaims: (uri) => dispatch(doFetchClaimsByChannel(uri))
})
export default connect(null, perform)(ChannelPage)
export default connect(makeSelect, perform)(ChannelPage)

View file

@ -1,4 +1,5 @@
import React from 'react';
import lbryuri from 'lbryuri'
class ChannelPage extends React.Component{
componentDidMount() {
@ -10,19 +11,19 @@ class ChannelPage extends React.Component{
}
fetchClaims(props) {
if (props.claims === undefined) {
if (props.claimsInChannel === undefined) {
props.fetchClaims(props.uri)
}
}
render() {
const {
claims,
claimsInChannel,
claim,
uri
} = this.props
console.log(claims);
console.log(claimsInChannel);
return <main className="main--single-column">
<section className="card">
<div className="card__inner">
@ -36,7 +37,9 @@ class ChannelPage extends React.Component{
</section>
<section className="card">
<div className="card__content">
{claims}
{claimsInChannel ?
claimsInChannel.map((claim) => <FileTile uri={lbryuri.build({name: claim.name, claimId: claim.claim_id})} /> )
: ''}
</div>
</section>
</main>

View file

@ -35,7 +35,7 @@ class DeveloperPage extends React.Component {
handleUpgradeFileChange(event) {
this.setState({
upgradePath: event.target.files[0].path,
upgradePath: event.target.value,
});
}

View file

@ -3,13 +3,11 @@ import {
connect
} from 'react-redux'
import {
doFetchDownloadedContent,
} from 'actions/content'
doFetchFileInfosAndPublishedClaims,
} from 'actions/file_info'
import {
selectFetchingDownloadedContent,
} from 'selectors/content'
import {
selectDownloadedFileInfo,
selectFileInfosDownloaded,
selectFileListDownloadedOrPublishedIsPending,
} from 'selectors/file_info'
import {
doNavigate,
@ -17,13 +15,13 @@ import {
import FileListDownloaded from './view'
const select = (state) => ({
downloadedContent: selectDownloadedFileInfo(state),
fetching: selectFetchingDownloadedContent(state),
fileInfos: selectFileInfosDownloaded(state),
isPending: selectFileListDownloadedOrPublishedIsPending(state),
})
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
fetchFileListDownloaded: () => dispatch(doFetchDownloadedContent()),
fetchFileInfosDownloaded: () => dispatch(doFetchFileInfosAndPublishedClaims()),
})
export default connect(select, perform)(FileListDownloaded)

View file

@ -12,21 +12,21 @@ import SubHeader from 'component/subHeader'
class FileListDownloaded extends React.Component {
componentWillMount() {
this.props.fetchFileListDownloaded()
this.props.fetchFileInfosDownloaded()
}
render() {
const {
downloadedContent,
fetching,
fileInfos,
isPending,
navigate,
} = this.props
let content
if (downloadedContent && downloadedContent.length > 0) {
content = <FileList fileInfos={downloadedContent} fetching={fetching} hidePrices={true} />
if (fileInfos && fileInfos.length > 0) {
content = <FileList fileInfos={fileInfos} fetching={isPending} />
} else {
if (fetching) {
if (isPending) {
content = <BusyMessage message="Loading" />
} else {
content = <span>You haven't downloaded anything from LBRY yet. Go <Link onClick={() => navigate('/discover')} label="search for your first download" />!</span>

View file

@ -3,13 +3,11 @@ import {
connect
} from 'react-redux'
import {
doFetchPublishedContent,
} from 'actions/content'
doFetchFileInfosAndPublishedClaims,
} from 'actions/file_info'
import {
selectFetchingPublishedContent,
} from 'selectors/content'
import {
selectPublishedFileInfo,
selectFileInfosPublished,
selectFileListDownloadedOrPublishedIsPending
} from 'selectors/file_info'
import {
doNavigate,
@ -17,13 +15,13 @@ import {
import FileListPublished from './view'
const select = (state) => ({
publishedContent: selectPublishedFileInfo(state),
fetching: selectFetchingPublishedContent(state),
fileInfos: selectFileInfosPublished(state),
isPending: selectFileListDownloadedOrPublishedIsPending(state),
})
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
fetchFileListPublished: () => dispatch(doFetchPublishedContent()),
fetchFileListPublished: () => dispatch(doFetchFileInfosAndPublishedClaims()),
})
export default connect(select, perform)(FileListPublished)

View file

@ -3,7 +3,7 @@ import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {FormField} from 'component/form.js';
import {FileTile} from 'component/fileTile';
import FileTile from 'component/fileTile';
import rewards from 'rewards.js';
import lbryio from 'lbryio.js';
import {BusyMessage, Thumbnail} from 'component/common.js';
@ -16,7 +16,7 @@ class FileListPublished extends React.Component {
}
componentDidUpdate() {
if(this.props.publishedContent.length > 0) this._requestPublishReward()
if(this.props.fileInfos.length > 0) this._requestPublishReward()
}
_requestPublishReward() {
@ -38,17 +38,17 @@ class FileListPublished extends React.Component {
render() {
const {
publishedContent,
fetching,
fileInfos,
isPending,
navigate,
} = this.props
let content
if (publishedContent && publishedContent.length > 0) {
content = <FileList fileInfos={publishedContent} fetching={fetching} hidePrices={true} />
if (fileInfos && fileInfos.length > 0) {
content = <FileList fileInfos={fileInfos} fetching={isPending} fileTileShowEmpty={FileTile.SHOW_EMPTY_PENDING} />
} else {
if (fetching) {
if (isPending) {
content = <BusyMessage message="Loading" />
} else {
content = <span>You haven't downloaded anything from LBRY yet. Go <Link onClick={() => navigate('/discover')} label="search for your first download" />!</span>

View file

@ -61,10 +61,10 @@ class FilePage extends React.Component{
render() {
const {
claim,
fileInfo,
metadata,
contentType,
uri,
fileInfo,
} = this.props
if (!claim || !metadata) {

View file

@ -28,10 +28,6 @@ class HelpPage extends React.Component {
});
}
componentDidMount() {
document.title = "Help";
}
render() {
let ver, osName, platform, newVerLink;
if (this.state.versionInfo) {

View file

@ -5,9 +5,13 @@ import {
import {
doNavigate,
} from 'actions/app'
import {
selectMyClaims
} from 'selectors/claims'
import PublishPage from './view'
const select = (state) => ({
myClaims: selectMyClaims(state)
})
const perform = (dispatch) => ({

View file

@ -1,5 +1,6 @@
import React from 'react';
import lbry from 'lbry';
import lbryuri from 'lbryuri'
import {FormField, FormRow} from 'component/form.js';
import Link from 'component/link';
import rewards from 'rewards';
@ -132,7 +133,7 @@ class PublishPage extends React.Component {
};
if (this.state.isFee) {
lbry.getUnusedAddress((address) => {
lbry.wallet_unused_address().then((address) => {
metadata.fee = {};
metadata.fee[this.state.feeCurrency] = {
amount: parseFloat(this.state.feeAmount),
@ -177,7 +178,7 @@ class PublishPage extends React.Component {
return;
}
if (!lbry.nameIsValid(rawName, false)) {
if (!lbryuri.isValidName(rawName, false)) {
this.refs.name.showError('LBRY names must contain only letters, numbers and dashes.');
return;
}
@ -190,11 +191,7 @@ class PublishPage extends React.Component {
myClaimExists: null,
});
lbry.getMyClaim(name, (myClaimInfo) => {
if (name != this.state.name) {
// A new name has been typed already, so bail
return;
}
const myClaimInfo = Object.values(this.props.myClaims).find(claim => claim.name === name)
this.setState({
myClaimExists: !!myClaimInfo,
@ -235,7 +232,6 @@ class PublishPage extends React.Component {
myClaimExists: false,
});
});
});
}
handleBidChange(event) {
@ -305,7 +301,7 @@ class PublishPage extends React.Component {
handleNewChannelNameChange(event) {
const newChannelName = (event.target.value.startsWith('@') ? event.target.value : '@' + event.target.value);
if (newChannelName.length > 1 && !lbry.nameIsValid(newChannelName.substr(1), false)) {
if (newChannelName.length > 1 && !lbryuri.isValidName(newChannelName.substr(1), false)) {
this.refs.newChannelName.showError('LBRY channel names must contain only letters, numbers and dashes.');
return;
} else {

View file

@ -101,7 +101,7 @@ class SettingsPage extends React.Component {
<h3>Download Directory</h3>
</div>
<div className="card__content">
<FormRow type="text"
<FormRow type="directory"
name="download_directory"
defaultValue={daemonSettings.download_directory}
helper="LBRY downloads will be saved here."

View file

@ -1,7 +1,9 @@
import React from 'react';
import lbryuri from 'lbryuri'
import {
BusyMessage,
} from 'component/common';
import ChannelPage from 'page/channel'
import FilePage from 'page/filePage'
class ShowPage extends React.Component{
@ -47,8 +49,7 @@ class ShowPage extends React.Component{
</section>
}
else if (claim.name.length && claim.name[0] === '@') {
innerContent = "channel"
// innerContent = <ChannelPage claim={claim} />
innerContent = <ChannelPage uri={lbryuri.build({ name: claim.name, claimId: claim.claim_id })} />
}
else if (claim) {
innerContent = <FilePage uri={uri} />

View file

@ -14,9 +14,7 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) {
const newClaims = Object.assign({}, state.claimsByUri)
if (claim !== undefined) {
newClaims[uri] = claim
}
//This needs a sanity boost...
if (certificate !== undefined && claim === undefined) {
@ -32,6 +30,33 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) {
})
}
reducers[types.RESOLVE_URI_CANCELED] = function(state, action) {
const uri = action.data.uri
const newClaims = Object.assign({}, state.claimsByUri)
delete newClaims[uri]
return Object.assign({}, state, {
claimsByUri: newClaims
})
}
reducers[types.CLAIM_LIST_MINE_STARTED] = function(state, action) {
return Object.assign({}, state, {
isClaimListMinePending: true
})
}
reducers[types.CLAIM_LIST_MINE_COMPLETED] = function(state, action) {
const myClaims = Object.assign({}, state.myClaims)
action.data.claims.forEach((claim) => {
myClaims[claim.claim_id] = claim
})
return Object.assign({}, state, {
isClaimListMinePending: false,
myClaims: myClaims
})
}
reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) {
const {
uri,
@ -49,23 +74,6 @@ reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) {
})
}
reducers[types.FETCH_MY_CLAIMS_COMPLETED] = function(state, action) {
const {
claims,
} = action.data
const newMine = Object.assign({}, state.mine)
const newById = Object.assign({}, newMine.byId)
claims.forEach(claim => {
newById[claim.claim_id] = claim
})
newMine.byId = newById
return Object.assign({}, state, {
mine: newMine,
})
}
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);

View file

@ -30,7 +30,7 @@ reducers[types.RESOLVE_URI_STARTED] = function(state, action) {
const oldResolving = state.resolvingUris || []
const newResolving = Object.assign([], oldResolving)
if (newResolving.indexOf(uri) == -1) newResolving.push(uri)
if (newResolving.indexOf(uri) === -1) newResolving.push(uri)
return Object.assign({}, state, {
resolvingUris: newResolving
@ -48,51 +48,14 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) {
...resolvingUris.slice(index + 1)
]
const newState = Object.assign({}, state, {
return Object.assign({}, state, {
resolvingUris: newResolvingUris,
})
return Object.assign({}, state, newState)
}
reducers[types.FETCH_DOWNLOADED_CONTENT_STARTED] = function(state, action) {
return Object.assign({}, state, {
fetchingDownloadedContent: true,
})
}
reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) {
const {
fileInfos
} = action.data
const newDownloadedContent = Object.assign({}, state.downloadedContent, {
fileInfos
})
return Object.assign({}, state, {
downloadedContent: newDownloadedContent,
fetchingDownloadedContent: false,
})
}
reducers[types.FETCH_PUBLISHED_CONTENT_STARTED] = function(state, action) {
return Object.assign({}, state, {
fetchingPublishedContent: true,
})
}
reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) {
const {
fileInfos
} = action.data
const newPublishedContent = Object.assign({}, state.publishedContent, {
fileInfos
})
return Object.assign({}, state, {
publishedContent: newPublishedContent,
fetchingPublishedContent: false,
})
reducers[types.RESOLVE_URI_CANCELED] = function(state, action) {
return reducers[types.RESOLVE_URI_COMPLETED](state, action)
}
export default function reducer(state = defaultState, action) {

View file

@ -5,13 +5,35 @@ const reducers = {}
const defaultState = {
}
reducers[types.FILE_LIST_STARTED] = function(state, action) {
return Object.assign({}, state, {
isFileListPending: true,
})
}
reducers[types.FILE_LIST_COMPLETED] = function(state, action) {
const {
fileInfos,
} = action.data
const newFileInfos = Object.assign({}, state.fileInfos)
fileInfos.forEach((fileInfo) => {
newFileInfos[fileInfo.outpoint] = fileInfo
})
return Object.assign({}, state, {
isFileListPending: false,
fileInfos: newFileInfos
})
}
reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) {
const {
uri,
outpoint
} = action.data
const newFetching = Object.assign({}, state.fetching)
newFetching[uri] = true
newFetching[outpoint] = true
return Object.assign({}, state, {
fetching: newFetching,
@ -20,19 +42,18 @@ reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) {
reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) {
const {
uri,
fileInfo,
outpoint,
} = action.data
const newByUri = Object.assign({}, state.byUri)
const newFileInfos = Object.assign({}, state.fileInfos)
const newFetching = Object.assign({}, state.fetching)
newByUri[uri] = fileInfo || null
delete newFetching[uri]
newFileInfos[outpoint] = fileInfo
delete newFetching[outpoint]
return Object.assign({}, state, {
byUri: newByUri,
fileInfos: newFileInfos,
fetching: newFetching,
})
}
@ -40,93 +61,74 @@ reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) {
reducers[types.DOWNLOADING_STARTED] = function(state, action) {
const {
uri,
outpoint,
fileInfo,
} = action.data
const newByUri = Object.assign({}, state.byUri)
const newDownloading = Object.assign({}, state.downloading)
const newDownloadingByUri = Object.assign({}, newDownloading.byUri)
const newLoading = Object.assign({}, state.loading)
const newLoadingByUri = Object.assign({}, newLoading)
newDownloadingByUri[uri] = true
newDownloading.byUri = newDownloadingByUri
newByUri[uri] = fileInfo
delete newLoadingByUri[uri]
newLoading.byUri = newLoadingByUri
const newFileInfos = Object.assign({}, state.fileInfos)
const newDownloading = Object.assign({}, state.urisDownloading)
const newLoading = Object.assign({}, state.urisLoading)
newDownloading[uri] = true
newFileInfos[outpoint] = fileInfo
delete newLoading[uri]
return Object.assign({}, state, {
downloading: newDownloading,
byUri: newByUri,
loading: newLoading,
urisDownloading: newDownloading,
urisLoading: newLoading,
fileInfos: newFileInfos,
})
}
reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) {
const {
uri,
outpoint,
fileInfo,
} = action.data
const newByUri = Object.assign({}, state.byUri)
const newDownloading = Object.assign({}, state.downloading)
newByUri[uri] = fileInfo
const newFileInfos = Object.assign({}, state.fileInfos)
const newDownloading = Object.assign({}, state.urisDownloading)
newFileInfos[outpoint] = fileInfo
newDownloading[uri] = true
return Object.assign({}, state, {
byUri: newByUri,
downloading: newDownloading
fileInfos: newFileInfos,
urisDownloading: newDownloading
})
}
reducers[types.DOWNLOADING_COMPLETED] = function(state, action) {
const {
uri,
outpoint,
fileInfo,
} = action.data
const newByUri = Object.assign({}, state.byUri)
const newDownloading = Object.assign({}, state.downloading)
const newDownloadingByUri = Object.assign({}, newDownloading.byUri)
newByUri[uri] = fileInfo
delete newDownloadingByUri[uri]
newDownloading.byUri = newDownloadingByUri
const newFileInfos = Object.assign({}, state.fileInfos)
const newDownloading = Object.assign({}, state.urisDownloading)
newFileInfos[outpoint] = fileInfo
delete newDownloading[uri]
return Object.assign({}, state, {
byUri: newByUri,
downloading: newDownloading,
fileInfos: newFileInfos,
urisDownloading: newDownloading,
})
}
reducers[types.DELETE_FILE_STARTED] = function(state, action) {
reducers[types.FILE_DELETE] = function(state, action) {
const {
uri,
outpoint,
} = action.data
const newDeleting = Object.assign({}, state.deleting)
const newByUri = Object.assign({}, newDeleting.byUri)
newByUri[uri] = true
newDeleting.byUri = newByUri
const newFileInfos = Object.assign({}, state.fileInfos)
delete newFileInfos[outpoint]
return Object.assign({}, state, {
deleting: newDeleting,
})
}
reducers[types.DELETE_FILE_COMPLETED] = function(state, action) {
const {
uri,
} = action.data
const newDeleting = Object.assign({}, state.deleting)
const newDeletingByUri = Object.assign({}, newDeleting.byUri)
const newByUri = Object.assign({}, state.byUri)
delete newDeletingByUri[uri]
newDeleting.byUri = newDeletingByUri
delete newByUri[uri]
return Object.assign({}, state, {
deleting: newDeleting,
byUri: newByUri,
fileInfos: newFileInfos,
})
}
@ -134,14 +136,13 @@ reducers[types.LOADING_VIDEO_STARTED] = function(state, action) {
const {
uri,
} = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
newByUri[uri] = true
newLoading.byUri = newByUri
const newLoading = Object.assign({}, state.urisLoading)
newLoading[uri] = true
return Object.assign({}, state, {
loading: newLoading,
urisLoading: newLoading,
})
}
@ -149,54 +150,13 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) {
const {
uri,
} = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
delete newByUri[uri]
newLoading.byUri = newByUri
const newLoading = Object.assign({}, state.urisLoading)
delete newLoading[uri]
return Object.assign({}, state, {
loading: newLoading,
})
}
reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) {
const {
fileInfos,
} = action.data
const newByUri = Object.assign({}, state.byUri)
fileInfos.forEach(fileInfo => {
const uri = lbryuri.build({
channelName: fileInfo.channel_name,
contentName: fileInfo.name,
})
newByUri[uri] = fileInfo
})
return Object.assign({}, state, {
byUri: newByUri
})
}
reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) {
const {
fileInfos
} = action.data
const newByUri = Object.assign({}, state.byUri)
fileInfos.forEach(fileInfo => {
const uri = lbryuri.build({
channelName: fileInfo.channel_name,
contentName: fileInfo.name,
})
newByUri[uri] = fileInfo
})
return Object.assign({}, state, {
byUri: newByUri
urisLoading: newLoading,
})
}

View file

@ -15,17 +15,6 @@ export const selectAllClaimsByChannel = createSelector(
(state) => state.claimsByChannel || {}
)
export const selectClaimsForChannel = (state, props) => {
return selectAllClaimsByChannel(state)[props.uri]
}
export const makeSelectClaimsForChannel = () => {
return createSelector(
selectClaimsForChannel,
(claim) => claim
)
}
const selectClaimForUri = (state, props) => {
const uri = lbryuri.normalize(props.uri)
return selectClaimsByUri(state)[uri]
@ -38,6 +27,17 @@ export const makeSelectClaimForUri = () => {
)
}
export const selectClaimsInChannelForUri = (state, props) => {
return selectAllClaimsByChannel(state)[props.uri]
}
export const makeSelectClaimsInChannelForUri = () => {
return createSelector(
selectClaimsInChannelForUri,
(claims) => claims
)
}
const selectMetadataForUri = (state, props) => {
const claim = selectClaimForUri(state, props)
const metadata = claim && claim.value && claim.value.stream && claim.value.stream.metadata
@ -73,26 +73,25 @@ export const makeSelectContentTypeForUri = () => {
)
}
export const selectMyClaims = createSelector(
export const selectClaimListMineIsPending = createSelector(
_selectState,
(state) => state.mine || {}
(state) => state.isClaimListMinePending
)
export const selectMyClaimsById = createSelector(
selectMyClaims,
(mine) => mine.byId || {}
export const selectMyClaims = createSelector(
_selectState,
(state) => state.myClaims || {}
)
export const selectMyClaimsOutpoints = createSelector(
selectMyClaimsById,
(byId) => {
const outpoints = []
Object.keys(byId).forEach(key => {
const claim = byId[key]
const outpoint = `${claim.txid}:${claim.nout}`
outpoints.push(outpoint)
})
selectMyClaims,
(claims) => {
if (!claims) {
return []
}
return outpoints
return Object.values(claims).map((claim) => {
return `${claim.txid}:${claim.nout}`
})
}
)

View file

@ -16,32 +16,6 @@ export const selectFetchingFeaturedUris = createSelector(
(state) => !!state.fetchingFeaturedContent
)
export const selectFetchingDownloadedContent = createSelector(
_selectState,
(state) => !!state.fetchingDownloadedContent
)
export const selectDownloadedContent = createSelector(
_selectState,
(state) => state.downloadedContent || {}
)
export const selectDownloadedContentFileInfos = createSelector(
selectDownloadedContent,
(downloadedContent) => downloadedContent.fileInfos || []
)
export const selectFetchingPublishedContent = createSelector(
_selectState,
(state) => !!state.fetchingPublishedContent
)
export const selectPublishedContent = createSelector(
_selectState,
(state) => state.publishedContent || {}
)
export const selectResolvingUris = createSelector(
_selectState,
(state) => state.resolvingUris || []

View file

@ -1,29 +1,38 @@
import lbry from 'lbry'
import {
createSelector,
} from 'reselect'
import {
selectClaimsByUri,
selectClaimListMineIsPending,
selectMyClaimsOutpoints,
} from 'selectors/claims'
export const _selectState = state => state.fileInfo || {}
export const selectAllFileInfoByUri = createSelector(
export const selectAllFileInfos = createSelector(
_selectState,
(state) => state.byUri || {}
(state) => state.fileInfos || {}
)
export const selectDownloading = createSelector(
export const selectFileListIsPending = createSelector(
_selectState,
(state) => state.downloading || {}
(state) => state.isFileListPending
)
export const selectDownloadingByUri = createSelector(
selectDownloading,
(downloading) => downloading.byUri || {}
export const selectFileListDownloadedOrPublishedIsPending = createSelector(
selectFileListIsPending,
selectClaimListMineIsPending,
(isFileListPending, isClaimListMinePending) => isFileListPending || isClaimListMinePending
)
export const selectFileInfoForUri = (state, props) => {
return selectAllFileInfoByUri(state)[props.uri]
const claims = selectClaimsByUri(state),
claim = claims[props.uri],
fileInfos = selectAllFileInfos(state),
outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined
return outpoint && fileInfos ? fileInfos[outpoint] : undefined
}
export const makeSelectFileInfoForUri = () => {
@ -33,8 +42,13 @@ export const makeSelectFileInfoForUri = () => {
)
}
export const selectUrisDownloading = createSelector(
_selectState,
(state) => state.urisDownloading || {}
)
const selectDownloadingForUri = (state, props) => {
const byUri = selectDownloadingByUri(state)
const byUri = selectUrisDownloading(state)
return byUri[props.uri]
}
@ -45,18 +59,13 @@ export const makeSelectDownloadingForUri = () => {
)
}
export const selectLoading = createSelector(
export const selectUrisLoading = createSelector(
_selectState,
(state) => state.loading || {}
)
export const selectLoadingByUri = createSelector(
selectLoading,
(loading) => loading.byUri || {}
(state) => state.urisLoading || {}
)
const selectLoadingForUri = (state, props) => {
const byUri = selectLoadingByUri(state)
const byUri = selectUrisLoading(state)
return byUri[props.uri]
}
@ -67,36 +76,38 @@ export const makeSelectLoadingForUri = () => {
)
}
export const selectDownloadedFileInfo = createSelector(
selectAllFileInfoByUri,
(byUri) => {
export const selectFileInfosDownloaded = createSelector(
selectAllFileInfos,
selectMyClaimsOutpoints,
(fileInfos, myClaimOutpoints) => {
const fileInfoList = []
Object.keys(byUri).forEach(key => {
const fileInfo = byUri[key]
if (fileInfo.completed || fileInfo.written_bytes) {
Object.values(fileInfos).forEach(fileInfo => {
if (fileInfo && myClaimOutpoints.indexOf(fileInfo.outpoint) === -1 && (fileInfo.completed || fileInfo.written_bytes)) {
fileInfoList.push(fileInfo)
}
})
return fileInfoList
}
)
export const selectPublishedFileInfo = createSelector(
selectAllFileInfoByUri,
selectMyClaimsOutpoints,
(byUri, outpoints) => {
const fileInfos = []
outpoints.forEach(outpoint => {
Object.keys(byUri).forEach(key => {
const fileInfo = byUri[key]
if (fileInfo.outpoint == outpoint) {
fileInfos.push(fileInfo)
}
})
})
return fileInfos
export const selectFileInfosPendingPublish = createSelector(
_selectState,
(state) => {
return lbry.getPendingPublishes()
}
)
export const selectFileInfosPublished = createSelector(
selectAllFileInfos,
selectFileInfosPendingPublish,
selectMyClaimsOutpoints,
(allFileInfos, pendingFileInfos, outpoints) => {
const fileInfos = []
outpoints.forEach(outpoint => {
if (allFileInfos[outpoint]) {
fileInfos.push(allFileInfos[outpoint])
}
})
return [...fileInfos, ...pendingFileInfos]
}
)

View file

@ -5,6 +5,7 @@
@import "component/_button.scss";
@import "component/_card.scss";
@import "component/_file-actions.scss";
@import "component/_file-selector.scss";
@import "component/_file-tile.scss";
@import "component/_form-field.scss";
@import "component/_header.scss";

View file

@ -0,0 +1,7 @@
.file-selector__choose-button {
font-size: 13px;
}
.file-selector__path {
font-size: 14px;
}