Merge branch 'redux' into file-selector

This commit is contained in:
Jeremy Kauffman 2017-05-19 09:11:32 -04:00
commit f60d17f604
73 changed files with 1328 additions and 1503 deletions

View file

@ -55,6 +55,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
* Handle more of price calculations at the daemon layer to improve page load time * Handle more of price calculations at the daemon layer to improve page load time
* Add special support for building channel claims in lbryuri module * Add special support for building channel claims in lbryuri module
* Enable windows code signing of binary * Enable windows code signing of binary
* Support for opening LBRY URIs from links in other apps
### Changed ### Changed
@ -66,6 +67,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
### Fixed ### Fixed
* Fix Watch page and progress bars for new API changes * Fix Watch page and progress bars for new API changes
* On Windows, prevent opening multiple LBRY instances (launching LBRY again just focuses the current instance)

View file

@ -37,6 +37,22 @@ let readyToQuit = false;
// send it to, it's cached in this variable. // send it to, it's cached in this variable.
let openUri = null; let openUri = null;
function denormalizeUri(uri) {
// Windows normalizes URIs when they're passed in from other apps. This tries
// to restore the original URI that was typed.
// - If the URI has no path, Windows adds a trailing slash. LBRY URIs
// can't have a slash with no path, so we just strip it off.
// - In a URI with a claim ID, like lbry://channel#claimid, Windows
// interprets the hash mark as an anchor and converts it to
// lbry://channel/#claimid. We remove the slash here as well.
if (process.platform == 'win32') {
return uri.replace(/\/$/, '').replace('/#', '#');
} else {
return uri;
}
}
function checkForNewVersion(callback) { function checkForNewVersion(callback) {
function formatRc(ver) { function formatRc(ver) {
// Adds dash if needed to make RC suffix semver friendly // Adds dash if needed to make RC suffix semver friendly
@ -186,6 +202,29 @@ function quitNow() {
app.quit(); app.quit();
} }
if (process.platform != 'linux') {
// On Linux, this is always returning true due to an Electron bug,
// so for now we just don't support single-instance apps on Linux.
const isSecondaryInstance = app.makeSingleInstance((argv) => {
if (win) {
if (win.isMinimized()) {
win.restore();
}
win.focus();
if (argv.length >= 2) {
win.webContents.send('open-uri-requested', denormalizeUri(argv[1]));
}
} else if (argv.length >= 2) {
openUri = denormalizeUri(argv[1]);
}
});
if (isSecondaryInstance) { // We're not in the original process, so quit
quitNow();
return;
}
}
app.on('ready', function(){ app.on('ready', function(){
launchDaemonIfNotRunning(); launchDaemonIfNotRunning();
@ -324,6 +363,8 @@ function upgrade(event, installerPath) {
ipcMain.on('upgrade', upgrade); ipcMain.on('upgrade', upgrade);
app.setAsDefaultProtocolClient('lbry');
if (process.platform == 'darwin') { if (process.platform == 'darwin') {
app.on('open-url', (event, uri) => { app.on('open-url', (event, uri) => {
if (!win) { if (!win) {
@ -333,7 +374,10 @@ if (process.platform == 'darwin') {
win.webContents.send('open-uri-requested', uri); win.webContents.send('open-uri-requested', uri);
} }
}); });
} else if (process.argv.length >= 3) { } else if (process.argv.length >= 2) {
// No open-url event on Win, but we can still handle URIs provided at launch time if (!win) {
win.webContents.send('open-uri-requested', process.argv[2]); openUri = denormalizeUri(process.argv[1]);
} else {
win.webContents.send('open-uri-requested', denormalizeUri(process.argv[1]));
}
} }

1
lbry Submodule

@ -0,0 +1 @@
Subproject commit d99fc519b56ee910a44ef4af668b0770e9430d12

1
lbryschema Submodule

@ -0,0 +1 @@
Subproject commit 5c2441fa13e39ba7280292519041e14ec696d753

1
lbryum Submodule

@ -0,0 +1 @@
Subproject commit 950b95aa7e45a2c15b269d807f6ff8e16bae4304

View file

@ -6,7 +6,6 @@ import {
selectUpgradeDownloadItem, selectUpgradeDownloadItem,
selectUpgradeFilename, selectUpgradeFilename,
selectPageTitle, selectPageTitle,
selectCurrentPath,
} from 'selectors/app' } from 'selectors/app'
const {remote, ipcRenderer, shell} = require('electron'); const {remote, ipcRenderer, shell} = require('electron');
@ -32,8 +31,7 @@ export function doNavigate(path, params = {}) {
const state = getState() const state = getState()
const pageTitle = selectPageTitle(state) const pageTitle = selectPageTitle(state)
history.pushState(params, pageTitle, url) dispatch(doHistoryPush(params, pageTitle, url))
window.document.title = pageTitle
} }
} }
@ -45,7 +43,6 @@ export function doChangePath(path) {
path, path,
} }
}) })
} }
} }
@ -55,7 +52,15 @@ export function doHistoryBack() {
} }
} }
export function doLogoClick() { export function doHistoryPush(params, title, relativeUrl) {
return function(dispatch, getState) {
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
}
} }
export function doOpenModal(modal) { export function doOpenModal(modal) {

View file

@ -1,39 +1,29 @@
import * as types from 'constants/action_types' import * as types from 'constants/action_types'
import lbry from 'lbry' import lbry from 'lbry'
import { import {
selectCurrentUri, selectFetchingAvailability
} from 'selectors/app' } from 'selectors/availability'
export function doFetchUriAvailability(uri) { export function doFetchAvailability(uri) {
return function(dispatch, getState) {
dispatch({
type: types.FETCH_AVAILABILITY_STARTED,
data: { uri }
})
const successCallback = (availability) => {
dispatch({
type: types.FETCH_AVAILABILITY_COMPLETED,
data: {
availability,
uri,
}
})
}
const errorCallback = () => {
console.debug('error')
}
lbry.get_availability({ uri }, successCallback, errorCallback)
}
}
export function doFetchCurrentUriAvailability() {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
const uri = selectCurrentUri(state) const alreadyFetching = !!selectFetchingAvailability(state)[uri]
dispatch(doFetchUriAvailability(uri)) if (!alreadyFetching) {
dispatch({
type: types.FETCH_AVAILABILITY_STARTED,
data: {uri}
})
lbry.get_availability({uri}).then((availability) => {
dispatch({
type: types.FETCH_AVAILABILITY_COMPLETED,
data: {
availability,
uri,
}
})
})
}
} }
} }

View file

@ -3,18 +3,18 @@ import lbry from 'lbry'
import lbryio from 'lbryio' import lbryio from 'lbryio'
import lbryuri from 'lbryuri' import lbryuri from 'lbryuri'
import rewards from 'rewards' import rewards from 'rewards'
import {
selectCurrentUri,
} from 'selectors/app'
import { import {
selectBalance, selectBalance,
} from 'selectors/wallet' } from 'selectors/wallet'
import { import {
selectCurrentUriFileInfo, selectFileInfoForUri,
selectDownloadingByUri, selectUrisDownloading,
} from 'selectors/file_info' } from 'selectors/file_info'
import { import {
selectCurrentUriCostInfo, selectResolvingUris
} from 'selectors/content'
import {
selectCostInfoForUri,
} from 'selectors/cost_info' } from 'selectors/cost_info'
import { import {
selectClaimsByUri, selectClaimsByUri,
@ -22,94 +22,44 @@ import {
import { import {
doOpenModal, doOpenModal,
} from 'actions/app' } from 'actions/app'
import {
doFetchCostInfoForUri,
} from 'actions/cost_info'
export function doResolveUri(uri) { export function doResolveUri(uri) {
return function(dispatch, getState) { return function(dispatch, getState) {
dispatch({
type: types.RESOLVE_URI_STARTED,
data: { uri }
})
lbry.resolve({ uri }).then((resolutionInfo) => { const state = getState()
const { const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1
claim,
certificate,
} = resolutionInfo ? resolutionInfo : { claim : null, certificate: null }
if (!alreadyResolving) {
dispatch({ dispatch({
type: types.RESOLVE_URI_COMPLETED, type: types.RESOLVE_URI_STARTED,
data: { data: { uri }
uri, })
lbry.resolve({ uri }).then((resolutionInfo) => {
const {
claim, claim,
certificate, certificate,
} } = resolutionInfo ? resolutionInfo : { claim : null, certificate: null }
})
dispatch(doFetchCostInfoForUri(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({ dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, type: types.RESOLVE_URI_COMPLETED,
data: { data: {
fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), uri,
} claim,
}) certificate,
});
});
}
}
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)),
} }
}) })
}) })
}
}
}
export function doCancelResolveUri(uri) {
return function(dispatch, getState) {
lbry.cancelResolve({ uri })
dispatch({
type: types.RESOLVE_URI_CANCELED,
data: { uri }
}) })
} }
} }
@ -131,6 +81,14 @@ export function doFetchFeaturedUris() {
featuredUris[category] = Uris[category] featuredUris[category] = Uris[category]
} }
}) })
//
// dispatch({
// type: types.FETCH_FEATURED_CONTENT_COMPLETED,
// data: {
// categories: ["FOO"],
// uris: { FOO: ["lbry://gtasoc"]},
// }
// })
dispatch({ dispatch({
type: types.FETCH_FEATURED_CONTENT_COMPLETED, type: types.FETCH_FEATURED_CONTENT_COMPLETED,
@ -175,6 +133,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
type: types.DOWNLOADING_COMPLETED, type: types.DOWNLOADING_COMPLETED,
data: { data: {
uri, uri,
outpoint,
fileInfo, fileInfo,
} }
}) })
@ -190,6 +149,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
type: types.DOWNLOADING_PROGRESSED, type: types.DOWNLOADING_PROGRESSED,
data: { data: {
uri, uri,
outpoint,
fileInfo, fileInfo,
progress, progress,
} }
@ -200,13 +160,6 @@ export function doUpdateLoadStatus(uri, outpoint) {
} }
} }
export function doPlayVideo(uri) {
return {
type: types.PLAY_VIDEO_STARTED,
data: { uri }
}
}
export function doDownloadFile(uri, streamInfo) { export function doDownloadFile(uri, streamInfo) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
@ -216,6 +169,7 @@ export function doDownloadFile(uri, streamInfo) {
type: types.DOWNLOADING_STARTED, type: types.DOWNLOADING_STARTED,
data: { data: {
uri, uri,
outpoint: streamInfo.outpoint,
fileInfo, fileInfo,
} }
}) })
@ -230,10 +184,9 @@ export function doDownloadFile(uri, streamInfo) {
} }
} }
export function doLoadVideo() { export function doLoadVideo(uri) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
const uri = selectCurrentUri(state)
dispatch({ dispatch({
type: types.LOADING_VIDEO_STARTED, type: types.LOADING_VIDEO_STARTED,
@ -260,14 +213,13 @@ export function doLoadVideo() {
} }
} }
export function doWatchVideo() { export function doPurchaseUri(uri) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
const uri = selectCurrentUri(state)
const balance = selectBalance(state) const balance = selectBalance(state)
const fileInfo = selectCurrentUriFileInfo(state) const fileInfo = selectFileInfoForUri(state, { uri })
const costInfo = selectCurrentUriCostInfo(state) const costInfo = selectCostInfoForUri(state, { uri })
const downloadingByUri = selectDownloadingByUri(state) const downloadingByUri = selectUrisDownloading(state)
const alreadyDownloading = !!downloadingByUri[uri] const alreadyDownloading = !!downloadingByUri[uri]
const { cost } = costInfo const { cost } = costInfo
@ -288,8 +240,8 @@ export function doWatchVideo() {
} }
// the file is free or we have partially downloaded it // the file is free or we have partially downloaded it
if (cost <= 0.01 || fileInfo.download_directory) { if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
dispatch(doLoadVideo()) dispatch(doLoadVideo(uri))
return Promise.resolve() return Promise.resolve()
} }
@ -302,3 +254,44 @@ export function doWatchVideo() {
return Promise.resolve() return Promise.resolve()
} }
} }
export function doFetchClaimsByChannel(uri) {
return function(dispatch, getState) {
dispatch({
type: types.FETCH_CHANNEL_CLAIMS_STARTED,
data: { uri }
})
lbry.resolve({ uri }).then((resolutionInfo) => {
const {
claims_in_channel,
} = resolutionInfo ? resolutionInfo : { claims_in_channel: [] }
dispatch({
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
data: {
uri,
claims: claims_in_channel
}
})
})
}
}
export function doClaimListMine() {
return function(dispatch, getState) {
dispatch({
type: types.CLAIM_LIST_MINE_STARTED
})
lbry.claim_list_mine().then((claims) => {
dispatch({
type: types.CLAIM_LIST_MINE_COMPLETED,
data: {
claims
}
})
})
}
}

View file

@ -1,19 +1,51 @@
import * as types from 'constants/action_types' import * as types from 'constants/action_types'
import {
selectCurrentUri,
} from 'selectors/app'
import lbry from 'lbry' import lbry from 'lbry'
import lbryio from 'lbryio'
import {
doResolveUri
} from 'actions/content'
import {
selectResolvingUris,
} from 'selectors/content'
import {
selectClaimsByUri
} from 'selectors/claims'
import {
selectSettingsIsGenerous
} from 'selectors/settings'
export function doFetchCostInfoForUri(uri) { export function doFetchCostInfoForUri(uri) {
return function(dispatch, getState) { return function(dispatch, getState) {
dispatch({ const state = getState(),
type: types.FETCH_COST_INFO_STARTED, claim = selectClaimsByUri(state)[uri],
data: { isResolving = selectResolvingUris(state).indexOf(uri) !== -1,
uri, isGenerous = selectSettingsIsGenerous(state)
}
})
lbry.getCostInfo(uri).then(costInfo => { 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({
type: types.FETCH_COST_INFO_STARTED,
data: {
uri,
}
})
}
function resolve(costInfo) {
dispatch({ dispatch({
type: types.FETCH_COST_INFO_COMPLETED, type: types.FETCH_COST_INFO_COMPLETED,
data: { data: {
@ -21,15 +53,25 @@ export function doFetchCostInfoForUri(uri) {
costInfo, costInfo,
} }
}) })
}).catch(() => { }
dispatch({
type: types.FETCH_COST_INFO_COMPLETED, if (isGenerous && claim) {
data: { let cost
uri, const fee = claim.value.stream.metadata.fee;
costInfo: {} if (fee === undefined ) {
} resolve({ cost: 0, includesData: true })
}) } else if (fee.currency == 'LBC') {
}) resolve({ cost: fee.amount, includesData: true })
} else {
begin()
lbryio.getExchangeRates().then(({lbc_usd}) => {
resolve({ cost: fee.amount / lbc_usd, includesData: true })
});
}
} else {
begin()
lbry.getCostInfo(uri).then(resolve)
}
} }
} }

View file

@ -1,11 +1,17 @@
import * as types from 'constants/action_types' import * as types from 'constants/action_types'
import lbry from 'lbry' import lbry from 'lbry'
import { import {
selectCurrentUri, doClaimListMine
} from 'selectors/app' } from 'actions/content'
import { import {
selectCurrentUriClaimOutpoint, selectClaimsByUri,
selectClaimListMineIsPending,
} from 'selectors/claims' } from 'selectors/claims'
import {
selectFileListIsPending,
selectAllFileInfos,
selectUrisLoading,
} from 'selectors/file_info'
import { import {
doCloseModal, doCloseModal,
} from 'actions/app' } from 'actions/app'
@ -14,29 +20,54 @@ const {
shell, shell,
} = require('electron') } = require('electron')
export function doFetchCurrentUriFileInfo() { export function doFetchFileInfo(uri) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
const uri = selectCurrentUri(state) const claim = selectClaimsByUri(state)[uri]
const outpoint = selectCurrentUriClaimOutpoint(state) const outpoint = claim ? `${claim.txid}:${claim.nout}` : null
const alreadyFetching = !!selectUrisLoading(state)[uri]
dispatch({ if (!alreadyFetching) {
type: types.FETCH_FILE_INFO_STARTED,
data: {
uri,
outpoint,
}
})
lbry.file_list({ outpoint: outpoint, full_status: true }).then(([fileInfo]) => {
dispatch({ dispatch({
type: types.FETCH_FILE_INFO_COMPLETED, type: types.FETCH_FILE_INFO_STARTED,
data: { data: {
uri, outpoint,
fileInfo,
} }
}) })
})
lbry.file_list({outpoint: outpoint, full_status: true}).then(fileInfos => {
dispatch({
type: types.FETCH_FILE_INFO_COMPLETED,
data: {
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,
}
})
})
}
} }
} }
@ -52,51 +83,39 @@ export function doOpenFileInFolder(fileInfo) {
} }
} }
export function doDeleteFile(uri, fileInfo, deleteFromComputer) { export function doDeleteFile(outpoint, deleteFromComputer) {
return function(dispatch, getState) { return function(dispatch, getState) {
dispatch({ dispatch({
type: types.DELETE_FILE_STARTED, type: types.FILE_DELETE,
data: { data: {
uri, outpoint
fileInfo,
deleteFromComputer,
} }
}) })
const successCallback = () => { lbry.file_delete({
dispatch({ outpoint: outpoint,
type: types.DELETE_FILE_COMPLETED, delete_target_file: deleteFromComputer,
data: {
uri,
}
})
dispatch(doCloseModal())
}
lbry.removeFile(fileInfo.outpoint, deleteFromComputer, successCallback)
}
}
export function doFetchDownloadedContent() {
return function(dispatch, getState) {
const state = getState()
dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_STARTED,
}) })
lbry.claim_list_mine().then((myClaimInfos) => { dispatch(doCloseModal())
lbry.file_list().then((fileInfos) => { }
const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); }
dispatch({
type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, export function doFetchFileInfosAndPublishedClaims() {
data: { return function(dispatch, getState) {
fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), const state = getState(),
} isClaimListMinePending = selectClaimListMineIsPending(state),
}) isFileInfoListPending = selectFileListIsPending(state)
});
}); if (isClaimListMinePending === undefined) {
dispatch(doClaimListMine())
}
if (isFileInfoListPending === undefined) {
dispatch(doFileList())
}
} }
} }

View file

@ -6,6 +6,7 @@ import {
} from 'actions/content' } from 'actions/content'
import { import {
doNavigate, doNavigate,
doHistoryPush
} from 'actions/app' } from 'actions/app'
import { import {
selectCurrentPage, selectCurrentPage,
@ -29,6 +30,8 @@ export function doSearch(query) {
if(page != 'search') { if(page != 'search') {
dispatch(doNavigate('search', { query: query })) dispatch(doNavigate('search', { query: query }))
} else {
dispatch(doHistoryPush({ query }, "Search for " + query, '/search'))
} }
lighthouse.search(query).then(results => { lighthouse.search(query).then(results => {

31
ui/js/actions/settings.js Normal file
View file

@ -0,0 +1,31 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
export function doFetchDaemonSettings() {
return function(dispatch, getState) {
lbry.get_settings().then((settings) => {
dispatch({
type: types.DAEMON_SETTINGS_RECEIVED,
data: {
settings
}
})
})
}
}
export function doSetDaemonSetting(key, value) {
return function(dispatch, getState) {
let settings = {};
settings[key] = value;
lbry.settings_set(settings).then(settings)
lbry.get_settings().then((settings) => {
dispatch({
type: types.DAEMON_SETTINGS_RECEIVED,
data: {
settings
}
})
})
}
}

View file

@ -2,30 +2,23 @@ import React from 'react';
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { import {
selectCurrentPage,
selectCurrentModal, selectCurrentModal,
selectDrawerOpen,
selectHeaderLinks,
selectSearchTerm,
} from 'selectors/app' } from 'selectors/app'
import { import {
doCheckUpgradeAvailable, doCheckUpgradeAvailable,
doOpenModal,
doCloseModal,
} from 'actions/app' } from 'actions/app'
import {
doUpdateBalance,
} from 'actions/wallet'
import App from './view' import App from './view'
const select = (state) => ({ const select = (state) => ({
currentPage: selectCurrentPage(state),
modal: selectCurrentModal(state), modal: selectCurrentModal(state),
headerLinks: selectHeaderLinks(state),
searchTerm: selectSearchTerm(state)
}) })
const perform = (dispatch) => ({ const perform = (dispatch) => ({
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
openModal: () => dispatch(doOpenModal()), updateBalance: (balance) => dispatch(doUpdateBalance(balance))
closeModal: () => dispatch(doCloseModal()),
}) })
export default connect(select, perform)(App) export default connect(select, perform)(App)

View file

@ -4,7 +4,8 @@ import Header from 'component/header';
import ErrorModal from 'component/errorModal' import ErrorModal from 'component/errorModal'
import DownloadingModal from 'component/downloadingModal' import DownloadingModal from 'component/downloadingModal'
import UpgradeModal from 'component/upgradeModal' import UpgradeModal from 'component/upgradeModal'
import {Line} from 'rc-progress'; import lbry from 'lbry'
import {Line} from 'rc-progress'
class App extends React.Component { class App extends React.Component {
componentWillMount() { componentWillMount() {
@ -15,12 +16,15 @@ class App extends React.Component {
if (!this.props.upgradeSkipped) { if (!this.props.upgradeSkipped) {
this.props.checkUpgradeAvailable() this.props.checkUpgradeAvailable()
} }
lbry.balanceSubscribe((balance) => {
this.props.updateBalance(balance)
})
} }
render() { render() {
const { const {
modal, modal,
headerLinks,
} = this.props } = this.props
return <div id="window"> return <div id="window">

View file

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

View file

@ -3,9 +3,6 @@ import {
connect, connect,
} from 'react-redux' } from 'react-redux'
import { import {
selectObscureNsfw,
selectHidePrice,
selectHasSignature,
selectPlatform, selectPlatform,
} from 'selectors/app' } from 'selectors/app'
import { import {
@ -14,7 +11,7 @@ import {
makeSelectLoadingForUri, makeSelectLoadingForUri,
} from 'selectors/file_info' } from 'selectors/file_info'
import { import {
makeSelectAvailabilityForUri, makeSelectIsAvailableForUri,
} from 'selectors/availability' } from 'selectors/availability'
import { import {
selectCurrentModal, selectCurrentModal,
@ -22,47 +19,50 @@ import {
import { import {
doCloseModal, doCloseModal,
doOpenModal, doOpenModal,
doHistoryBack,
} from 'actions/app' } from 'actions/app'
import {
doFetchAvailability
} from 'actions/availability'
import { import {
doOpenFileInShell, doOpenFileInShell,
doOpenFileInFolder, doOpenFileInFolder,
doDeleteFile, doDeleteFile,
} from 'actions/file_info' } from 'actions/file_info'
import { import {
doWatchVideo, doPurchaseUri,
doLoadVideo, doLoadVideo,
} from 'actions/content' } from 'actions/content'
import FileActions from './view' import FileActions from './view'
const makeSelect = () => { const makeSelect = () => {
const selectFileInfoForUri = makeSelectFileInfoForUri() const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectAvailabilityForUri = makeSelectAvailabilityForUri() const selectIsAvailableForUri = makeSelectIsAvailableForUri()
const selectDownloadingForUri = makeSelectDownloadingForUri() const selectDownloadingForUri = makeSelectDownloadingForUri()
const selectLoadingForUri = makeSelectLoadingForUri()
const select = (state, props) => ({ const select = (state, props) => ({
obscureNsfw: selectObscureNsfw(state),
hidePrice: selectHidePrice(state),
hasSignature: selectHasSignature(state),
fileInfo: selectFileInfoForUri(state, props), fileInfo: selectFileInfoForUri(state, props),
availability: selectAvailabilityForUri(state, props), isAvailable: selectIsAvailableForUri(state, props),
platform: selectPlatform(state), platform: selectPlatform(state),
modal: selectCurrentModal(state), modal: selectCurrentModal(state),
downloading: selectDownloadingForUri(state, props), downloading: selectDownloadingForUri(state, props),
loading: selectLoadingForUri(state, props),
}) })
return select return select
} }
const perform = (dispatch) => ({ const perform = (dispatch) => ({
checkAvailability: (uri) => dispatch(doFetchAvailability(uri)),
closeModal: () => dispatch(doCloseModal()), closeModal: () => dispatch(doCloseModal()),
openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)), openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)),
openInShell: (fileInfo) => dispatch(doOpenFileInShell(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)), openModal: (modal) => dispatch(doOpenModal(modal)),
downloadClick: () => dispatch(doWatchVideo()), startDownload: (uri) => dispatch(doPurchaseUri(uri)),
loadVideo: () => dispatch(doLoadVideo()) loadVideo: (uri) => dispatch(doLoadVideo(uri))
}) })
export default connect(makeSelect, perform)(FileActions) export default connect(makeSelect, perform)(FileActions)

View file

@ -1,7 +1,5 @@
import React from 'react'; import React from 'react';
import lbry from 'lbry'; import {Icon,BusyMessage} from 'component/common';
import lbryuri from 'lbryuri';
import {Icon,} from 'component/common';
import FilePrice from 'component/filePrice' import FilePrice from 'component/filePrice'
import {Modal} from 'component/modal'; import {Modal} from 'component/modal';
import {FormField} from 'component/form'; import {FormField} from 'component/form';
@ -9,14 +7,36 @@ import Link from 'component/link';
import {ToolTip} from 'component/tooltip'; import {ToolTip} from 'component/tooltip';
import {DropDownMenu, DropDownMenuItem} from 'component/menu'; import {DropDownMenu, DropDownMenuItem} from 'component/menu';
class FileActionsRow extends React.Component { class FileActions extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
forceShowActions: false,
deleteChecked: false, deleteChecked: false,
} }
} }
componentWillMount() {
this.checkAvailability(this.props.uri)
}
componentWillReceiveProps(nextProps) {
this.checkAvailability(nextProps.uri)
}
checkAvailability(uri) {
if (!this._uri || uri !== this._uri) {
this._uri = uri;
this.props.checkAvailability(uri)
}
}
onShowFileActionsRowClicked() {
this.setState({
forceShowActions: true,
});
}
handleDeleteCheckboxClicked(event) { handleDeleteCheckboxClicked(event) {
this.setState({ this.setState({
deleteChecked: event.target.checked, deleteChecked: event.target.checked,
@ -25,65 +45,73 @@ class FileActionsRow extends React.Component {
onAffirmPurchase() { onAffirmPurchase() {
this.props.closeModal() this.props.closeModal()
this.props.loadVideo() this.props.loadVideo(this.props.uri)
} }
render() { render() {
const { const {
fileInfo, fileInfo,
isAvailable,
platform, platform,
downloading, downloading,
loading,
uri, uri,
deleteFile, deleteFile,
openInFolder, openInFolder,
openInShell, openInShell,
modal, modal,
openModal, openModal,
affirmPurchase,
closeModal, closeModal,
downloadClick, startDownload,
} = this.props } = this.props
const { const deleteChecked = this.state.deleteChecked,
deleteChecked, metadata = fileInfo ? fileInfo.metadata : null,
} = this.state openInFolderMessage = platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
showMenu = fileInfo && Object.keys(fileInfo).length > 0,
title = metadata ? metadata.title : uri;
const metadata = fileInfo ? fileInfo.metadata : null let content
if (!fileInfo) if (downloading) {
{
return null;
}
const openInFolderMessage = platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
showMenu = Object.keys(fileInfo).length != 0;
let linkBlock;
if (Object.keys(fileInfo).length == 0 && !downloading && !loading) {
linkBlock = <Link button="text" label="Download" icon="icon-download" onClick={downloadClick} />;
} else if (downloading || loading) {
const const
progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>; labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
linkBlock = ( content = <div className="faux-button-block file-actions__download-status-bar button-set-item">
<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>
<div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div> {labelWithIcon}
{labelWithIcon} </div>
</div>
); } else if (!fileInfo && isAvailable === undefined) {
content = <BusyMessage message="Checking availability" />
} else if (!fileInfo && !isAvailable && !this.state.forceShowActions) {
content = <div>
<div className="button-set-item empty">Content unavailable.</div>
<ToolTip label="Why?"
body="The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment."
className="button-set-item" />
<Link label="Try Anyway" onClick={this.onShowFileActionsRowClicked.bind(this)} className="button-text button-set-item" />
</div>
} else if (fileInfo === null && !downloading) {
content = <Link button="text" label="Download" icon="icon-download" onClick={() => { startDownload(uri) } } />;
} else if (fileInfo && fileInfo.download_path) {
content = <Link label="Open" button="text" icon="icon-folder-open" onClick={() => openInShell(fileInfo)} />;
} else { } else {
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={() => openInShell(fileInfo)} />; console.log('handle this case of file action props?');
console.log(this.props)
} }
const title = metadata ? metadata.title : uri;
return ( return (
<div> <section className="file-actions">
{fileInfo !== null || fileInfo.isMine { content }
? linkBlock
: null}
{ showMenu ? { showMenu ?
<DropDownMenu> <DropDownMenu>
<DropDownMenuItem key={0} onClick={() => openInFolder(fileInfo)} label={openInFolderMessage} /> <DropDownMenuItem key={0} onClick={() => openInFolder(fileInfo)} label={openInFolderMessage} />
@ -91,7 +119,7 @@ class FileActionsRow extends React.Component {
</DropDownMenu> : '' } </DropDownMenu> : '' }
<Modal type="confirm" isOpen={modal == 'affirmPurchase'} <Modal type="confirm" isOpen={modal == 'affirmPurchase'}
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase.bind(this)} onAborted={closeModal}> contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase.bind(this)} onAborted={closeModal}>
Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits? This will purchase <strong>{title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits.
</Modal> </Modal>
<Modal isOpen={modal == 'notEnoughCredits'} contentLabel="Not enough credits" <Modal isOpen={modal == 'notEnoughCredits'} contentLabel="Not enough credits"
onConfirmed={closeModal}> onConfirmed={closeModal}>
@ -102,62 +130,18 @@ class FileActionsRow extends React.Component {
LBRY was unable to download the stream <strong>{uri}</strong>. LBRY was unable to download the stream <strong>{uri}</strong>.
</Modal> </Modal>
<Modal isOpen={modal == 'confirmRemove'} <Modal isOpen={modal == 'confirmRemove'}
contentLabel="Not enough credits" contentLabel="Not enough credits"
type="confirm" type="confirm"
confirmButtonLabel="Remove" confirmButtonLabel="Remove"
onConfirmed={() => deleteFile(uri, fileInfo, deleteChecked)} onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
onAborted={closeModal}> onAborted={closeModal}>
<p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p> <p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p>
<label><FormField type="checkbox" checked={deleteChecked} onClick={this.handleDeleteCheckboxClicked.bind(this)} /> Delete this file from my computer</label> <label><FormField type="checkbox" checked={deleteChecked} onClick={this.handleDeleteCheckboxClicked.bind(this)} /> Delete this file from my computer</label>
</Modal> </Modal>
</div> </section>
); );
} }
} }
class FileActions extends React.Component {
constructor(props) {
super(props)
this._isMounted = false
this._fileInfoSubscribeId = null
this.state = {
available: true,
forceShowActions: false,
fileInfo: null,
}
}
onShowFileActionsRowClicked() {
this.setState({
forceShowActions: true,
});
}
render() {
const {
fileInfo,
availability,
} = this.props
if (fileInfo === null) {
return null;
}
return (<section className="file-actions">
{
fileInfo || this.state.available || this.state.forceShowActions
? <FileActionsRow {...this.props} />
: <div>
<div className="button-set-item empty">Content unavailable.</div>
<ToolTip label="Why?"
body="The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment."
className="button-set-item" />
<Link label="Try Anyway" onClick={this.onShowFileActionsRowClicked.bind(this)} className="button-text button-set-item" />
</div>
}
</section>);
}
}
export default FileActions export default FileActions

View file

@ -7,21 +7,20 @@ import {
} from 'actions/app' } from 'actions/app'
import { import {
doResolveUri, doResolveUri,
doCancelResolveUri,
} from 'actions/content' } from 'actions/content'
import { import {
selectHidePrice,
selectObscureNsfw, selectObscureNsfw,
} from 'selectors/app' } from 'selectors/app'
import { import {
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectSourceForUri,
makeSelectMetadataForUri, makeSelectMetadataForUri,
} from 'selectors/claims' } from 'selectors/claims'
import { import {
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
} from 'selectors/file_info' } from 'selectors/file_info'
import { import {
makeSelectResolvingUri, makeSelectIsResolvingForUri,
} from 'selectors/content' } from 'selectors/content'
import FileCard from './view' import FileCard from './view'
@ -29,17 +28,13 @@ const makeSelect = () => {
const selectClaimForUri = makeSelectClaimForUri() const selectClaimForUri = makeSelectClaimForUri()
const selectFileInfoForUri = makeSelectFileInfoForUri() const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectMetadataForUri = makeSelectMetadataForUri() const selectMetadataForUri = makeSelectMetadataForUri()
const selectSourceForUri = makeSelectSourceForUri() const selectResolvingUri = makeSelectIsResolvingForUri()
const selectResolvingUri = makeSelectResolvingUri()
const select = (state, props) => ({ const select = (state, props) => ({
claim: selectClaimForUri(state, props), claim: selectClaimForUri(state, props),
fileInfo: selectFileInfoForUri(state, props), fileInfo: selectFileInfoForUri(state, props),
hidePrice: selectHidePrice(state),
obscureNsfw: selectObscureNsfw(state), obscureNsfw: selectObscureNsfw(state),
hasSignature: false,
metadata: selectMetadataForUri(state, props), metadata: selectMetadataForUri(state, props),
source: selectSourceForUri(state, props),
isResolvingUri: selectResolvingUri(state, props), isResolvingUri: selectResolvingUri(state, props),
}) })
@ -49,6 +44,7 @@ const makeSelect = () => {
const perform = (dispatch) => ({ const perform = (dispatch) => ({
navigate: (path, params) => dispatch(doNavigate(path, params)), navigate: (path, params) => dispatch(doNavigate(path, params)),
resolveUri: (uri) => dispatch(doResolveUri(uri)), resolveUri: (uri) => dispatch(doResolveUri(uri)),
cancelResolveUri: (uri) => dispatch(doCancelResolveUri(uri))
}) })
export default connect(makeSelect, perform)(FileCard) export default connect(makeSelect, perform)(FileCard)

View file

@ -2,21 +2,41 @@ 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 {Thumbnail, TruncatedText,} from 'component/common'; import {Thumbnail, TruncatedText, Icon} from 'component/common';
import FilePrice from 'component/filePrice' import FilePrice from 'component/filePrice'
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
class FileCard extends React.Component { class FileCard extends React.Component {
componentDidMount() { componentWillMount() {
this.resolve(this.props)
}
componentWillReceiveProps(nextProps) {
this.resolve(nextProps)
}
resolve(props) {
const { const {
isResolvingUri, isResolvingUri,
resolveUri, resolveUri,
claim, claim,
uri, uri,
} = props
if(!isResolvingUri && claim === undefined && uri) {
resolveUri(uri)
}
}
componentWillUnmount() {
const {
isResolvingUri,
cancelResolveUri,
uri
} = this.props } = this.props
if(!isResolvingUri && !claim && uri) { if (isResolvingUri) {
resolveUri(uri) cancelResolveUri(uri)
} }
} }
@ -35,10 +55,11 @@ class FileCard extends React.Component {
render() { render() {
const { const {
claim,
fileInfo,
metadata, metadata,
isResolvingUri, isResolvingUri,
navigate, navigate,
hidePrice,
} = this.props } = this.props
const uri = lbryuri.normalize(this.props.uri); const uri = lbryuri.normalize(this.props.uri);
@ -50,6 +71,8 @@ class FileCard extends React.Component {
description = "Loading..." description = "Loading..."
} else if (metadata && metadata.description) { } else if (metadata && metadata.description) {
description = metadata.description description = metadata.description
} else if (claim === null) {
description = 'This address contains no content.'
} }
return ( return (
@ -59,7 +82,10 @@ class FileCard extends React.Component {
<div className="card__title-identity"> <div className="card__title-identity">
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5> <h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
<div className="card__subtitle"> <div className="card__subtitle">
{ !hidePrice ? <span style={{float: "right"}}><FilePrice uri={uri} /></span> : null} <span style={{float: "right"}}>
<FilePrice uri={uri} />
{ fileInfo ? <span>{' '}<Icon fixed icon="icon-folder" /></span> : '' }
</span>
<UriIndicator uri={uri} /> <UriIndicator uri={uri} />
</div> </div>
</div> </div>

View file

@ -58,8 +58,8 @@ class FileList extends React.Component {
render() { render() {
const { const {
handleSortChanged, handleSortChanged,
fetching,
fileInfos, fileInfos,
hidePrices,
} = this.props } = this.props
const { const {
sortBy, sortBy,
@ -71,10 +71,11 @@ class FileList extends React.Component {
contentName: fileInfo.name, contentName: fileInfo.name,
channelName: fileInfo.channel_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 ( return (
<section> <section className="file-list__header">
{ fetching && <span className="busy-indicator"/> }
<span className='sort-section'> <span className='sort-section'>
Sort by { ' ' } Sort by { ' ' }
<FormField type="select" onChange={this.handleSortChanged.bind(this)}> <FormField type="select" onChange={this.handleSortChanged.bind(this)}>

View file

@ -2,6 +2,9 @@ import React from 'react'
import { import {
connect, connect,
} from 'react-redux' } from 'react-redux'
import {
doFetchCostInfoForUri,
} from 'actions/cost_info'
import { import {
makeSelectCostInfoForUri, makeSelectCostInfoForUri,
} from 'selectors/cost_info' } from 'selectors/cost_info'
@ -9,6 +12,7 @@ import FilePrice from './view'
const makeSelect = () => { const makeSelect = () => {
const selectCostInfoForUri = makeSelectCostInfoForUri() const selectCostInfoForUri = makeSelectCostInfoForUri()
const select = (state, props) => ({ const select = (state, props) => ({
costInfo: selectCostInfoForUri(state, props), costInfo: selectCostInfoForUri(state, props),
}) })
@ -17,6 +21,8 @@ const makeSelect = () => {
} }
const perform = (dispatch) => ({ const perform = (dispatch) => ({
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
// cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(uri))
}) })
export default connect(makeSelect, perform)(FilePrice) export default connect(makeSelect, perform)(FilePrice)

View file

@ -3,19 +3,41 @@ import {
CreditAmount, CreditAmount,
} from 'component/common' } from 'component/common'
const FilePrice = (props) => { class FilePrice extends React.Component{
const { componentWillMount() {
costInfo, this.fetchCost(this.props)
look = 'indicator',
} = props
const isEstimate = costInfo ? !costInfo.includesData : null
if (!costInfo) {
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
} }
return <CreditAmount label={false} amount={costInfo.cost} isEstimate={isEstimate} showFree={true} /> componentWillReceiveProps(nextProps) {
this.fetchCost(nextProps)
}
fetchCost(props) {
const {
costInfo,
fetchCostInfo,
uri
} = props
if (costInfo === undefined) {
fetchCostInfo(uri)
}
}
render() {
const {
costInfo,
look = 'indicator',
} = this.props
const isEstimate = costInfo ? !costInfo.includesData : null
if (!costInfo) {
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
}
return <CreditAmount label={false} amount={costInfo.cost} isEstimate={isEstimate} showFree={true} />
}
} }
export default FilePrice export default FilePrice

View file

@ -10,41 +10,30 @@ import {
} from 'actions/content' } from 'actions/content'
import { import {
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectSourceForUri,
makeSelectMetadataForUri, makeSelectMetadataForUri,
} from 'selectors/claims' } from 'selectors/claims'
import { import {
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
} from 'selectors/file_info' } from 'selectors/file_info'
import {
makeSelectFetchingAvailabilityForUri,
makeSelectAvailabilityForUri,
} from 'selectors/availability'
import { import {
selectObscureNsfw, selectObscureNsfw,
} from 'selectors/app' } from 'selectors/app'
import { import {
makeSelectResolvingUri, makeSelectIsResolvingForUri,
} from 'selectors/content' } from 'selectors/content'
import FileTile from './view' import FileTile from './view'
const makeSelect = () => { const makeSelect = () => {
const selectClaimForUri = makeSelectClaimForUri() const selectClaimForUri = makeSelectClaimForUri()
const selectFileInfoForUri = makeSelectFileInfoForUri() const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectFetchingAvailabilityForUri = makeSelectFetchingAvailabilityForUri()
const selectAvailabilityForUri = makeSelectAvailabilityForUri()
const selectMetadataForUri = makeSelectMetadataForUri() const selectMetadataForUri = makeSelectMetadataForUri()
const selectSourceForUri = makeSelectSourceForUri() const selectResolvingUri = makeSelectIsResolvingForUri()
const selectResolvingUri = makeSelectResolvingUri()
const select = (state, props) => ({ const select = (state, props) => ({
claim: selectClaimForUri(state, props), claim: selectClaimForUri(state, props),
fileInfo: selectFileInfoForUri(state, props), fileInfo: selectFileInfoForUri(state, props),
fetchingAvailability: selectFetchingAvailabilityForUri(state, props),
selectAvailabilityForUri: selectAvailabilityForUri(state, props),
obscureNsfw: selectObscureNsfw(state), obscureNsfw: selectObscureNsfw(state),
metadata: selectMetadataForUri(state, props), metadata: selectMetadataForUri(state, props),
source: selectSourceForUri(state, props),
isResolvingUri: selectResolvingUri(state, props), isResolvingUri: selectResolvingUri(state, props),
}) })

View file

@ -13,11 +13,8 @@ class FileTile extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this._fileInfoSubscribeId = null
this._isMounted = null
this.state = { this.state = {
showNsfwHelp: false, showNsfwHelp: false,
isHidden: false,
} }
} }
@ -29,31 +26,11 @@ class FileTile extends React.Component {
uri, uri,
} = this.props } = this.props
this._isMounted = true;
if (this.props.hideOnRemove) {
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
}
if(!isResolvingUri && !claim && uri) { if(!isResolvingUri && !claim && uri) {
resolveUri(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() { handleMouseOver() {
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
this.setState({ this.setState({
@ -71,10 +48,6 @@ class FileTile extends React.Component {
} }
render() { render() {
if (this.state.isHidden) {
return null;
}
const { const {
claim, claim,
metadata, metadata,
@ -86,18 +59,22 @@ class FileTile extends React.Component {
const uri = lbryuri.normalize(this.props.uri); const uri = lbryuri.normalize(this.props.uri);
const isClaimed = !!claim; const isClaimed = !!claim;
const isClaimable = lbryuri.isClaimable(uri)
const title = isClaimed && metadata && metadata.title ? metadata.title : uri; const title = isClaimed && metadata && metadata.title ? metadata.title : uri;
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
let onClick = () => navigate('/show', { uri }) let onClick = () => navigate('/show', { uri })
let description = "" let description = ""
if (isClaimed) { if (isClaimed) {
description = metadata.description description = metadata && metadata.description
} else if (isResolvingUri) { } else if (isResolvingUri) {
description = "Loading..." description = "Loading..."
} else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) { } else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) {
onClick = () => navigate('/publish') onClick = () => navigate('/publish', { })
description = <span className="empty">This location is unclaimed - <span className="button-text">put something here</span>!</span> description = <span className="empty">
This location is unused. { ' ' }
{ isClaimable && <span className="button-text">Put something here!</span> }
</span>
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) { } else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
description = <span className="empty">This file is pending confirmation.</span> description = <span className="empty">This file is pending confirmation.</span>
} }

View file

@ -2,14 +2,13 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Router from './view.jsx'; import Router from './view.jsx';
import { import {
selectCurrentPage selectCurrentPage,
selectCurrentParams,
} from 'selectors/app.js'; } from 'selectors/app.js';
const select = (state) => ({ const select = (state) => ({
params: selectCurrentParams(state),
currentPage: selectCurrentPage(state) currentPage: selectCurrentPage(state)
}) })
const perform = {
}
export default connect(select, null)(Router); export default connect(select, null)(Router);

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import SettingsPage from 'page/settings.js'; import SettingsPage from 'page/settings';
import HelpPage from 'page/help'; import HelpPage from 'page/help';
import ReportPage from 'page/report.js'; import ReportPage from 'page/report.js';
import StartPage from 'page/start.js'; import StartPage from 'page/start.js';
@ -25,25 +25,26 @@ const route = (page, routesMap) => {
const Router = (props) => { const Router = (props) => {
const { const {
currentPage, currentPage,
params,
} = props; } = props;
return route(currentPage, { return route(currentPage, {
'settings': <SettingsPage {...props} />, 'settings': <SettingsPage {...params} />,
'help': <HelpPage {...props} />, 'help': <HelpPage {...params} />,
'report': <ReportPage {...props} />, 'report': <ReportPage {...params} />,
'downloaded': <FileListDownloaded {...props} />, 'downloaded': <FileListDownloaded {...params} />,
'published': <FileListPublished {...props} />, 'published': <FileListPublished {...params} />,
'start': <StartPage {...props} />, 'start': <StartPage {...params} />,
'wallet': <WalletPage {...props} />, 'wallet': <WalletPage {...params} />,
'send': <WalletPage {...props} />, 'send': <WalletPage {...params} />,
'receive': <WalletPage {...props} />, 'receive': <WalletPage {...params} />,
'show': <ShowPage {...props} />, 'show': <ShowPage {...params} />,
'channel': <ChannelPage {...props} />, 'channel': <ChannelPage {...params} />,
'publish': <PublishPage {...props} />, 'publish': <PublishPage {...params} />,
'developer': <DeveloperPage {...props} />, 'developer': <DeveloperPage {...params} />,
'discover': <DiscoverPage {...props} />, 'discover': <DiscoverPage {...params} />,
'rewards': <RewardsPage {...props} />, 'rewards': <RewardsPage {...params} />,
'search': <SearchPage {...props} />, 'search': <SearchPage {...params} />,
}) })
} }

View file

@ -9,34 +9,46 @@ import {
selectCurrentModal, selectCurrentModal,
} from 'selectors/app' } from 'selectors/app'
import { import {
doWatchVideo, doPurchaseUri,
doLoadVideo, doLoadVideo,
} from 'actions/content' } from 'actions/content'
import { import {
selectLoadingCurrentUri, makeSelectMetadataForUri
selectCurrentUriFileReadyToPlay, } from 'selectors/claims'
selectCurrentUriIsPlaying, import {
selectCurrentUriFileInfo, makeSelectFileInfoForUri,
selectDownloadingCurrentUri, makeSelectLoadingForUri,
makeSelectDownloadingForUri,
} from 'selectors/file_info' } from 'selectors/file_info'
import { import {
selectCurrentUriCostInfo, makeSelectCostInfoForUri,
} from 'selectors/cost_info' } from 'selectors/cost_info'
import Video from './view' import Video from './view'
const select = (state) => ({
costInfo: selectCurrentUriCostInfo(state), const makeSelect = () => {
fileInfo: selectCurrentUriFileInfo(state), const selectCostInfo = makeSelectCostInfoForUri()
modal: selectCurrentModal(state), const selectFileInfo = makeSelectFileInfoForUri()
isLoading: selectLoadingCurrentUri(state), const selectIsLoading = makeSelectLoadingForUri()
readyToPlay: selectCurrentUriFileReadyToPlay(state), const selectIsDownloading = makeSelectDownloadingForUri()
isDownloading: selectDownloadingCurrentUri(state), const selectMetadata = makeSelectMetadataForUri()
})
const select = (state, props) => ({
costInfo: selectCostInfo(state, props),
fileInfo: selectFileInfo(state, props),
metadata: selectMetadata(state, props),
modal: selectCurrentModal(state),
isLoading: selectIsLoading(state, props),
isDownloading: selectIsDownloading(state, props),
})
return select
}
const perform = (dispatch) => ({ const perform = (dispatch) => ({
loadVideo: () => dispatch(doLoadVideo()), loadVideo: (uri) => dispatch(doLoadVideo(uri)),
watchVideo: (elem) => dispatch(doWatchVideo()), purchaseUri: (uri) => dispatch(doPurchaseUri(uri)),
closeModal: () => dispatch(doCloseModal()), closeModal: () => dispatch(doCloseModal()),
}) })
export default connect(select, perform)(Video) export default connect(makeSelect, perform)(Video)

View file

@ -1,17 +1,21 @@
import React from 'react'; import React from 'react';
import {
Icon,
Thumbnail,
} from 'component/common';
import FilePrice from 'component/filePrice' import FilePrice from 'component/filePrice'
import Link from 'component/link'; import Link from 'component/link';
import Modal from 'component/modal'; import Modal from 'component/modal';
class WatchLink extends React.Component { class VideoPlayButton extends React.Component {
confirmPurchaseClick() { onPurchaseConfirmed() {
this.props.closeModal() this.props.closeModal()
this.props.startPlaying() this.props.startPlaying()
this.props.loadVideo() this.props.loadVideo(this.props.uri)
}
onWatchClick() {
this.props.purchaseUri(this.props.uri).then(() => {
if (!this.props.modal) {
this.props.startPlaying()
}
})
} }
render() { render() {
@ -19,7 +23,6 @@ class WatchLink extends React.Component {
button, button,
label, label,
className, className,
onWatchClick,
metadata, metadata,
metadata: { metadata: {
title, title,
@ -32,13 +35,21 @@ class WatchLink extends React.Component {
fileInfo, fileInfo,
} = this.props } = this.props
/*
title={
isLoading ? "Video is Loading" :
!costInfo ? "Waiting on cost info..." :
fileInfo === undefined ? "Waiting on file info..." : ""
}
*/
return (<div> return (<div>
<Link button={ button ? button : null } <Link button={ button ? button : null }
disabled={isLoading || costInfo.cost == undefined || fileInfo === undefined} disabled={isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined))}
label={label ? label : ""} label={label ? label : ""}
className="video__play-button" className="video__play-button"
icon="icon-play" icon="icon-play"
onClick={onWatchClick} /> onClick={this.onWatchClick.bind(this)} />
{modal} {modal}
<Modal contentLabel="Not enough credits" isOpen={modal == 'notEnoughCredits'} onConfirmed={closeModal}> <Modal contentLabel="Not enough credits" isOpen={modal == 'notEnoughCredits'} onConfirmed={closeModal}>
You don't have enough LBRY credits to pay for this stream. You don't have enough LBRY credits to pay for this stream.
@ -47,9 +58,9 @@ class WatchLink extends React.Component {
type="confirm" type="confirm"
isOpen={modal == 'affirmPurchase'} isOpen={modal == 'affirmPurchase'}
contentLabel="Confirm Purchase" contentLabel="Confirm Purchase"
onConfirmed={this.confirmPurchaseClick.bind(this)} onConfirmed={this.onPurchaseConfirmed.bind(this)}
onAborted={closeModal}> onAborted={closeModal}>
Are you sure you'd like to buy <strong>{this.props.metadata.title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits? This will purchase <strong>{title}</strong> for <strong><FilePrice uri={uri} look="plain" /></strong> credits.
</Modal> </Modal>
<Modal <Modal
isOpen={modal == 'timedOut'} onConfirmed={closeModal} contentLabel="Timed Out"> isOpen={modal == 'timedOut'} onConfirmed={closeModal} contentLabel="Timed Out">
@ -67,16 +78,6 @@ class Video extends React.Component {
this.state = { isPlaying: false } this.state = { isPlaying: false }
} }
onWatchClick() {
this.props.watchVideo().then(() => {
if (!this.props.modal) {
this.setState({
isPlaying: true
})
}
})
}
startPlaying() { startPlaying() {
this.setState({ this.setState({
isPlaying: true isPlaying: true
@ -85,8 +86,6 @@ class Video extends React.Component {
render() { render() {
const { const {
readyToPlay = false,
thumbnail,
metadata, metadata,
isLoading, isLoading,
isDownloading, isDownloading,
@ -96,6 +95,8 @@ class Video extends React.Component {
isPlaying = false, isPlaying = false,
} = this.state } = this.state
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0
let loadStatusMessage = '' let loadStatusMessage = ''
if (isLoading) { if (isLoading) {
@ -105,39 +106,42 @@ class Video extends React.Component {
} }
return ( return (
<div className={"video " + this.props.className + (isPlaying && readyToPlay ? " video--active" : " video--hidden")}>{ <div className={"video " + this.props.className + (isPlaying ? " video--active" : " video--hidden")}>{
isPlaying ? isPlaying || isLoading ?
!readyToPlay ? (!isReadyToPlay ?
<span>this is the world's worst loading screen and we shipped our software with it anyway... <br /><br />{loadStatusMessage}</span> : <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} /> : <VideoPlayer poster={metadata.thumbnail} autoplay={isPlaying} downloadPath={fileInfo.download_path} />) :
<div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}> <div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}>
<WatchLink icon="icon-play" onWatchClick={this.onWatchClick.bind(this)} <VideoPlayButton startPlaying={this.startPlaying.bind(this)} {...this.props} />
startPlaying={this.startPlaying.bind(this)} {...this.props}></WatchLink>
</div> </div>
}</div> }</div>
); );
} }
} }
class VideoPlayer extends React.PureComponent { class VideoPlayer extends React.Component {
componentDidMount() { componentDidMount() {
const elem = this.refs.video const elem = this.refs.video
const { const {
autoplay,
downloadPath, downloadPath,
contentType, contentType,
} = this.props } = this.props
const players = plyr.setup(elem) const players = plyr.setup(elem)
players[0].play() if (autoplay) {
players[0].play()
}
} }
render() { render() {
const { const {
downloadPath, downloadPath,
contentType, contentType,
poster,
} = this.props } = this.props
return ( return (
<video controls id="video" ref="video"> <video controls id="video" ref="video" style={{backgroundImage: "url('" + poster + "')"}} >
<source src={downloadPath} type={contentType} /> <source src={downloadPath} type={contentType} />
</video> </video>
) )

View file

@ -99,10 +99,18 @@ class WunderBar extends React.PureComponent {
} }
componentDidUpdate() { componentDidUpdate() {
this._input.value = this.state.address; if (this._input) {
if (this._input && this._focusPending) { const start = this._input.selectionStart,
this._input.select(); end = this._input.selectionEnd;
this._focusPending = false;
this._input.value = this.state.address; //this causes cursor to go to end of input
this._input.setSelectionRange(start, end);
if (this._focusPending) {
this._input.select();
this._focusPending = false;
}
} }
} }

View file

@ -35,10 +35,13 @@ export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED'
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED' export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED'
export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED' export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED'
export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED' export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED'
export const FETCH_DOWNLOADED_CONTENT_STARTED = 'FETCH_DOWNLOADED_CONTENT_STARTED' export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED'
export const FETCH_DOWNLOADED_CONTENT_COMPLETED = 'FETCH_DOWNLOADED_CONTENT_COMPLETED' export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'
export const FETCH_PUBLISHED_CONTENT_STARTED = 'FETCH_PUBLISHED_CONTENT_STARTED' export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'
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_STARTED = 'FETCH_FILE_INFO_STARTED'
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED' export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED' export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'
@ -52,11 +55,12 @@ export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED' export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED' export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED' export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'
export const DELETE_FILE_STARTED = 'DELETE_FILE_STARTED' export const FILE_DELETE = 'FILE_DELETE'
export const DELETE_FILE_COMPLETED = 'DELETE_FILE_COMPLETED'
export const FETCH_MY_CLAIMS_COMPLETED = 'FETCH_MY_CLAIMS_COMPLETED'
// Search // Search
export const SEARCH_STARTED = 'SEARCH_STARTED' export const SEARCH_STARTED = 'SEARCH_STARTED'
export const SEARCH_COMPLETED = 'SEARCH_COMPLETED' export const SEARCH_COMPLETED = 'SEARCH_COMPLETED'
export const SEARCH_CANCELLED = 'SEARCH_CANCELLED' export const SEARCH_CANCELLED = 'SEARCH_CANCELLED'
// Settings
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED'

View file

@ -68,6 +68,8 @@ jsonrpc.call = function (connectionString, method, params, callback, errorCallba
})); }));
sessionStorage.setItem('JSONRPCCounter', counter + 1); sessionStorage.setItem('JSONRPCCounter', counter + 1);
return xhr
}; };
export default jsonrpc; export default jsonrpc;

View file

@ -7,6 +7,25 @@ import {getLocal, getSession, setSession, setLocal} from './utils.js';
const {remote, ipcRenderer} = require('electron'); const {remote, ipcRenderer} = require('electron');
const menu = remote.require('./menu/main-menu'); 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 * 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. * 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)); 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 * Gets the current list of pending publish attempts. Filters out any that have timed out and
* removes them from the list. * removes them from the list.
*/ */
function getPendingPublishes() { lbry.getPendingPublishes = function() {
const pendingPublishes = getLocal('pendingPublishes') || []; const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout); const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout);
setLocal('pendingPublishes', newPendingPublishes); setLocal('pendingPublishes', newPendingPublishes);
@ -59,7 +78,7 @@ function getPendingPublishes() {
* provided along withe the name. If no pending publish is found, returns null. * provided along withe the name. If no pending publish is found, returns null.
*/ */
function getPendingPublish({name, channel_name, outpoint}) { function getPendingPublish({name, channel_name, outpoint}) {
const pendingPublishes = getPendingPublishes(); const pendingPublishes = lbry.getPendingPublishes();
return pendingPublishes.find( return pendingPublishes.find(
pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name)) pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name))
) || null; ) || null;
@ -74,28 +93,8 @@ function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) {
} }
window.pptdfi = pendingPublishToDummyFileInfo; 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) { lbry.call = function (method, params, callback, errorCallback, connectFailedCallback) {
jsonrpc.call(lbry.daemonConnectionString, method, params, callback, errorCallback, connectFailedCallback); return jsonrpc.call(lbry.daemonConnectionString, method, params, callback, errorCallback, connectFailedCallback);
} }
//core //core
@ -151,69 +150,14 @@ lbry.isDaemonAcceptingConnections = function (callback) {
lbry.call('status', {}, () => callback(true), null, () => callback(false)) 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.checkAddressIsMine = function(address, callback) {
lbry.call('address_is_mine', {address: address}, callback); lbry.call('address_is_mine', {address: address}, callback);
} }
lbry.getDaemonSettings = function(callback) {
lbry.call('get_settings', {}, callback);
}
lbry.setDaemonSettings = function(settings, callback) {
lbry.call('set_settings', settings, callback);
}
lbry.setDaemonSetting = function(setting, value, callback) {
var setSettingsArgs = {};
setSettingsArgs[setting] = value;
lbry.call('set_settings', setSettingsArgs, callback)
}
lbry.getBalance = function(callback) {
lbry.call("wallet_balance", {}, callback);
}
lbry.sendToAddress = function(amount, address, callback, errorCallback) { lbry.sendToAddress = function(amount, address, callback, errorCallback) {
lbry.call("send_amount_to_address", { "amount" : amount, "address": 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);
}
});
}
/** /**
* Takes a LBRY URI; will first try and calculate a total cost using * Takes a LBRY URI; will first try and calculate a total cost using
* Lighthouse. If Lighthouse can't be reached, it just retrives the * Lighthouse. If Lighthouse can't be reached, it just retrives the
@ -251,83 +195,22 @@ lbry.getCostInfo = function(uri) {
}, reject); }, reject);
} }
function getCostGenerous(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) => {
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 uriObj = lbryuri.parse(uri);
const name = uriObj.path || uriObj.name; const name = uriObj.path || uriObj.name;
lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => { lighthouse.get_size_for_name(name).then((size) => {
if (is_generous_host) { if (size) {
return getCostGenerous(uri); getCost(name, size);
} }
else {
lighthouse.get_size_for_name(name).then((size) => {
if (size) {
getCost(name, size);
}
else {
getCost(name, null);
}
}, () => {
getCost(name, null); getCost(name, null);
}); }
}); })
}); });
} }
return lbry.costPromiseCache[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 * Publishes a file. The optional fileListedCallback is called when the file becomes available in
* lbry.file_list() during the publish process. * lbry.file_list() during the publish process.
@ -368,10 +251,6 @@ lbry.publish = function(params, fileListedCallback, publishedCallback, errorCall
fileListedCallback(true); fileListedCallback(true);
} }
}, 2000); }, 2000);
//lbry.getFileInfoWhenListed(params.name, function(fileInfo) {
// fileListedCallback(fileInfo);
//});
} }
@ -426,29 +305,10 @@ lbry.formatName = function(name) {
return 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) lbry.imagePath = function(file)
{ {
return lbry.rootPath + '/img/' + file; return 'img/' + file;
} }
lbry.getMediaType = function(contentType, fileName) { lbry.getMediaType = function(contentType, fileName) {
@ -479,71 +339,9 @@ lbry.stop = function(callback) {
lbry.call('stop', {}, callback); lbry.call('stop', {}, callback);
}; };
lbry.fileInfo = {};
lbry._subscribeIdCount = 0; lbry._subscribeIdCount = 0;
lbry._fileInfoSubscribeCallbacks = {};
lbry._fileInfoSubscribeInterval = 500000;
lbry._balanceSubscribeCallbacks = {}; lbry._balanceSubscribeCallbacks = {};
lbry._balanceSubscribeInterval = 5000; 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._balanceUpdateInterval = null;
lbry._updateBalanceSubscribers = function() { lbry._updateBalanceSubscribers = function() {
@ -620,7 +418,7 @@ lbry.file_list = function(params={}) {
lbry.call('file_list', params, (fileInfos) => { lbry.call('file_list', params, (fileInfos) => {
removePendingPublishIfNeeded({name, channel_name, outpoint}); removePendingPublishIfNeeded({name, channel_name, outpoint});
const dummyFileInfos = getPendingPublishes().map(pendingPublishToDummyFileInfo); const dummyFileInfos = lbry.getPendingPublishes().map(pendingPublishToDummyFileInfo);
resolve([...fileInfos, ...dummyFileInfos]); resolve([...fileInfos, ...dummyFileInfos]);
}, reject, reject); }, reject, reject);
}); });
@ -633,14 +431,15 @@ lbry.claim_list_mine = function(params={}) {
removePendingPublishIfNeeded({name, channel_name, outpoint: txid + ':' + nout}); removePendingPublishIfNeeded({name, channel_name, outpoint: txid + ':' + nout});
} }
const dummyClaims = getPendingPublishes().map(pendingPublishToDummyClaim); const dummyClaims = lbry.getPendingPublishes().map(pendingPublishToDummyClaim);
resolve([...claims, ...dummyClaims]); resolve([...claims, ...dummyClaims]);
}, reject, reject) }, reject, reject)
}); });
} }
const claimCacheKey = 'resolve_claim_cache'; const claimCacheKey = 'resolve_claim_cache';
lbry._claimCache = getLocal(claimCacheKey, {}); lbry._claimCache = getSession(claimCacheKey, {});
lbry._resolveXhrs = {}
lbry.resolve = function(params={}) { lbry.resolve = function(params={}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!params.uri) { if (!params.uri) {
@ -649,28 +448,22 @@ lbry.resolve = function(params={}) {
if (params.uri && lbry._claimCache[params.uri] !== undefined) { if (params.uri && lbry._claimCache[params.uri] !== undefined) {
resolve(lbry._claimCache[params.uri]); resolve(lbry._claimCache[params.uri]);
} else { } else {
lbry.call('resolve', params, function(data) { lbry._resolveXhrs[params.uri] = lbry.call('resolve', params, function(data) {
lbry._claimCache[params.uri] = data; if (data !== undefined) {
setLocal(claimCacheKey, lbry._claimCache) lbry._claimCache[params.uri] = data;
}
setSession(claimCacheKey, lbry._claimCache)
resolve(data) resolve(data)
}, reject) }, reject)
} }
}); });
} }
// Adds caching. lbry.cancelResolve = function(params={}) {
lbry._settingsPromise = null; const xhr = lbry._resolveXhrs[params.uri]
lbry.settings_get = function(params={}) { if (xhr && xhr.readyState > 0 && xhr.readyState < 4) {
if (params.allow_cached && lbry._settingsPromise) { xhr.abort()
return lbry._settingsPromise;
} }
lbry._settingsPromise = new Promise((resolve, reject) => {
lbry.call('settings_get', {}, (settings) => {
setSession('settings', settings);
resolve(settings);
}, reject);
});
return lbry._settingsPromise;
} }
// lbry.get = function(params={}) { // lbry.get = function(params={}) {

View file

@ -13,22 +13,19 @@ const lbryio = {
const CONNECTION_STRING = 'https://api.lbry.io/'; const CONNECTION_STRING = 'https://api.lbry.io/';
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000; const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
lbryio._exchangePromise = null;
lbryio._exchangeLastFetched = null;
lbryio.getExchangeRates = function() { lbryio.getExchangeRates = function() {
return new Promise((resolve, reject) => { if (!lbryio._exchangeLastFetched || Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
const cached = getSession('exchangeRateCache'); lbryio._exchangePromise = new Promise((resolve, reject) => {
if (!cached || Date.now() - cached.time > EXCHANGE_RATE_TIMEOUT) {
lbryio.call('lbc', 'exchange_rate', {}, 'get', true).then(({lbc_usd, lbc_btc, btc_usd}) => { lbryio.call('lbc', 'exchange_rate', {}, 'get', true).then(({lbc_usd, lbc_btc, btc_usd}) => {
const rates = {lbc_usd, lbc_btc, btc_usd}; const rates = {lbc_usd, lbc_btc, btc_usd};
setSession('exchangeRateCache', {
rates: rates,
time: Date.now(),
});
resolve(rates); resolve(rates);
}); }).catch(reject);
} else { });
resolve(cached.rates); lbryio._exchangeLastFetched = Date.now();
} }
}); return lbryio._exchangePromise;
} }
lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled=false) { // evenIfDisabled is just for development, when we may have some calls working and some not lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled=false) { // evenIfDisabled is just for development, when we may have some calls working and some not

View file

@ -165,5 +165,30 @@ lbryuri.normalize= function(uri) {
return lbryuri.build({name, path, claimSequence, bidPosition, claimId}); return lbryuri.build({name, path, claimSequence, bidPosition, claimId});
} }
lbryuri.isValid = function(uri) {
let parts
try {
parts = lbryuri.parse(lbryuri.normalize(uri))
} catch (error) {
return false;
}
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 {
parts = lbryuri.parse(lbryuri.normalize(uri))
} catch (error) {
return false;
}
return parts && parts.name && !parts.claimId && !parts.bidPosition && !parts.claimSequence && !parts.isChannel && !parts.path;
}
window.lbryuri = lbryuri; window.lbryuri = lbryuri;
export default lbryuri; export default lbryuri;

View file

@ -10,9 +10,16 @@ import {AuthOverlay} from './component/auth.js';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import store from 'store.js'; import store from 'store.js';
import { import {
doDaemonReady,
doChangePath, doChangePath,
doDaemonReady,
doHistoryPush
} from 'actions/app' } from 'actions/app'
import {
doFetchDaemonSettings
} from 'actions/settings'
import {
doFileList
} from 'actions/file_info'
import parseQueryParams from 'util/query_params' import parseQueryParams from 'util/query_params'
const {remote, ipcRenderer} = require('electron'); const {remote, ipcRenderer} = require('electron');
@ -27,16 +34,22 @@ window.addEventListener('contextmenu', (event) => {
event.preventDefault(); event.preventDefault();
}); });
window.addEventListener('popstate', (event) => { window.addEventListener('popstate', (event, param) => {
const pathname = document.location.pathname
const queryString = document.location.search const queryString = document.location.search
if (pathname.match(/dist/)) return const pathParts = document.location.pathname.split('/')
const route = '/' + pathParts[pathParts.length - 1]
app.store.dispatch(doChangePath(`${pathname}${queryString}`)) 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}`))
}) })
ipcRenderer.on('open-uri-requested', (event, uri) => { ipcRenderer.on('open-uri-requested', (event, uri) => {
console.log('FIX ME do magic dispatch'); if (uri) {
console.log('FIX ME do magic dispatch: ' + uri);
}
}); });
const initialState = app.store.getState(); const initialState = app.store.getState();
@ -46,7 +59,9 @@ var init = function() {
function onDaemonReady() { function onDaemonReady() {
app.store.dispatch(doDaemonReady()) app.store.dispatch(doDaemonReady())
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
window.history.pushState({}, "Discover", '/discover'); 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) ReactDOM.render(<Provider store={store}><div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div></Provider>, canvas)
} }

View file

@ -3,15 +3,31 @@ import {
connect connect
} from 'react-redux' } from 'react-redux'
import { import {
selectCurrentUriTitle, doFetchClaimsByChannel
} from 'selectors/app' } from 'actions/content'
import {
makeSelectClaimForUri,
makeSelectClaimsInChannelForUri
} from 'selectors/claims'
import ChannelPage from './view' import ChannelPage from './view'
const select = (state) => ({ import FilePage from './view'
title: selectCurrentUriTitle(state)
}) const makeSelect = () => {
const selectClaim = makeSelectClaimForUri(),
selectClaimsInChannel = makeSelectClaimsInChannelForUri()
const select = (state, props) => ({
claim: selectClaim(state, props),
claimsInChannel: selectClaimsInChannel(state, props)
})
return select
}
const perform = (dispatch) => ({ const perform = (dispatch) => ({
// fetchClaims: () => { console.log('fetch claims') }
fetchClaims: (uri) => dispatch(doFetchClaimsByChannel(uri))
}) })
export default connect(select, perform)(ChannelPage) export default connect(makeSelect, perform)(ChannelPage)

View file

@ -1,22 +1,49 @@
import React from 'react'; import React from 'react';
import lbryuri from 'lbryuri'
const ChannelPage = (props) => { class ChannelPage extends React.Component{
const { componentDidMount() {
title this.fetchClaims(this.props)
} = props }
return <main className="main--single-column"> componentWillReceiveProps(nextProps) {
<section className="card"> this.fetchClaims(nextProps)
<div className="card__inner"> }
<div className="card__title-identity"><h1>{title}</h1></div>
</div> fetchClaims(props) {
<div className="card__content"> if (props.claimsInChannel === undefined) {
<p> props.fetchClaims(props.uri)
This channel page is a stub. }
</p> }
</div>
</section> render() {
</main> const {
claimsInChannel,
claim,
uri
} = this.props
console.log(claimsInChannel);
return <main className="main--single-column">
<section className="card">
<div className="card__inner">
<div className="card__title-identity"><h1>{uri}</h1></div>
</div>
<div className="card__content">
<p>
This channel page is a stub.
</p>
</div>
</section>
<section className="card">
<div className="card__content">
{claimsInChannel ?
claimsInChannel.map((claim) => <FileTile uri={lbryuri.build({name: claim.name, claimId: claim.claim_id})} /> )
: ''}
</div>
</section>
</main>
}
} }
export default ChannelPage; export default ChannelPage;

View file

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

View file

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

View file

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

View file

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

View file

@ -3,29 +3,41 @@ import {
connect connect
} from 'react-redux' } from 'react-redux'
import { import {
selectCurrentUri, doFetchFileInfo,
} from 'selectors/app' } from 'actions/file_info'
import { import {
selectCurrentUriFileInfo, makeSelectFileInfoForUri,
selectCurrentUriIsDownloaded,
} from 'selectors/file_info' } from 'selectors/file_info'
import { import {
selectCurrentUriClaim, makeSelectClaimForUri,
makeSelectContentTypeForUri,
makeSelectMetadataForUri,
} from 'selectors/claims' } from 'selectors/claims'
import { import {
selectCurrentUriCostInfo, makeSelectCostInfoForUri,
} from 'selectors/cost_info' } from 'selectors/cost_info'
import FilePage from './view' import FilePage from './view'
const select = (state) => ({ const makeSelect = () => {
claim: selectCurrentUriClaim(state), const selectClaim = makeSelectClaimForUri(),
uri: selectCurrentUri(state), selectContentType = makeSelectContentTypeForUri(),
isDownloaded: selectCurrentUriIsDownloaded(state), selectFileInfo = makeSelectFileInfoForUri(),
fileInfo: selectCurrentUriFileInfo(state), selectCostInfo = makeSelectCostInfoForUri(),
costInfo: selectCurrentUriCostInfo(state), selectMetadata = makeSelectMetadataForUri()
})
const select = (state, props) => ({
claim: selectClaim(state, props),
contentType: selectContentType(state, props),
costInfo: selectCostInfo(state, props),
metadata: selectMetadata(state, props),
fileInfo: selectFileInfo(state, props)
})
return select
}
const perform = (dispatch) => ({ const perform = (dispatch) => ({
fetchFileInfo: (uri) => dispatch(doFetchFileInfo(uri))
}) })
export default connect(select, perform)(FilePage) export default connect(makeSelect, perform)(FilePage)

View file

@ -1,12 +1,9 @@
import React from 'react'; import React from 'react';
import lbry from 'lbry.js'; import lbry from 'lbry.js';
import lighthouse from 'lighthouse.js';
import lbryuri from 'lbryuri.js'; import lbryuri from 'lbryuri.js';
import Video from 'component/video' import Video from 'component/video'
import { import {
TruncatedText,
Thumbnail, Thumbnail,
BusyMessage,
} from 'component/common'; } from 'component/common';
import FilePrice from 'component/filePrice' import FilePrice from 'component/filePrice'
import FileActions from 'component/fileActions'; import FileActions from 'component/fileActions';
@ -45,87 +42,88 @@ const FormatItem = (props) => {
) )
} }
const FilePage = (props) => { class FilePage extends React.Component{
const {
claim, componentDidMount() {
navigate, this.fetchFileInfo(this.props)
claim: { }
componentWillReceiveProps(nextProps) {
this.fetchFileInfo(nextProps)
}
fetchFileInfo(props) {
if (props.fileInfo === undefined) {
props.fetchFileInfo(props.uri)
}
}
render() {
const {
claim,
fileInfo,
metadata,
contentType,
uri,
} = this.props
if (!claim || !metadata) {
return <span className="empty">Empty claim or metadata info.</span>
}
const {
txid, txid,
nout, nout,
has_signature: hasSignature, has_signature: hasSignature,
signature_is_valid: signatureIsValid, signature_is_valid: signatureIsValid,
value, value
value: { } = claim
stream,
stream: {
metadata,
source,
metadata: {
title,
} = {},
source: {
contentType,
} = {},
} = {},
} = {},
},
uri,
isDownloaded,
fileInfo,
costInfo,
costInfo: {
cost,
includesData: costIncludesData,
} = {},
} = props
const outpoint = txid + ':' + nout; const outpoint = txid + ':' + nout
const uriLookupComplete = !!claim && Object.keys(claim).length const title = metadata.title
const channelUriObj = lbryuri.parse(uri)
delete channelUriObj.path;
delete channelUriObj.contentName;
const channelUri = signatureIsValid && hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null
const uriIndicator = <UriIndicator uri={uri} />
const channelUriObj = lbryuri.parse(uri) return (
delete channelUriObj.path; <main className="main--single-column">
delete channelUriObj.contentName; <section className="show-page-media">
const channelUri = signatureIsValid && hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null; { contentType && contentType.startsWith('video/') ?
const uriIndicator = <UriIndicator uri={uri} /> <Video className="video-embedded" uri={uri} /> :
(metadata && metadata.thumbnail ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
// <p>This location is not yet in use. { ' ' }<Link onClick={() => navigate('/publish')} label="Put something here" />.</p> </section>
<section className="card">
return ( <div className="card__inner">
<main className="main--single-column"> <div className="card__title-identity">
<section className="show-page-media"> {!fileInfo || fileInfo.written_bytes <= 0
{ contentType && contentType.startsWith('video/') ? ? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} /></span>
<Video className="video-embedded" uri={uri} metadata={metadata} outpoint={outpoint} /> : : null}<h1>{title}</h1>
(Object.keys(metadata).length > 0 ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) } <div className="card__subtitle">
</section> { channelUri ?
<section className="card"> <Link href={"?show=" + channelUri }>{uriIndicator}</Link> :
<div className="card__inner"> uriIndicator}
<div className="card__title-identity"> </div>
{isDownloaded === false <div className="card__actions">
? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} /></span> <FileActions uri={uri} />
: null} </div>
<h1>{title}</h1> </div>
<div className="card__subtitle"> <div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
{ channelUri ? {metadata && metadata.description}
<Link href={"?show=" + channelUri }>{uriIndicator}</Link> : </div>
uriIndicator}
</div>
<div className="card__actions">
<FileActions uri={uri} /></div>
</div> </div>
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines"> { metadata ?
{metadata.description} <div className="card__content">
</div> <FormatItem metadata={metadata} contentType={contentType} />
</div> </div> : '' }
{ metadata ?
<div className="card__content"> <div className="card__content">
<FormatItem metadata={metadata} contentType={contentType} cost={cost} uri={uri} outpoint={outpoint} costIncludesData={costIncludesData} /> <Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
</div> : '' } </div>
<div className="card__content"> </section>
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" /> </main>
</div> )
</section> }
</main>
)
} }
export default FilePage; export default FilePage;

View file

@ -24,9 +24,6 @@ var HelpPage = React.createClass({
}); });
}); });
}, },
componentDidMount: function() {
document.title = "Help";
},
render: function() { render: function() {
let ver, osName, platform, newVerLink; let ver, osName, platform, newVerLink;
if (this.state.versionInfo) { if (this.state.versionInfo) {

View file

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

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import lbry from 'lbry'; import lbry from 'lbry';
import lbryuri from 'lbryuri'
import {FormField, FormRow} from 'component/form.js'; import {FormField, FormRow} from 'component/form.js';
import Link from 'component/link'; import Link from 'component/link';
import rewards from 'rewards'; import rewards from 'rewards';
@ -100,7 +101,7 @@ var PublishPage = React.createClass({
}; };
if (this.state.isFee) { if (this.state.isFee) {
lbry.getUnusedAddress((address) => { lbry.wallet_unused_address().then((address) => {
metadata.fee = {}; metadata.fee = {};
metadata.fee[this.state.feeCurrency] = { metadata.fee[this.state.feeCurrency] = {
amount: parseFloat(this.state.feeAmount), amount: parseFloat(this.state.feeAmount),
@ -169,7 +170,7 @@ var PublishPage = React.createClass({
return; return;
} }
if (!lbry.nameIsValid(rawName, false)) { if (!lbryuri.isValidName(rawName, false)) {
this.refs.name.showError('LBRY names must contain only letters, numbers and dashes.'); this.refs.name.showError('LBRY names must contain only letters, numbers and dashes.');
return; return;
} }
@ -182,50 +183,45 @@ var PublishPage = React.createClass({
myClaimExists: null, myClaimExists: null,
}); });
lbry.getMyClaim(name, (myClaimInfo) => { const myClaimInfo = Object.values(this.props.myClaims).find(claim => claim.name === name)
this.setState({
myClaimExists: !!myClaimInfo,
});
lbry.resolve({uri: name}).then((claimInfo) => {
if (name != this.state.name) { if (name != this.state.name) {
// A new name has been typed already, so bail
return; return;
} }
this.setState({ if (!claimInfo) {
myClaimExists: !!myClaimInfo,
});
lbry.resolve({uri: name}).then((claimInfo) => {
if (name != this.state.name) {
return;
}
if (!claimInfo) {
this.setState({
nameResolved: false,
});
} else {
const topClaimIsMine = (myClaimInfo && myClaimInfo.claim.amount >= claimInfo.claim.amount);
const newState = {
nameResolved: true,
topClaimValue: parseFloat(claimInfo.claim.amount),
myClaimExists: !!myClaimInfo,
myClaimValue: myClaimInfo ? parseFloat(myClaimInfo.claim.amount) : null,
myClaimMetadata: myClaimInfo ? myClaimInfo.value : null,
topClaimIsMine: topClaimIsMine,
};
if (topClaimIsMine) {
newState.bid = myClaimInfo.claim.amount;
} else if (this.state.myClaimMetadata) {
// Just changed away from a name we have a claim on, so clear pre-fill
newState.bid = '';
}
this.setState(newState);
}
}, () => { // Assume an error means the name is available
this.setState({ this.setState({
name: name,
nameResolved: false, nameResolved: false,
myClaimExists: false,
}); });
} else {
const topClaimIsMine = (myClaimInfo && myClaimInfo.claim.amount >= claimInfo.claim.amount);
const newState = {
nameResolved: true,
topClaimValue: parseFloat(claimInfo.claim.amount),
myClaimExists: !!myClaimInfo,
myClaimValue: myClaimInfo ? parseFloat(myClaimInfo.claim.amount) : null,
myClaimMetadata: myClaimInfo ? myClaimInfo.value : null,
topClaimIsMine: topClaimIsMine,
};
if (topClaimIsMine) {
newState.bid = myClaimInfo.claim.amount;
} else if (this.state.myClaimMetadata) {
// Just changed away from a name we have a claim on, so clear pre-fill
newState.bid = '';
}
this.setState(newState);
}
}, () => { // Assume an error means the name is available
this.setState({
name: name,
nameResolved: false,
myClaimExists: false,
}); });
}); });
}, },
@ -287,7 +283,7 @@ var PublishPage = React.createClass({
handleNewChannelNameChange: function (event) { handleNewChannelNameChange: function (event) {
const newChannelName = (event.target.value.startsWith('@') ? event.target.value : '@' + event.target.value); 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.'); this.refs.newChannelName.showError('LBRY channel names must contain only letters, numbers and dashes.');
return; return;
} else { } else {

View file

@ -8,14 +8,15 @@ import {BusyMessage} from 'component/common.js';
class SearchPage extends React.Component{ class SearchPage extends React.Component{
render() { render() {
const isValidUri = (query) => true //FIXME console.log('render search page')
const { const {
query, query,
} = this.props } = this.props
return ( return (
<main className="main--single-column"> <main className="main--single-column">
{ isValidUri(query) ? { lbryuri.isValid(query) ?
<section className="section-spaced"> <section className="section-spaced">
<h3 className="card-row__header"> <h3 className="card-row__header">
Exact URL <ToolTip label="?" body="This is the resolution of a LBRY URL and not controlled by LBRY Inc." Exact URL <ToolTip label="?" body="This is the resolution of a LBRY URL and not controlled by LBRY Inc."

View file

@ -0,0 +1,21 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doSetDaemonSetting
} from 'actions/settings'
import {
selectDaemonSettings
} from 'selectors/settings'
import SettingsPage from './view'
const select = (state) => ({
daemonSettings: selectDaemonSettings(state)
})
const perform = (dispatch) => ({
setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)),
})
export default connect(select, perform)(SettingsPage)

View file

@ -1,19 +1,11 @@
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/subHeader' import SubHeader from 'component/subHeader'
import lbry from '../lbry.js'; import lbry from 'lbry.js';
var SettingsPage = React.createClass({ var SettingsPage = React.createClass({
_onSettingSaveSuccess: function() {
// This is bad.
// document.dispatchEvent(new CustomEvent('globalNotice', {
// detail: {
// message: "Settings saved",
// },
// }))
},
setDaemonSetting: function(name, value) { setDaemonSetting: function(name, value) {
lbry.setDaemonSetting(name, value, this._onSettingSaveSuccess) this.props.setDaemonSetting(name, value)
}, },
setClientSetting: function(name, value) { setClientSetting: function(name, value) {
lbry.setClientSetting(name, value) lbry.setClientSetting(name, value)
@ -51,21 +43,15 @@ var SettingsPage = React.createClass({
this.setDaemonSetting('max_download', Number(event.target.value)); this.setDaemonSetting('max_download', Number(event.target.value));
}, },
getInitialState: function() { getInitialState: function() {
const daemonSettings = this.props.daemonSettings
return { return {
settings: null, isMaxUpload: daemonSettings && daemonSettings.max_upload != 0,
isMaxDownload: daemonSettings && daemonSettings.max_download != 0,
showNsfw: lbry.getClientSetting('showNsfw'), showNsfw: lbry.getClientSetting('showNsfw'),
showUnavailable: lbry.getClientSetting('showUnavailable'), showUnavailable: lbry.getClientSetting('showUnavailable'),
} }
}, },
componentWillMount: function() {
lbry.getDaemonSettings((settings) => {
this.setState({
daemonSettings: settings,
isMaxUpload: settings.max_upload != 0,
isMaxDownload: settings.max_download != 0
});
});
},
onShowNsfwChange: function(event) { onShowNsfwChange: function(event) {
lbry.setClientSetting('showNsfw', event.target.checked); lbry.setClientSetting('showNsfw', event.target.checked);
}, },
@ -73,8 +59,12 @@ var SettingsPage = React.createClass({
}, },
render: function() { render: function() {
if (!this.state.daemonSettings) { const {
return null; daemonSettings
} = this.props
if (!daemonSettings) {
return <main className="main--single-column"><span className="empty">Failed to load settings.</span></main>;
} }
/* /*
<section className="card"> <section className="card">
@ -84,7 +74,7 @@ var SettingsPage = React.createClass({
<div className="card__content"> <div className="card__content">
<FormRow type="checkbox" <FormRow type="checkbox"
onChange={this.onRunOnStartChange} onChange={this.onRunOnStartChange}
defaultChecked={this.state.daemonSettings.run_on_startup} defaultChecked={daemonSettings.run_on_startup}
label="Run LBRY automatically when I start my computer" /> label="Run LBRY automatically when I start my computer" />
</div> </div>
</section> </section>
@ -99,7 +89,7 @@ var SettingsPage = React.createClass({
<div className="card__content"> <div className="card__content">
<FormRow type="directory" <FormRow type="directory"
name="download_directory" name="download_directory"
defaultValue={this.state.daemonSettings.download_directory} defaultValue={daemonSettings.download_directory}
helper="LBRY downloads will be saved here." helper="LBRY downloads will be saved here."
onChange={this.onDownloadDirChange} /> onChange={this.onDownloadDirChange} />
</div> </div>
@ -125,7 +115,7 @@ var SettingsPage = React.createClass({
<FormField type="number" <FormField type="number"
min="0" min="0"
step=".5" step=".5"
defaultValue={this.state.daemonSettings.max_upload} defaultValue={daemonSettings.max_upload}
placeholder="10" placeholder="10"
className="form-field__input--inline" className="form-field__input--inline"
onChange={this.onMaxUploadFieldChange} onChange={this.onMaxUploadFieldChange}
@ -153,7 +143,7 @@ var SettingsPage = React.createClass({
<FormField type="number" <FormField type="number"
min="0" min="0"
step=".5" step=".5"
defaultValue={this.state.daemonSettings.max_download} defaultValue={daemonSettings.max_download}
placeholder="10" placeholder="10"
className="form-field__input--inline" className="form-field__input--inline"
onChange={this.onMaxDownloadFieldChange} onChange={this.onMaxDownloadFieldChange}
@ -188,7 +178,7 @@ var SettingsPage = React.createClass({
<div className="card__content"> <div className="card__content">
<FormRow type="checkbox" <FormRow type="checkbox"
onChange={this.onShareDataChange} onChange={this.onShareDataChange}
defaultChecked={this.state.daemonSettings.share_usage_data} defaultChecked={daemonSettings.share_usage_data}
label="Help make LBRY better by contributing diagnostic data about my usage" /> label="Help make LBRY better by contributing diagnostic data about my usage" />
</div> </div>
</section> </section>

View file

@ -6,24 +6,27 @@ import {
doResolveUri, doResolveUri,
} from 'actions/content' } from 'actions/content'
import { import {
selectCurrentUri, makeSelectClaimForUri,
} from 'selectors/app'
import {
selectCurrentUriClaim,
} from 'selectors/claims' } from 'selectors/claims'
import { import {
selectCurrentUriIsResolving, makeSelectIsResolvingForUri,
} from 'selectors/content' } from 'selectors/content'
import ShowPage from './view' import ShowPage from './view'
const select = (state, props) => ({ const makeSelect = () => {
claim: selectCurrentUriClaim(state), const selectClaim = makeSelectClaimForUri(),
uri: selectCurrentUri(state), selectIsResolving = makeSelectIsResolvingForUri();
isResolvingUri: selectCurrentUriIsResolving(state)
}) const select = (state, props) => ({
claim: selectClaim(state, props),
isResolvingUri: selectIsResolving(state, props)
})
return select
}
const perform = (dispatch) => ({ const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri)) resolveUri: (uri) => dispatch(doResolveUri(uri))
}) })
export default connect(select, perform)(ShowPage) export default connect(makeSelect, perform)(ShowPage)

View file

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import lbryuri from 'lbryuri'
import { import {
BusyMessage, BusyMessage,
} from 'component/common'; } from 'component/common';
import ChannelPage from 'page/channel'
import FilePage from 'page/filePage' import FilePage from 'page/filePage'
class ShowPage extends React.Component{ class ShowPage extends React.Component{
@ -21,7 +23,7 @@ class ShowPage extends React.Component{
uri, uri,
} = props } = props
if(!isResolvingUri && !claim && uri) { if(!isResolvingUri && claim === undefined && uri) {
resolveUri(uri) resolveUri(uri)
} }
} }
@ -35,22 +37,22 @@ class ShowPage extends React.Component{
let innerContent = ""; let innerContent = "";
if (isResolvingUri) { if (isResolvingUri || !claim) {
innerContent = <section className="card"> innerContent = <section className="card">
<div className="card__inner"> <div className="card__inner">
<div className="card__title-identity"><h1>{uri}</h1></div> <div className="card__title-identity"><h1>{uri}</h1></div>
</div> </div>
<div className="card__content"> <div className="card__content">
<BusyMessage message="Loading magic decentralized data..." /> : { isResolvingUri && <BusyMessage message="Loading magic decentralized data..." /> }
{ claim === null && <span className="empty">There's nothing at this location.</span> }
</div> </div>
</section>; </section>
} }
else if (claim && claim.whatever) { else if (claim.name.length && claim.name[0] === '@') {
innerContent = "channel" innerContent = <ChannelPage uri={lbryuri.build({ name: claim.name, claimId: claim.claim_id })} />
// innerContent = <ChannelPage title={uri} />
} }
else if (claim) { else if (claim) {
innerContent = <FilePage /> innerContent = <FilePage uri={uri} />
} }
return ( return (
@ -59,4 +61,4 @@ class ShowPage extends React.Component{
} }
} }
export default ShowPage export default ShowPage

View file

@ -9,7 +9,6 @@ const defaultState = {
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
daemonReady: false, daemonReady: false,
obscureNsfw: !lbry.getClientSetting('showNsfw'), obscureNsfw: !lbry.getClientSetting('showNsfw'),
hidePrice: false,
hasSignature: false, hasSignature: false,
} }

View file

@ -9,10 +9,8 @@ reducers[types.FETCH_AVAILABILITY_STARTED] = function(state, action) {
uri, uri,
} = action.data } = action.data
const newFetching = Object.assign({}, state.fetching) const newFetching = Object.assign({}, state.fetching)
const newByUri = Object.assign({}, newFetching.byUri)
newByUri[uri] = true newFetching[uri] = true
newFetching.byUri = newByUri
return Object.assign({}, state, { return Object.assign({}, state, {
fetching: newFetching, fetching: newFetching,
@ -24,12 +22,11 @@ reducers[types.FETCH_AVAILABILITY_COMPLETED] = function(state, action) {
uri, uri,
availability, availability,
} = action.data } = action.data
const newFetching = Object.assign({}, state.fetching) const newFetching = Object.assign({}, state.fetching)
const newFetchingByUri = Object.assign({}, newFetching.byUri)
const newAvailabilityByUri = Object.assign({}, state.byUri) const newAvailabilityByUri = Object.assign({}, state.byUri)
delete newFetchingByUri[uri] delete newFetching[uri]
newFetching.byUri = newFetchingByUri
newAvailabilityByUri[uri] = availability newAvailabilityByUri[uri] = availability
return Object.assign({}, state, { return Object.assign({}, state, {

View file

@ -8,30 +8,69 @@ const defaultState = {
reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) {
const { const {
uri, uri,
certificate,
claim, claim,
} = action.data } = action.data
const newByUri = Object.assign({}, state.byUri)
newByUri[uri] = claim const newClaims = Object.assign({}, state.claimsByUri)
newClaims[uri] = claim
//This needs a sanity boost...
if (certificate !== undefined && claim === undefined) {
const uriParts = lbryuri.parse(uri);
// newChannelClaims[uri] = certificate
if (claim === undefined) {
newClaims[uri] = certificate
}
}
return Object.assign({}, state, { return Object.assign({}, state, {
byUri: newByUri, claimsByUri: newClaims
}) })
} }
reducers[types.FETCH_MY_CLAIMS_COMPLETED] = function(state, action) { reducers[types.RESOLVE_URI_CANCELED] = function(state, action) {
const { const uri = action.data.uri
claims, const newClaims = Object.assign({}, state.claimsByUri)
} = action.data delete newClaims[uri]
const newMine = Object.assign({}, state.mine) return Object.assign({}, state, {
const newById = Object.assign({}, newMine.byId) claimsByUri: newClaims
claims.forEach(claim => {
newById[claim.claim_id] = claim
}) })
newMine.byId = newById }
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,
claims
} = action.data
const newClaims = Object.assign({}, state.claimsByChannel)
if (claims !== undefined) {
newClaims[uri] = claims
}
return Object.assign({}, state, { return Object.assign({}, state, {
mine: newMine, claimsByChannel: newClaims
}) })
} }

View file

@ -30,7 +30,7 @@ reducers[types.RESOLVE_URI_STARTED] = function(state, action) {
const oldResolving = state.resolvingUris || [] const oldResolving = state.resolvingUris || []
const newResolving = Object.assign([], oldResolving) 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, { return Object.assign({}, state, {
resolvingUris: newResolving resolvingUris: newResolving
@ -48,51 +48,14 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) {
...resolvingUris.slice(index + 1) ...resolvingUris.slice(index + 1)
] ]
const newState = Object.assign({}, state, { return Object.assign({}, state, {
resolvingUris: newResolvingUris, 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) { reducers[types.RESOLVE_URI_CANCELED] = function(state, action) {
const { return reducers[types.RESOLVE_URI_COMPLETED](state, action)
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,
})
} }
export default function reducer(state = defaultState, action) { export default function reducer(state = defaultState, action) {

View file

@ -5,13 +5,35 @@ const reducers = {}
const defaultState = { 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) { reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) {
const { const {
uri, outpoint
} = action.data } = action.data
const newFetching = Object.assign({}, state.fetching) const newFetching = Object.assign({}, state.fetching)
newFetching[uri] = true newFetching[outpoint] = true
return Object.assign({}, state, { return Object.assign({}, state, {
fetching: newFetching, fetching: newFetching,
@ -20,17 +42,18 @@ reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) {
reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) {
const { const {
uri,
fileInfo, fileInfo,
outpoint,
} = action.data } = action.data
const newByUri = Object.assign({}, state.byUri)
const newFileInfos = Object.assign({}, state.fileInfos)
const newFetching = Object.assign({}, state.fetching) const newFetching = Object.assign({}, state.fetching)
newByUri[uri] = fileInfo || {} newFileInfos[outpoint] = fileInfo
delete newFetching[uri] delete newFetching[outpoint]
return Object.assign({}, state, { return Object.assign({}, state, {
byUri: newByUri, fileInfos: newFileInfos,
fetching: newFetching, fetching: newFetching,
}) })
} }
@ -38,93 +61,74 @@ reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) {
reducers[types.DOWNLOADING_STARTED] = function(state, action) { reducers[types.DOWNLOADING_STARTED] = function(state, action) {
const { const {
uri, uri,
outpoint,
fileInfo, fileInfo,
} = action.data } = 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 const newFileInfos = Object.assign({}, state.fileInfos)
newDownloading.byUri = newDownloadingByUri const newDownloading = Object.assign({}, state.urisDownloading)
newByUri[uri] = fileInfo const newLoading = Object.assign({}, state.urisLoading)
delete newLoadingByUri[uri]
newLoading.byUri = newLoadingByUri newDownloading[uri] = true
newFileInfos[outpoint] = fileInfo
delete newLoading[uri]
return Object.assign({}, state, { return Object.assign({}, state, {
downloading: newDownloading, urisDownloading: newDownloading,
byUri: newByUri, urisLoading: newLoading,
loading: newLoading, fileInfos: newFileInfos,
}) })
} }
reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) { reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) {
const { const {
uri, uri,
outpoint,
fileInfo, fileInfo,
} = action.data } = 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 newDownloading[uri] = true
return Object.assign({}, state, { return Object.assign({}, state, {
byUri: newByUri, fileInfos: newFileInfos,
downloading: newDownloading urisDownloading: newDownloading
}) })
} }
reducers[types.DOWNLOADING_COMPLETED] = function(state, action) { reducers[types.DOWNLOADING_COMPLETED] = function(state, action) {
const { const {
uri, uri,
outpoint,
fileInfo, fileInfo,
} = action.data } = action.data
const newByUri = Object.assign({}, state.byUri)
const newDownloading = Object.assign({}, state.downloading)
const newDownloadingByUri = Object.assign({}, newDownloading.byUri)
newByUri[uri] = fileInfo const newFileInfos = Object.assign({}, state.fileInfos)
delete newDownloadingByUri[uri] const newDownloading = Object.assign({}, state.urisDownloading)
newDownloading.byUri = newDownloadingByUri
newFileInfos[outpoint] = fileInfo
delete newDownloading[uri]
return Object.assign({}, state, { return Object.assign({}, state, {
byUri: newByUri, fileInfos: newFileInfos,
downloading: newDownloading, urisDownloading: newDownloading,
}) })
} }
reducers[types.DELETE_FILE_STARTED] = function(state, action) { reducers[types.FILE_DELETE] = function(state, action) {
const { const {
uri, outpoint,
} = action.data } = action.data
const newDeleting = Object.assign({}, state.deleting)
const newByUri = Object.assign({}, newDeleting.byUri)
newByUri[uri] = true const newFileInfos = Object.assign({}, state.fileInfos)
newDeleting.byUri = newByUri
delete newFileInfos[outpoint]
return Object.assign({}, state, { return Object.assign({}, state, {
deleting: newDeleting, fileInfos: newFileInfos,
})
}
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,
}) })
} }
@ -132,14 +136,13 @@ reducers[types.LOADING_VIDEO_STARTED] = function(state, action) {
const { const {
uri, uri,
} = action.data } = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
newByUri[uri] = true const newLoading = Object.assign({}, state.urisLoading)
newLoading.byUri = newByUri
newLoading[uri] = true
return Object.assign({}, state, { return Object.assign({}, state, {
loading: newLoading, urisLoading: newLoading,
}) })
} }
@ -147,54 +150,13 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) {
const { const {
uri, uri,
} = action.data } = action.data
const newLoading = Object.assign({}, state.loading)
const newByUri = Object.assign({}, newLoading.byUri)
delete newByUri[uri] const newLoading = Object.assign({}, state.urisLoading)
newLoading.byUri = newByUri
delete newLoading[uri]
return Object.assign({}, state, { return Object.assign({}, state, {
loading: newLoading, urisLoading: 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
}) })
} }

View file

@ -1,25 +1,14 @@
import * as types from 'constants/action_types' import * as types from 'constants/action_types'
const reducers = {} const reducers = {}
const defaultState = { const defaultState = {}
}
reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) {
const {
uri,
certificate,
} = action.data
if (!certificate) return state
const newByUri = Object.assign({}, state.byUri)
newByUri[uri] = certificate
return Object.assign({}, state, { return Object.assign({}, state, {
byUri: newByUri, daemonSettings: action.data.settings
}) })
} }
export default function reducer(state = defaultState, action) { export default function reducer(state = defaultState, action) {
const handler = reducers[action.type]; const handler = reducers[action.type];
if (handler) return handler(state, action); if (handler) return handler(state, action);

View file

@ -31,27 +31,10 @@ export const selectCurrentParams = createSelector(
} }
) )
export const selectCurrentUri = createSelector(
selectCurrentPath,
(path) => {
if (path.match(/=/)) {
return path.split('=')[1]
}
else {
return undefined
}
}
)
export const selectCurrentUriTitle = createSelector(
_selectState,
(state) => "fix me"
)
export const selectPageTitle = createSelector( export const selectPageTitle = createSelector(
selectCurrentPage, selectCurrentPage,
selectCurrentUri, selectCurrentParams,
(page, uri) => { (page, params) => {
switch (page) { switch (page) {
case 'search': case 'search':
return 'Search' return 'Search'
@ -67,7 +50,7 @@ export const selectPageTitle = createSelector(
case 'rewards': case 'rewards':
return page.charAt(0).toUpperCase() + page.slice(1) return page.charAt(0).toUpperCase() + page.slice(1)
case 'show': case 'show':
return lbryuri.normalize(uri) return lbryuri.normalize(params.uri)
case 'downloaded': case 'downloaded':
return 'Downloads & Purchases' return 'Downloads & Purchases'
case 'published': case 'published':
@ -195,11 +178,6 @@ export const selectUpgradeDownloadItem = createSelector(
(state) => state.downloadItem (state) => state.downloadItem
) )
export const selectSearchTerm = createSelector(
_selectState,
(state) => state.searchTerm
)
export const selectError = createSelector( export const selectError = createSelector(
_selectState, _selectState,
(state) => state.error (state) => state.error
@ -213,14 +191,4 @@ export const selectDaemonReady = createSelector(
export const selectObscureNsfw = createSelector( export const selectObscureNsfw = createSelector(
_selectState, _selectState,
(state) => !!state.obscureNsfw (state) => !!state.obscureNsfw
) )
export const selectHidePrice = createSelector(
_selectState,
(state) => !!state.hidePrice
)
export const selectHasSignature = createSelector(
_selectState,
(state) => !!state.hasSignature
)

View file

@ -4,7 +4,6 @@ import {
import { import {
selectDaemonReady, selectDaemonReady,
selectCurrentPage, selectCurrentPage,
selectCurrentUri,
} from 'selectors/app' } from 'selectors/app'
const _selectState = state => state.availability const _selectState = state => state.availability
@ -14,29 +13,24 @@ export const selectAvailabilityByUri = createSelector(
(state) => state.byUri || {} (state) => state.byUri || {}
) )
const selectAvailabilityForUri = (state, props) => {
return selectAvailabilityByUri(state)[props.uri]
}
export const makeSelectIsAvailableForUri = () => {
return createSelector(
selectAvailabilityForUri,
(availability) => availability === undefined ? undefined : availability > 0
)
}
export const selectFetchingAvailability = createSelector( export const selectFetchingAvailability = createSelector(
_selectState, _selectState,
(state) => state.fetching || {} (state) => state.fetching || {}
) )
export const selectFetchingAvailabilityByUri = createSelector(
selectFetchingAvailability,
(fetching) => fetching.byUri || {}
)
const selectAvailabilityForUri = (state, props) => {
return selectAvailabilityByUri(state)[props.uri]
}
export const makeSelectAvailabilityForUri = () => {
return createSelector(
selectAvailabilityForUri,
(availability) => availability
)
}
const selectFetchingAvailabilityForUri = (state, props) => { const selectFetchingAvailabilityForUri = (state, props) => {
return selectFetchingAvailabilityByUri(state)[props.uri] return selectFetchingAvailability(state)[props.uri]
} }
export const makeSelectFetchingAvailabilityForUri = () => { export const makeSelectFetchingAvailabilityForUri = () => {
@ -44,16 +38,4 @@ export const makeSelectFetchingAvailabilityForUri = () => {
selectFetchingAvailabilityForUri, selectFetchingAvailabilityForUri,
(fetching) => fetching (fetching) => fetching
) )
} }
export const selectFetchingAvailabilityForCurrentUri = createSelector(
selectCurrentUri,
selectFetchingAvailabilityByUri,
(uri, byUri) => byUri[uri]
)
export const selectAvailabilityForCurrentUri = createSelector(
selectCurrentUri,
selectAvailabilityByUri,
(uri, byUri) => byUri[uri]
)

View file

@ -2,28 +2,17 @@ import {
createSelector, createSelector,
} from 'reselect' } from 'reselect'
import lbryuri from 'lbryuri' import lbryuri from 'lbryuri'
import {
selectCurrentUri,
} from 'selectors/app'
export const _selectState = state => state.claims || {} export const _selectState = state => state.claims || {}
export const selectClaimsByUri = createSelector( export const selectClaimsByUri = createSelector(
_selectState, _selectState,
(state) => state.byUri || {} (state) => state.claimsByUri || {}
) )
export const selectCurrentUriClaim = createSelector( export const selectAllClaimsByChannel = createSelector(
selectCurrentUri, _selectState,
selectClaimsByUri, (state) => state.claimsByChannel || {}
(uri, byUri) => byUri[uri]
)
export const selectCurrentUriClaimOutpoint = createSelector(
selectCurrentUriClaim,
(claim) => {
return claim ? `${claim.txid}:${claim.nout}` : null
}
) )
const selectClaimForUri = (state, props) => { const selectClaimForUri = (state, props) => {
@ -38,11 +27,22 @@ 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 selectMetadataForUri = (state, props) => {
const claim = selectClaimForUri(state, props) const claim = selectClaimForUri(state, props)
const metadata = claim && claim.value && claim.value.stream && claim.value.stream.metadata const metadata = claim && claim.value && claim.value.stream && claim.value.stream.metadata
return metadata ? metadata : undefined return metadata ? metadata : (claim === undefined ? undefined : null)
} }
export const makeSelectMetadataForUri = () => { export const makeSelectMetadataForUri = () => {
@ -56,7 +56,7 @@ const selectSourceForUri = (state, props) => {
const claim = selectClaimForUri(state, props) const claim = selectClaimForUri(state, props)
const source = claim && claim.value && claim.value.stream && claim.value.stream.source const source = claim && claim.value && claim.value.stream && claim.value.stream.source
return source ? source : undefined return source ? source : (claim === undefined ? undefined : null)
} }
export const makeSelectSourceForUri = () => { export const makeSelectSourceForUri = () => {
@ -66,26 +66,32 @@ export const makeSelectSourceForUri = () => {
) )
} }
export const selectMyClaims = createSelector( export const makeSelectContentTypeForUri = () => {
return createSelector(
selectSourceForUri,
(source) => source ? source.contentType : source
)
}
export const selectClaimListMineIsPending = createSelector(
_selectState, _selectState,
(state) => state.mine || {} (state) => state.isClaimListMinePending
) )
export const selectMyClaimsById = createSelector( export const selectMyClaims = createSelector(
selectMyClaims, _selectState,
(mine) => mine.byId || {} (state) => state.myClaims || {}
) )
export const selectMyClaimsOutpoints = createSelector( export const selectMyClaimsOutpoints = createSelector(
selectMyClaimsById, selectMyClaims,
(byId) => { (claims) => {
const outpoints = [] if (!claims) {
Object.keys(byId).forEach(key => { return []
const claim = byId[key] }
const outpoint = `${claim.txid}:${claim.nout}`
outpoints.push(outpoint)
})
return outpoints return Object.values(claims).map((claim) => {
return `${claim.txid}:${claim.nout}`
})
} }
) )

View file

@ -2,7 +2,6 @@ import { createSelector } from 'reselect'
import { import {
selectDaemonReady, selectDaemonReady,
selectCurrentPage, selectCurrentPage,
selectCurrentUri,
} from 'selectors/app' } from 'selectors/app'
export const _selectState = state => state.content || {} export const _selectState = state => state.content || {}
@ -17,56 +16,18 @@ export const selectFetchingFeaturedUris = createSelector(
(state) => !!state.fetchingFeaturedContent (state) => !!state.fetchingFeaturedContent
) )
export const selectFetchingFileInfos = createSelector(
_selectState,
(state) => state.fetchingFileInfos || {}
)
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( export const selectResolvingUris = createSelector(
_selectState, _selectState,
(state) => state.resolvingUris || [] (state) => state.resolvingUris || []
) )
export const selectCurrentUriIsResolving = createSelector(
selectCurrentUri,
selectResolvingUris,
(uri, resolvingUris) => resolvingUris.indexOf(uri) != -1
)
const selectResolvingUri = (state, props) => { const selectResolvingUri = (state, props) => {
return selectResolvingUris(state).indexOf(props.uri) != -1 return selectResolvingUris(state).indexOf(props.uri) != -1
} }
export const makeSelectResolvingUri = () => { export const makeSelectIsResolvingForUri = () => {
return createSelector( return createSelector(
selectResolvingUri, selectResolvingUri,
(resolving) => resolving (resolving) => resolving
) )
} }

View file

@ -1,8 +1,4 @@
import { createSelector } from 'reselect' import { createSelector } from 'reselect'
import {
selectCurrentUri,
selectCurrentPage,
} from 'selectors/app'
export const _selectState = state => state.costInfo || {} export const _selectState = state => state.costInfo || {}
@ -11,24 +7,7 @@ export const selectAllCostInfoByUri = createSelector(
(state) => state.byUri || {} (state) => state.byUri || {}
) )
export const selectCurrentUriCostInfo = createSelector( export const selectCostInfoForUri = (state, props) => {
selectCurrentUri,
selectAllCostInfoByUri,
(uri, byUri) => byUri[uri] || {}
)
export const selectFetchingCostInfo = createSelector(
_selectState,
(state) => state.fetching || {}
)
export const selectFetchingCurrentUriCostInfo = createSelector(
selectCurrentUri,
selectFetchingCostInfo,
(uri, byUri) => !!byUri[uri]
)
const selectCostInfoForUri = (state, props) => {
return selectAllCostInfoByUri(state)[props.uri] return selectAllCostInfoByUri(state)[props.uri]
} }

View file

@ -1,72 +1,38 @@
import lbry from 'lbry'
import { import {
createSelector, createSelector,
} from 'reselect' } from 'reselect'
import { import {
selectCurrentUri, selectClaimsByUri,
selectCurrentPage, selectClaimListMineIsPending,
} from 'selectors/app'
import {
selectMyClaimsOutpoints, selectMyClaimsOutpoints,
} from 'selectors/claims' } from 'selectors/claims'
export const _selectState = state => state.fileInfo || {} export const _selectState = state => state.fileInfo || {}
export const selectAllFileInfoByUri = createSelector( export const selectAllFileInfos = createSelector(
_selectState, _selectState,
(state) => state.byUri || {} (state) => state.fileInfos || {}
) )
export const selectCurrentUriRawFileInfo = createSelector( export const selectFileListIsPending = createSelector(
selectCurrentUri,
selectAllFileInfoByUri,
(uri, byUri) => byUri[uri]
)
export const selectCurrentUriFileInfo = createSelector(
selectCurrentUriRawFileInfo,
(fileInfo) => fileInfo
)
export const selectFetchingFileInfo = createSelector(
_selectState, _selectState,
(state) => state.fetching || {} (state) => state.isFileListPending
) )
export const selectFetchingCurrentUriFileInfo = createSelector( export const selectFileListDownloadedOrPublishedIsPending = createSelector(
selectCurrentUri, selectFileListIsPending,
selectFetchingFileInfo, selectClaimListMineIsPending,
(uri, byUri) => !!byUri[uri] (isFileListPending, isClaimListMinePending) => isFileListPending || isClaimListMinePending
) )
export const selectDownloading = createSelector( export const selectFileInfoForUri = (state, props) => {
_selectState, const claims = selectClaimsByUri(state),
(state) => state.downloading || {} claim = claims[props.uri],
) fileInfos = selectAllFileInfos(state),
outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined
export const selectDownloadingByUri = createSelector( return outpoint && fileInfos ? fileInfos[outpoint] : undefined
selectDownloading,
(downloading) => downloading.byUri || {}
)
export const selectDownloadingCurrentUri = createSelector(
selectCurrentUri,
selectDownloadingByUri,
(uri, byUri) => !!byUri[uri]
)
export const selectCurrentUriIsDownloaded = createSelector(
selectCurrentUriFileInfo,
(fileInfo) => {
if (!fileInfo) return false
if (!fileInfo.completed) return false
if (!fileInfo.written_bytes > 0) return false
return true
}
)
const selectFileInfoForUri = (state, props) => {
return selectAllFileInfoByUri(state)[props.uri]
} }
export const makeSelectFileInfoForUri = () => { export const makeSelectFileInfoForUri = () => {
@ -76,8 +42,13 @@ export const makeSelectFileInfoForUri = () => {
) )
} }
export const selectUrisDownloading = createSelector(
_selectState,
(state) => state.urisDownloading || {}
)
const selectDownloadingForUri = (state, props) => { const selectDownloadingForUri = (state, props) => {
const byUri = selectDownloadingByUri(state) const byUri = selectUrisDownloading(state)
return byUri[props.uri] return byUri[props.uri]
} }
@ -88,31 +59,13 @@ export const makeSelectDownloadingForUri = () => {
) )
} }
export const selectLoading = createSelector( export const selectUrisLoading = createSelector(
_selectState, _selectState,
(state) => state.loading || {} (state) => state.urisLoading || {}
)
export const selectLoadingByUri = createSelector(
selectLoading,
(loading) => loading.byUri || {}
)
export const selectLoadingCurrentUri = createSelector(
selectLoadingByUri,
selectCurrentUri,
(byUri, uri) => !!byUri[uri]
)
// TODO make this smarter so it doesn't start playing and immediately freeze
// while downloading more.
export const selectCurrentUriFileReadyToPlay = createSelector(
selectCurrentUriFileInfo,
(fileInfo) => (fileInfo || {}).written_bytes > 0
) )
const selectLoadingForUri = (state, props) => { const selectLoadingForUri = (state, props) => {
const byUri = selectLoadingByUri(state) const byUri = selectUrisLoading(state)
return byUri[props.uri] return byUri[props.uri]
} }
@ -123,36 +76,38 @@ export const makeSelectLoadingForUri = () => {
) )
} }
export const selectDownloadedFileInfo = createSelector( export const selectFileInfosDownloaded = createSelector(
selectAllFileInfoByUri, selectAllFileInfos,
(byUri) => { selectMyClaimsOutpoints,
(fileInfos, myClaimOutpoints) => {
const fileInfoList = [] const fileInfoList = []
Object.keys(byUri).forEach(key => { Object.values(fileInfos).forEach(fileInfo => {
const fileInfo = byUri[key] if (fileInfo && myClaimOutpoints.indexOf(fileInfo.outpoint) === -1 && (fileInfo.completed || fileInfo.written_bytes)) {
if (fileInfo.completed || fileInfo.written_bytes) {
fileInfoList.push(fileInfo) fileInfoList.push(fileInfo)
} }
}) })
return fileInfoList return fileInfoList
} }
) )
export const selectPublishedFileInfo = createSelector( export const selectFileInfosPendingPublish = createSelector(
selectAllFileInfoByUri, _selectState,
selectMyClaimsOutpoints, (state) => {
(byUri, outpoints) => { return lbry.getPendingPublishes()
const fileInfos = [] }
outpoints.forEach(outpoint => { )
Object.keys(byUri).forEach(key => {
const fileInfo = byUri[key] export const selectFileInfosPublished = createSelector(
if (fileInfo.outpoint == outpoint) { selectAllFileInfos,
fileInfos.push(fileInfo) selectFileInfosPendingPublish,
} selectMyClaimsOutpoints,
}) (allFileInfos, pendingFileInfos, outpoints) => {
}) const fileInfos = []
outpoints.forEach(outpoint => {
return fileInfos if (allFileInfos[outpoint]) {
fileInfos.push(allFileInfos[outpoint])
}
})
return [...fileInfos, ...pendingFileInfos]
} }
) )

View file

@ -2,7 +2,6 @@ import { createSelector } from 'reselect'
import { import {
selectPageTitle, selectPageTitle,
selectCurrentPage, selectCurrentPage,
selectCurrentUri
} from 'selectors/app' } from 'selectors/app'
export const _selectState = state => state.search || {} export const _selectState = state => state.search || {}
@ -43,8 +42,7 @@ export const selectWunderBarAddress = createSelector(
export const selectWunderBarIcon = createSelector( export const selectWunderBarIcon = createSelector(
selectCurrentPage, selectCurrentPage,
selectCurrentUri, (page) => {
(page, uri) => {
switch (page) { switch (page) {
case 'search': case 'search':
return 'icon-search' return 'icon-search'

View file

@ -0,0 +1,20 @@
import {
createSelector,
} from 'reselect'
const _selectState = state => state.settings || {}
export const selectDaemonSettings = createSelector(
_selectState,
(state) => state.daemonSettings || {}
)
export const selectClientSettings = createSelector(
_selectState,
(state) => state.clientSettings
)
export const selectSettingsIsGenerous = createSelector(
selectDaemonSettings,
(settings) => settings && settings.is_generous_host
)

View file

@ -7,13 +7,13 @@ import {
} from 'redux-logger' } from 'redux-logger'
import appReducer from 'reducers/app'; import appReducer from 'reducers/app';
import availabilityReducer from 'reducers/availability' import availabilityReducer from 'reducers/availability'
import certificatesReducer from 'reducers/certificates'
import claimsReducer from 'reducers/claims' import claimsReducer from 'reducers/claims'
import contentReducer from 'reducers/content'; import contentReducer from 'reducers/content';
import costInfoReducer from 'reducers/cost_info' import costInfoReducer from 'reducers/cost_info'
import fileInfoReducer from 'reducers/file_info' import fileInfoReducer from 'reducers/file_info'
import rewardsReducer from 'reducers/rewards' import rewardsReducer from 'reducers/rewards'
import searchReducer from 'reducers/search' import searchReducer from 'reducers/search'
import settingsReducer from 'reducers/settings'
import walletReducer from 'reducers/wallet' import walletReducer from 'reducers/wallet'
function isFunction(object) { function isFunction(object) {
@ -49,13 +49,13 @@ function enableBatching(reducer) {
const reducers = redux.combineReducers({ const reducers = redux.combineReducers({
app: appReducer, app: appReducer,
availability: availabilityReducer, availability: availabilityReducer,
certificates: certificatesReducer,
claims: claimsReducer, claims: claimsReducer,
fileInfo: fileInfoReducer, fileInfo: fileInfoReducer,
content: contentReducer, content: contentReducer,
costInfo: costInfoReducer, costInfo: costInfoReducer,
rewards: rewardsReducer, rewards: rewardsReducer,
search: searchReducer, search: searchReducer,
settings: settingsReducer,
wallet: walletReducer, wallet: walletReducer,
}); });

View file

@ -22,7 +22,6 @@
"babel-cli": "^6.11.4", "babel-cli": "^6.11.4",
"babel-preset-es2015": "^6.13.2", "babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1", "babel-preset-react": "^6.11.1",
"mediaelement": "^2.23.4",
"node-sass": "^3.8.0", "node-sass": "^3.8.0",
"plyr": "^2.0.12", "plyr": "^2.0.12",
"rc-progress": "^2.0.6", "rc-progress": "^2.0.6",

View file

@ -143,6 +143,14 @@ p
font-style: italic; font-style: italic;
} }
/*should be redone/moved*/
.file-list__header {
.busy-indicator {
float: left;
margin-top: 12px;
}
}
.sort-section { .sort-section {
display: block; display: block;
margin-bottom: $spacing-vertical * 2/3; margin-bottom: $spacing-vertical * 2/3;

View file

@ -1,16 +0,0 @@
@import "global";
.mejs-container, .mejs-overlay, .mejs-mediaelement {
width: 100%;
height: 100%;
}
.me-plugin {
width: 100%;
height: 100%;
> embed {
width: 100%;
height: 100%;
}
}

View file

@ -1,6 +1,5 @@
@import "_reset"; @import "_reset";
@import "_icons"; @import "_icons";
@import "_mediaelement";
@import "_gui"; @import "_gui";
@import "component/_table"; @import "component/_table";
@import "component/_button.scss"; @import "component/_button.scss";
@ -20,6 +19,5 @@
@import "component/_snack-bar.scss"; @import "component/_snack-bar.scss";
@import "component/_video.scss"; @import "component/_video.scss";
@import "page/_developer.scss"; @import "page/_developer.scss";
@import "page/_watch.scss";
@import "page/_reward.scss"; @import "page/_reward.scss";
@import "page/_show.scss"; @import "page/_show.scss";

View file

@ -3,6 +3,9 @@ video {
box-sizing: border-box; box-sizing: border-box;
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
} }
.video { .video {
@ -15,6 +18,7 @@ video {
max-width: $width-page-constrained; max-width: $width-page-constrained;
max-height: $height-video-embedded; max-height: $height-video-embedded;
height: $height-video-embedded; height: $height-video-embedded;
position: relative; /*for .plyr below*/
video { video {
height: 100%; height: 100%;
} }
@ -24,6 +28,10 @@ video {
&.video--active { &.video--active {
/*background: none;*/ /*background: none;*/
} }
.plyr {
top: 50%;
transform: translateY(-50%);
}
} }
.video__cover { .video__cover {

View file

@ -1,48 +0,0 @@
.video__overlay {
position: absolute;
top: 0px;
left: 0px;
color: #fff;
z-index: 1;
}
.video__back {
margin-top: 30px;
margin-left: 50px;
display: flex;
flex-direction: row;
align-items: center;
}
.video__back-link {
font-size: 50px;
}
.video__back-label {
opacity: 0.5;
transition: opacity 100ms ease-in;
}
.video__back-link:hover + .video__back-label {
opacity: 1;
}
$video-back-background: #333;
$video-back-size: 20px;
.video__back-label-arrow {
color: $video-back-background;
font-size: $video-back-size;
}
.video__back-label-content {
display: inline-block;
margin-left: -2px;
font-size: $video-back-size;
padding: $spacing-vertical / 2;
border-radius: 3px;
background-color: $video-back-background;
color: #fff;
pointer-events: none;
}