diff --git a/CHANGELOG.md b/CHANGELOG.md index bf84a1a32..65aecac57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 * Add special support for building channel claims in lbryuri module * Enable windows code signing of binary + * Support for opening LBRY URIs from links in other apps ### Changed @@ -66,6 +67,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Fixed * 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) diff --git a/app/main.js b/app/main.js index 90668b138..6fb0ba981 100644 --- a/app/main.js +++ b/app/main.js @@ -37,6 +37,22 @@ let readyToQuit = false; // send it to, it's cached in this variable. 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 formatRc(ver) { // Adds dash if needed to make RC suffix semver friendly @@ -186,6 +202,29 @@ function quitNow() { 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(){ launchDaemonIfNotRunning(); @@ -324,6 +363,8 @@ function upgrade(event, installerPath) { ipcMain.on('upgrade', upgrade); +app.setAsDefaultProtocolClient('lbry'); + if (process.platform == 'darwin') { app.on('open-url', (event, uri) => { if (!win) { @@ -333,7 +374,10 @@ if (process.platform == 'darwin') { win.webContents.send('open-uri-requested', uri); } }); -} else if (process.argv.length >= 3) { - // No open-url event on Win, but we can still handle URIs provided at launch time - win.webContents.send('open-uri-requested', process.argv[2]); +} else if (process.argv.length >= 2) { + if (!win) { + openUri = denormalizeUri(process.argv[1]); + } else { + win.webContents.send('open-uri-requested', denormalizeUri(process.argv[1])); + } } diff --git a/lbry b/lbry new file mode 160000 index 000000000..d99fc519b --- /dev/null +++ b/lbry @@ -0,0 +1 @@ +Subproject commit d99fc519b56ee910a44ef4af668b0770e9430d12 diff --git a/lbryschema b/lbryschema new file mode 160000 index 000000000..5c2441fa1 --- /dev/null +++ b/lbryschema @@ -0,0 +1 @@ +Subproject commit 5c2441fa13e39ba7280292519041e14ec696d753 diff --git a/lbryum b/lbryum new file mode 160000 index 000000000..950b95aa7 --- /dev/null +++ b/lbryum @@ -0,0 +1 @@ +Subproject commit 950b95aa7e45a2c15b269d807f6ff8e16bae4304 diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 14bad12cd..8ede77fee 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -6,7 +6,6 @@ import { selectUpgradeDownloadItem, selectUpgradeFilename, selectPageTitle, - selectCurrentPath, } from 'selectors/app' const {remote, ipcRenderer, shell} = require('electron'); @@ -32,8 +31,7 @@ export function doNavigate(path, params = {}) { const state = getState() const pageTitle = selectPageTitle(state) - history.pushState(params, pageTitle, url) - window.document.title = pageTitle + dispatch(doHistoryPush(params, pageTitle, url)) } } @@ -45,7 +43,6 @@ export function doChangePath(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) { diff --git a/ui/js/actions/availability.js b/ui/js/actions/availability.js index 74c85eaa3..501a2eeda 100644 --- a/ui/js/actions/availability.js +++ b/ui/js/actions/availability.js @@ -1,39 +1,29 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import { - selectCurrentUri, -} from 'selectors/app' + selectFetchingAvailability +} from 'selectors/availability' -export function doFetchUriAvailability(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() { +export function doFetchAvailability(uri) { return function(dispatch, 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, + } + }) + }) + } } -} +} \ No newline at end of file diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index cc64d8d9a..40ff1b048 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -3,18 +3,18 @@ import lbry from 'lbry' import lbryio from 'lbryio' import lbryuri from 'lbryuri' import rewards from 'rewards' -import { - selectCurrentUri, -} from 'selectors/app' import { selectBalance, } from 'selectors/wallet' import { - selectCurrentUriFileInfo, - selectDownloadingByUri, + selectFileInfoForUri, + selectUrisDownloading, } from 'selectors/file_info' import { - selectCurrentUriCostInfo, + selectResolvingUris +} from 'selectors/content' +import { + selectCostInfoForUri, } from 'selectors/cost_info' import { selectClaimsByUri, @@ -22,94 +22,44 @@ import { import { doOpenModal, } from 'actions/app' -import { - doFetchCostInfoForUri, -} from 'actions/cost_info' export function doResolveUri(uri) { return function(dispatch, getState) { - dispatch({ - type: types.RESOLVE_URI_STARTED, - data: { uri } - }) - lbry.resolve({ uri }).then((resolutionInfo) => { - const { - claim, - certificate, - } = resolutionInfo ? resolutionInfo : { claim : null, certificate: null } + const state = getState() + const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1 + if (!alreadyResolving) { dispatch({ - type: types.RESOLVE_URI_COMPLETED, - data: { - uri, + type: types.RESOLVE_URI_STARTED, + data: { uri } + }) + + lbry.resolve({ uri }).then((resolutionInfo) => { + const { claim, certificate, - } - }) - - 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)) - }) + } = resolutionInfo ? resolutionInfo : { claim : null, certificate: null } dispatch({ - type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, + type: types.RESOLVE_URI_COMPLETED, data: { - fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), - } - }) - }); - }); - } -} - -export function doFetchPublishedContent() { - return function(dispatch, getState) { - const state = getState() - - dispatch({ - type: types.FETCH_PUBLISHED_CONTENT_STARTED, - }) - - lbry.claim_list_mine().then((claimInfos) => { - dispatch({ - type: types.FETCH_MY_CLAIMS_COMPLETED, - data: { - claims: claimInfos, - } - }) - lbry.file_list().then((fileInfos) => { - const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout) - - dispatch({ - type: types.FETCH_PUBLISHED_CONTENT_COMPLETED, - data: { - fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), + uri, + claim, + certificate, } }) }) + } + } +} + +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] } }) + // + // dispatch({ + // type: types.FETCH_FEATURED_CONTENT_COMPLETED, + // data: { + // categories: ["FOO"], + // uris: { FOO: ["lbry://gtasoc"]}, + // } + // }) dispatch({ type: types.FETCH_FEATURED_CONTENT_COMPLETED, @@ -175,6 +133,7 @@ export function doUpdateLoadStatus(uri, outpoint) { type: types.DOWNLOADING_COMPLETED, data: { uri, + outpoint, fileInfo, } }) @@ -190,6 +149,7 @@ export function doUpdateLoadStatus(uri, outpoint) { type: types.DOWNLOADING_PROGRESSED, data: { uri, + outpoint, fileInfo, 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) { return function(dispatch, getState) { const state = getState() @@ -216,6 +169,7 @@ export function doDownloadFile(uri, streamInfo) { type: types.DOWNLOADING_STARTED, data: { uri, + outpoint: streamInfo.outpoint, fileInfo, } }) @@ -230,10 +184,9 @@ export function doDownloadFile(uri, streamInfo) { } } -export function doLoadVideo() { +export function doLoadVideo(uri) { return function(dispatch, getState) { const state = getState() - const uri = selectCurrentUri(state) dispatch({ type: types.LOADING_VIDEO_STARTED, @@ -260,14 +213,13 @@ export function doLoadVideo() { } } -export function doWatchVideo() { +export function doPurchaseUri(uri) { return function(dispatch, getState) { const state = getState() - const uri = selectCurrentUri(state) const balance = selectBalance(state) - const fileInfo = selectCurrentUriFileInfo(state) - const costInfo = selectCurrentUriCostInfo(state) - const downloadingByUri = selectDownloadingByUri(state) + const fileInfo = selectFileInfoForUri(state, { uri }) + const costInfo = selectCostInfoForUri(state, { uri }) + const downloadingByUri = selectUrisDownloading(state) const alreadyDownloading = !!downloadingByUri[uri] const { cost } = costInfo @@ -288,8 +240,8 @@ export function doWatchVideo() { } // the file is free or we have partially downloaded it - if (cost <= 0.01 || fileInfo.download_directory) { - dispatch(doLoadVideo()) + if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) { + dispatch(doLoadVideo(uri)) return Promise.resolve() } @@ -302,3 +254,44 @@ export function doWatchVideo() { 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 + } + }) + }) + } +} \ No newline at end of file diff --git a/ui/js/actions/cost_info.js b/ui/js/actions/cost_info.js index 5d28fb42c..79ff4bdd5 100644 --- a/ui/js/actions/cost_info.js +++ b/ui/js/actions/cost_info.js @@ -1,19 +1,51 @@ import * as types from 'constants/action_types' -import { - selectCurrentUri, -} from 'selectors/app' 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) { return function(dispatch, getState) { - dispatch({ - type: types.FETCH_COST_INFO_STARTED, - data: { - uri, - } - }) + const state = getState(), + claim = selectClaimsByUri(state)[uri], + isResolving = selectResolvingUris(state).indexOf(uri) !== -1, + 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({ type: types.FETCH_COST_INFO_COMPLETED, data: { @@ -21,15 +53,25 @@ export function doFetchCostInfoForUri(uri) { costInfo, } }) - }).catch(() => { - dispatch({ - type: types.FETCH_COST_INFO_COMPLETED, - data: { - uri, - costInfo: {} - } - }) - }) + } + + if (isGenerous && claim) { + let cost + const fee = claim.value.stream.metadata.fee; + 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) + } } } diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index b7611fb3d..23ba50e71 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -1,11 +1,17 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import { - selectCurrentUri, -} from 'selectors/app' + doClaimListMine +} from 'actions/content' import { - selectCurrentUriClaimOutpoint, + selectClaimsByUri, + selectClaimListMineIsPending, } from 'selectors/claims' +import { + selectFileListIsPending, + selectAllFileInfos, + selectUrisLoading, +} from 'selectors/file_info' import { doCloseModal, } from 'actions/app' @@ -14,29 +20,54 @@ const { shell, } = require('electron') -export function doFetchCurrentUriFileInfo() { +export function doFetchFileInfo(uri) { return function(dispatch, getState) { const state = getState() - const uri = selectCurrentUri(state) - const outpoint = selectCurrentUriClaimOutpoint(state) + const claim = selectClaimsByUri(state)[uri] + const outpoint = claim ? `${claim.txid}:${claim.nout}` : null + const alreadyFetching = !!selectUrisLoading(state)[uri] - dispatch({ - type: types.FETCH_FILE_INFO_STARTED, - data: { - uri, - outpoint, - } - }) - - lbry.file_list({ outpoint: outpoint, full_status: true }).then(([fileInfo]) => { + if (!alreadyFetching) { dispatch({ - type: types.FETCH_FILE_INFO_COMPLETED, + type: types.FETCH_FILE_INFO_STARTED, data: { - uri, - fileInfo, + outpoint, } }) - }) + + 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) { + dispatch({ - type: types.DELETE_FILE_STARTED, + type: types.FILE_DELETE, data: { - uri, - fileInfo, - deleteFromComputer, + outpoint } }) - const successCallback = () => { - dispatch({ - type: types.DELETE_FILE_COMPLETED, - data: { - uri, - } - }) - 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.file_delete({ + outpoint: outpoint, + delete_target_file: deleteFromComputer, }) - lbry.claim_list_mine().then((myClaimInfos) => { - lbry.file_list().then((fileInfos) => { - const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); - - dispatch({ - type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, - data: { - fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), - } - }) - }); - }); + dispatch(doCloseModal()) + } +} + + +export function doFetchFileInfosAndPublishedClaims() { + return function(dispatch, getState) { + const state = getState(), + isClaimListMinePending = selectClaimListMineIsPending(state), + isFileInfoListPending = selectFileListIsPending(state) + + if (isClaimListMinePending === undefined) { + dispatch(doClaimListMine()) + } + + if (isFileInfoListPending === undefined) { + dispatch(doFileList()) + } } } diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index b2748ff1b..8dd85d822 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -6,6 +6,7 @@ import { } from 'actions/content' import { doNavigate, + doHistoryPush } from 'actions/app' import { selectCurrentPage, @@ -29,6 +30,8 @@ export function doSearch(query) { if(page != 'search') { dispatch(doNavigate('search', { query: query })) + } else { + dispatch(doHistoryPush({ query }, "Search for " + query, '/search')) } lighthouse.search(query).then(results => { diff --git a/ui/js/actions/settings.js b/ui/js/actions/settings.js new file mode 100644 index 000000000..18e3a5004 --- /dev/null +++ b/ui/js/actions/settings.js @@ -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 + } + }) + }) + } +} \ No newline at end of file diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index a94c0e6f6..dcb94bd0f 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -2,30 +2,23 @@ import React from 'react'; import { connect } from 'react-redux' import { - selectCurrentPage, selectCurrentModal, - selectDrawerOpen, - selectHeaderLinks, - selectSearchTerm, } from 'selectors/app' import { doCheckUpgradeAvailable, - doOpenModal, - doCloseModal, } from 'actions/app' +import { + doUpdateBalance, +} from 'actions/wallet' import App from './view' const select = (state) => ({ - currentPage: selectCurrentPage(state), modal: selectCurrentModal(state), - headerLinks: selectHeaderLinks(state), - searchTerm: selectSearchTerm(state) }) const perform = (dispatch) => ({ checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), - openModal: () => dispatch(doOpenModal()), - closeModal: () => dispatch(doCloseModal()), + updateBalance: (balance) => dispatch(doUpdateBalance(balance)) }) export default connect(select, perform)(App) diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index e6162c4dd..1c7ff4eb4 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -4,7 +4,8 @@ import Header from 'component/header'; import ErrorModal from 'component/errorModal' import DownloadingModal from 'component/downloadingModal' import UpgradeModal from 'component/upgradeModal' -import {Line} from 'rc-progress'; +import lbry from 'lbry' +import {Line} from 'rc-progress' class App extends React.Component { componentWillMount() { @@ -15,12 +16,15 @@ class App extends React.Component { if (!this.props.upgradeSkipped) { this.props.checkUpgradeAvailable() } + + lbry.balanceSubscribe((balance) => { + this.props.updateBalance(balance) + }) } render() { const { modal, - headerLinks, } = this.props return
diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index 261b065b1..7b934f339 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -200,7 +200,7 @@ const CodeRequiredStage = React.createClass({ }) if (!this.state.address) { - lbry.getUnusedAddress((address) => { + lbry.wallet_unused_address().then((address) => { setLocal('wallet_address', address); this.setState({ address: address }); }); diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index 21a2c05d0..a570cd413 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -3,9 +3,6 @@ import { connect, } from 'react-redux' import { - selectObscureNsfw, - selectHidePrice, - selectHasSignature, selectPlatform, } from 'selectors/app' import { @@ -14,7 +11,7 @@ import { makeSelectLoadingForUri, } from 'selectors/file_info' import { - makeSelectAvailabilityForUri, + makeSelectIsAvailableForUri, } from 'selectors/availability' import { selectCurrentModal, @@ -22,47 +19,50 @@ import { import { doCloseModal, doOpenModal, + doHistoryBack, } from 'actions/app' +import { + doFetchAvailability +} from 'actions/availability' import { doOpenFileInShell, doOpenFileInFolder, doDeleteFile, } from 'actions/file_info' import { - doWatchVideo, + doPurchaseUri, doLoadVideo, } from 'actions/content' import FileActions from './view' const makeSelect = () => { const selectFileInfoForUri = makeSelectFileInfoForUri() - const selectAvailabilityForUri = makeSelectAvailabilityForUri() + const selectIsAvailableForUri = makeSelectIsAvailableForUri() const selectDownloadingForUri = makeSelectDownloadingForUri() - const selectLoadingForUri = makeSelectLoadingForUri() const select = (state, props) => ({ - obscureNsfw: selectObscureNsfw(state), - hidePrice: selectHidePrice(state), - hasSignature: selectHasSignature(state), fileInfo: selectFileInfoForUri(state, props), - availability: selectAvailabilityForUri(state, props), + isAvailable: selectIsAvailableForUri(state, props), platform: selectPlatform(state), modal: selectCurrentModal(state), downloading: selectDownloadingForUri(state, props), - loading: selectLoadingForUri(state, props), }) return select } const perform = (dispatch) => ({ + checkAvailability: (uri) => dispatch(doFetchAvailability(uri)), closeModal: () => dispatch(doCloseModal()), openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)), openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)), - deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)), + deleteFile: (fileInfo, deleteFromComputer) => { + dispatch(doHistoryBack()) + dispatch(doDeleteFile(fileInfo, deleteFromComputer)) + }, openModal: (modal) => dispatch(doOpenModal(modal)), - downloadClick: () => dispatch(doWatchVideo()), - loadVideo: () => dispatch(doLoadVideo()) + startDownload: (uri) => dispatch(doPurchaseUri(uri)), + loadVideo: (uri) => dispatch(doLoadVideo(uri)) }) -export default connect(makeSelect, perform)(FileActions) +export default connect(makeSelect, perform)(FileActions) \ No newline at end of file diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 5d74949bc..c1e9d6e3c 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -1,7 +1,5 @@ import React from 'react'; -import lbry from 'lbry'; -import lbryuri from 'lbryuri'; -import {Icon,} from 'component/common'; +import {Icon,BusyMessage} from 'component/common'; import FilePrice from 'component/filePrice' import {Modal} from 'component/modal'; import {FormField} from 'component/form'; @@ -9,14 +7,36 @@ import Link from 'component/link'; import {ToolTip} from 'component/tooltip'; import {DropDownMenu, DropDownMenuItem} from 'component/menu'; -class FileActionsRow extends React.Component { +class FileActions extends React.Component { constructor(props) { super(props) this.state = { + forceShowActions: 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) { this.setState({ deleteChecked: event.target.checked, @@ -25,65 +45,73 @@ class FileActionsRow extends React.Component { onAffirmPurchase() { this.props.closeModal() - this.props.loadVideo() + this.props.loadVideo(this.props.uri) } render() { const { fileInfo, + isAvailable, platform, downloading, - loading, uri, deleteFile, openInFolder, openInShell, modal, openModal, - affirmPurchase, closeModal, - downloadClick, + startDownload, } = this.props - const { - deleteChecked, - } = this.state + const deleteChecked = this.state.deleteChecked, + metadata = fileInfo ? fileInfo.metadata : null, + 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) - { - return null; - } + if (downloading) { - 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 = ; - } else if (downloading || loading) { const progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', labelWithIcon = {label}; - linkBlock = ( -
-
{labelWithIcon}
- {labelWithIcon} -
- ); + content =
+
{labelWithIcon}
+ {labelWithIcon} +
+ + } else if (!fileInfo && isAvailable === undefined) { + + content = + + } else if (!fileInfo && !isAvailable && !this.state.forceShowActions) { + + content =
+
Content unavailable.
+ + +
+ + } else if (fileInfo === null && !downloading) { + + content = { startDownload(uri) } } />; + + } else if (fileInfo && fileInfo.download_path) { + content = openInShell(fileInfo)} />; } else { - linkBlock = openInShell(fileInfo)} />; + console.log('handle this case of file action props?'); + console.log(this.props) } - const title = metadata ? metadata.title : uri; return ( -
- {fileInfo !== null || fileInfo.isMine - ? linkBlock - : null} +
+ { content } { showMenu ? openInFolder(fileInfo)} label={openInFolderMessage} /> @@ -91,7 +119,7 @@ class FileActionsRow extends React.Component { : '' } - Are you sure you'd like to buy {title} for credits? + This will purchase {title} for credits. @@ -102,62 +130,18 @@ class FileActionsRow extends React.Component { LBRY was unable to download the stream {uri}. deleteFile(uri, fileInfo, deleteChecked)} - onAborted={closeModal}> + contentLabel="Not enough credits" + type="confirm" + confirmButtonLabel="Remove" + onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)} + onAborted={closeModal}>

Are you sure you'd like to remove {title} from LBRY?

-
+ ); } } -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 (
- { - fileInfo || this.state.available || this.state.forceShowActions - ? - :
-
Content unavailable.
- - -
- } -
); - } -} - export default FileActions diff --git a/ui/js/component/fileCard/index.js b/ui/js/component/fileCard/index.js index 1fcd3ab9b..89ebc5040 100644 --- a/ui/js/component/fileCard/index.js +++ b/ui/js/component/fileCard/index.js @@ -7,21 +7,20 @@ import { } from 'actions/app' import { doResolveUri, + doCancelResolveUri, } from 'actions/content' import { - selectHidePrice, selectObscureNsfw, } from 'selectors/app' import { makeSelectClaimForUri, - makeSelectSourceForUri, makeSelectMetadataForUri, } from 'selectors/claims' import { makeSelectFileInfoForUri, } from 'selectors/file_info' import { - makeSelectResolvingUri, + makeSelectIsResolvingForUri, } from 'selectors/content' import FileCard from './view' @@ -29,17 +28,13 @@ const makeSelect = () => { const selectClaimForUri = makeSelectClaimForUri() const selectFileInfoForUri = makeSelectFileInfoForUri() const selectMetadataForUri = makeSelectMetadataForUri() - const selectSourceForUri = makeSelectSourceForUri() - const selectResolvingUri = makeSelectResolvingUri() + const selectResolvingUri = makeSelectIsResolvingForUri() const select = (state, props) => ({ claim: selectClaimForUri(state, props), fileInfo: selectFileInfoForUri(state, props), - hidePrice: selectHidePrice(state), obscureNsfw: selectObscureNsfw(state), - hasSignature: false, metadata: selectMetadataForUri(state, props), - source: selectSourceForUri(state, props), isResolvingUri: selectResolvingUri(state, props), }) @@ -49,6 +44,7 @@ const makeSelect = () => { const perform = (dispatch) => ({ navigate: (path, params) => dispatch(doNavigate(path, params)), resolveUri: (uri) => dispatch(doResolveUri(uri)), + cancelResolveUri: (uri) => dispatch(doCancelResolveUri(uri)) }) export default connect(makeSelect, perform)(FileCard) diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 19e03bc1f..918476a53 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -2,21 +2,41 @@ import React from 'react'; import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import {Thumbnail, TruncatedText,} from 'component/common'; +import {Thumbnail, TruncatedText, Icon} from 'component/common'; import FilePrice from 'component/filePrice' import UriIndicator from 'component/uriIndicator'; class FileCard extends React.Component { - componentDidMount() { + componentWillMount() { + this.resolve(this.props) + } + + componentWillReceiveProps(nextProps) { + this.resolve(nextProps) + } + + resolve(props) { const { isResolvingUri, resolveUri, claim, uri, + } = props + + if(!isResolvingUri && claim === undefined && uri) { + resolveUri(uri) + } + } + + componentWillUnmount() { + const { + isResolvingUri, + cancelResolveUri, + uri } = this.props - if(!isResolvingUri && !claim && uri) { - resolveUri(uri) + if (isResolvingUri) { + cancelResolveUri(uri) } } @@ -35,10 +55,11 @@ class FileCard extends React.Component { render() { const { + claim, + fileInfo, metadata, isResolvingUri, navigate, - hidePrice, } = this.props const uri = lbryuri.normalize(this.props.uri); @@ -50,6 +71,8 @@ class FileCard extends React.Component { description = "Loading..." } else if (metadata && metadata.description) { description = metadata.description + } else if (claim === null) { + description = 'This address contains no content.' } return ( @@ -59,7 +82,10 @@ class FileCard extends React.Component {
{title}
- { !hidePrice ? : null} + + + { fileInfo ? {' '} : '' } +
diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index ebd94c0ee..eb3b620b8 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -58,8 +58,8 @@ class FileList extends React.Component { render() { const { handleSortChanged, + fetching, fileInfos, - hidePrices, } = this.props const { sortBy, @@ -71,10 +71,11 @@ class FileList extends React.Component { contentName: fileInfo.name, channelName: fileInfo.channel_name, }) - content.push() + content.push() }) return ( -
+
+ { fetching && } Sort by { ' ' } diff --git a/ui/js/component/filePrice/index.js b/ui/js/component/filePrice/index.js index c5a9497d0..726ceb3ee 100644 --- a/ui/js/component/filePrice/index.js +++ b/ui/js/component/filePrice/index.js @@ -2,6 +2,9 @@ import React from 'react' import { connect, } from 'react-redux' +import { + doFetchCostInfoForUri, +} from 'actions/cost_info' import { makeSelectCostInfoForUri, } from 'selectors/cost_info' @@ -9,6 +12,7 @@ import FilePrice from './view' const makeSelect = () => { const selectCostInfoForUri = makeSelectCostInfoForUri() + const select = (state, props) => ({ costInfo: selectCostInfoForUri(state, props), }) @@ -17,6 +21,8 @@ const makeSelect = () => { } const perform = (dispatch) => ({ + fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)), + // cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(uri)) }) export default connect(makeSelect, perform)(FilePrice) diff --git a/ui/js/component/filePrice/view.jsx b/ui/js/component/filePrice/view.jsx index 17b830bf2..4edc3a3e7 100644 --- a/ui/js/component/filePrice/view.jsx +++ b/ui/js/component/filePrice/view.jsx @@ -3,19 +3,41 @@ import { CreditAmount, } from 'component/common' -const FilePrice = (props) => { - const { - costInfo, - look = 'indicator', - } = props - - const isEstimate = costInfo ? !costInfo.includesData : null - - if (!costInfo) { - return ???; +class FilePrice extends React.Component{ + componentWillMount() { + this.fetchCost(this.props) } - return + 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 ???; + } + + return + } } export default FilePrice diff --git a/ui/js/component/fileTile/index.js b/ui/js/component/fileTile/index.js index dd17e8e60..d61e58b64 100644 --- a/ui/js/component/fileTile/index.js +++ b/ui/js/component/fileTile/index.js @@ -10,41 +10,30 @@ import { } from 'actions/content' import { makeSelectClaimForUri, - makeSelectSourceForUri, makeSelectMetadataForUri, } from 'selectors/claims' import { makeSelectFileInfoForUri, } from 'selectors/file_info' -import { - makeSelectFetchingAvailabilityForUri, - makeSelectAvailabilityForUri, -} from 'selectors/availability' import { selectObscureNsfw, } from 'selectors/app' import { - makeSelectResolvingUri, + makeSelectIsResolvingForUri, } from 'selectors/content' import FileTile from './view' const makeSelect = () => { const selectClaimForUri = makeSelectClaimForUri() const selectFileInfoForUri = makeSelectFileInfoForUri() - const selectFetchingAvailabilityForUri = makeSelectFetchingAvailabilityForUri() - const selectAvailabilityForUri = makeSelectAvailabilityForUri() const selectMetadataForUri = makeSelectMetadataForUri() - const selectSourceForUri = makeSelectSourceForUri() - const selectResolvingUri = makeSelectResolvingUri() + const selectResolvingUri = makeSelectIsResolvingForUri() const select = (state, props) => ({ claim: selectClaimForUri(state, props), fileInfo: selectFileInfoForUri(state, props), - fetchingAvailability: selectFetchingAvailabilityForUri(state, props), - selectAvailabilityForUri: selectAvailabilityForUri(state, props), obscureNsfw: selectObscureNsfw(state), metadata: selectMetadataForUri(state, props), - source: selectSourceForUri(state, props), isResolvingUri: selectResolvingUri(state, props), }) diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index 455e59d5a..250bf6296 100644 --- a/ui/js/component/fileTile/view.jsx +++ b/ui/js/component/fileTile/view.jsx @@ -13,11 +13,8 @@ class FileTile extends React.Component { constructor(props) { super(props) - this._fileInfoSubscribeId = null - this._isMounted = null this.state = { showNsfwHelp: false, - isHidden: false, } } @@ -29,31 +26,11 @@ class FileTile extends React.Component { uri, } = this.props - this._isMounted = true; - - if (this.props.hideOnRemove) { - this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); - } - if(!isResolvingUri && !claim && uri) { resolveUri(uri) } } - componentWillUnmount() { - if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); - } - } - - onFileInfoUpdate(fileInfo) { - if (!fileInfo && this._isMounted && this.props.hideOnRemove) { - this.setState({ - isHidden: true - }); - } - } - handleMouseOver() { if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { this.setState({ @@ -71,10 +48,6 @@ class FileTile extends React.Component { } render() { - if (this.state.isHidden) { - return null; - } - const { claim, metadata, @@ -86,18 +59,22 @@ class FileTile extends React.Component { const uri = lbryuri.normalize(this.props.uri); const isClaimed = !!claim; + const isClaimable = lbryuri.isClaimable(uri) const title = isClaimed && metadata && metadata.title ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw; let onClick = () => navigate('/show', { uri }) let description = "" if (isClaimed) { - description = metadata.description + description = metadata && metadata.description } else if (isResolvingUri) { description = "Loading..." } else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) { - onClick = () => navigate('/publish') - description = This location is unclaimed - put something here! + onClick = () => navigate('/publish', { }) + description = + This location is unused. { ' ' } + { isClaimable && Put something here! } + } else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) { description = This file is pending confirmation. } diff --git a/ui/js/component/router/index.jsx b/ui/js/component/router/index.jsx index c75222949..48bf22ccc 100644 --- a/ui/js/component/router/index.jsx +++ b/ui/js/component/router/index.jsx @@ -2,14 +2,13 @@ import React from 'react'; import { connect } from 'react-redux'; import Router from './view.jsx'; import { - selectCurrentPage + selectCurrentPage, + selectCurrentParams, } from 'selectors/app.js'; const select = (state) => ({ + params: selectCurrentParams(state), currentPage: selectCurrentPage(state) }) -const perform = { -} - export default connect(select, null)(Router); diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 8b3d66102..53dec32ef 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import SettingsPage from 'page/settings.js'; +import SettingsPage from 'page/settings'; import HelpPage from 'page/help'; import ReportPage from 'page/report.js'; import StartPage from 'page/start.js'; @@ -25,25 +25,26 @@ const route = (page, routesMap) => { const Router = (props) => { const { currentPage, + params, } = props; return route(currentPage, { - 'settings': , - 'help': , - 'report': , - 'downloaded': , - 'published': , - 'start': , - 'wallet': , - 'send': , - 'receive': , - 'show': , - 'channel': , - 'publish': , - 'developer': , - 'discover': , - 'rewards': , - 'search': , + 'settings': , + 'help': , + 'report': , + 'downloaded': , + 'published': , + 'start': , + 'wallet': , + 'send': , + 'receive': , + 'show': , + 'channel': , + 'publish': , + 'developer': , + 'discover': , + 'rewards': , + 'search': , }) } diff --git a/ui/js/component/video/index.js b/ui/js/component/video/index.js index 94978f702..5566da840 100644 --- a/ui/js/component/video/index.js +++ b/ui/js/component/video/index.js @@ -9,34 +9,46 @@ import { selectCurrentModal, } from 'selectors/app' import { - doWatchVideo, + doPurchaseUri, doLoadVideo, } from 'actions/content' import { - selectLoadingCurrentUri, - selectCurrentUriFileReadyToPlay, - selectCurrentUriIsPlaying, - selectCurrentUriFileInfo, - selectDownloadingCurrentUri, + makeSelectMetadataForUri +} from 'selectors/claims' +import { + makeSelectFileInfoForUri, + makeSelectLoadingForUri, + makeSelectDownloadingForUri, } from 'selectors/file_info' import { - selectCurrentUriCostInfo, + makeSelectCostInfoForUri, } from 'selectors/cost_info' import Video from './view' -const select = (state) => ({ - costInfo: selectCurrentUriCostInfo(state), - fileInfo: selectCurrentUriFileInfo(state), - modal: selectCurrentModal(state), - isLoading: selectLoadingCurrentUri(state), - readyToPlay: selectCurrentUriFileReadyToPlay(state), - isDownloading: selectDownloadingCurrentUri(state), -}) + +const makeSelect = () => { + const selectCostInfo = makeSelectCostInfoForUri() + const selectFileInfo = makeSelectFileInfoForUri() + const selectIsLoading = makeSelectLoadingForUri() + const selectIsDownloading = makeSelectDownloadingForUri() + 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) => ({ - loadVideo: () => dispatch(doLoadVideo()), - watchVideo: (elem) => dispatch(doWatchVideo()), + loadVideo: (uri) => dispatch(doLoadVideo(uri)), + purchaseUri: (uri) => dispatch(doPurchaseUri(uri)), closeModal: () => dispatch(doCloseModal()), }) -export default connect(select, perform)(Video) \ No newline at end of file +export default connect(makeSelect, perform)(Video) \ No newline at end of file diff --git a/ui/js/component/video/view.jsx b/ui/js/component/video/view.jsx index 5485f1a48..3d364bcc9 100644 --- a/ui/js/component/video/view.jsx +++ b/ui/js/component/video/view.jsx @@ -1,17 +1,21 @@ import React from 'react'; -import { - Icon, - Thumbnail, -} from 'component/common'; import FilePrice from 'component/filePrice' import Link from 'component/link'; import Modal from 'component/modal'; -class WatchLink extends React.Component { - confirmPurchaseClick() { +class VideoPlayButton extends React.Component { + onPurchaseConfirmed() { this.props.closeModal() 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() { @@ -19,7 +23,6 @@ class WatchLink extends React.Component { button, label, className, - onWatchClick, metadata, metadata: { title, @@ -32,13 +35,21 @@ class WatchLink extends React.Component { fileInfo, } = this.props + /* + title={ + isLoading ? "Video is Loading" : + !costInfo ? "Waiting on cost info..." : + fileInfo === undefined ? "Waiting on file info..." : "" + } + */ + return (
+ onClick={this.onWatchClick.bind(this)} /> {modal} You don't have enough LBRY credits to pay for this stream. @@ -47,9 +58,9 @@ class WatchLink extends React.Component { type="confirm" isOpen={modal == 'affirmPurchase'} contentLabel="Confirm Purchase" - onConfirmed={this.confirmPurchaseClick.bind(this)} + onConfirmed={this.onPurchaseConfirmed.bind(this)} onAborted={closeModal}> - Are you sure you'd like to buy {this.props.metadata.title} for credits? + This will purchase {title} for credits. @@ -67,16 +78,6 @@ class Video extends React.Component { this.state = { isPlaying: false } } - onWatchClick() { - this.props.watchVideo().then(() => { - if (!this.props.modal) { - this.setState({ - isPlaying: true - }) - } - }) - } - startPlaying() { this.setState({ isPlaying: true @@ -85,8 +86,6 @@ class Video extends React.Component { render() { const { - readyToPlay = false, - thumbnail, metadata, isLoading, isDownloading, @@ -96,6 +95,8 @@ class Video extends React.Component { isPlaying = false, } = this.state + const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0 + let loadStatusMessage = '' if (isLoading) { @@ -105,39 +106,42 @@ class Video extends React.Component { } return ( -
{ - isPlaying ? - !readyToPlay ? - this is the world's worst loading screen and we shipped our software with it anyway...

{loadStatusMessage}
: - : +
{ + isPlaying || isLoading ? + (!isReadyToPlay ? + this is the world's worst loading screen and we shipped our software with it anyway...

{loadStatusMessage}
: + ) :
- +
}
); } } -class VideoPlayer extends React.PureComponent { +class VideoPlayer extends React.Component { componentDidMount() { const elem = this.refs.video const { + autoplay, downloadPath, contentType, } = this.props const players = plyr.setup(elem) - players[0].play() + if (autoplay) { + players[0].play() + } } render() { const { downloadPath, contentType, + poster, } = this.props return ( -
+
+ + ) + } } export default FilePage; \ No newline at end of file diff --git a/ui/js/page/help/index.jsx b/ui/js/page/help/index.js similarity index 100% rename from ui/js/page/help/index.jsx rename to ui/js/page/help/index.js diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index c57c4d1f0..2d4f516a6 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -24,9 +24,6 @@ var HelpPage = React.createClass({ }); }); }, - componentDidMount: function() { - document.title = "Help"; - }, render: function() { let ver, osName, platform, newVerLink; if (this.state.versionInfo) { diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index 29347b6ed..d083fd118 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -5,9 +5,13 @@ import { import { doNavigate, } from 'actions/app' +import { + selectMyClaims +} from 'selectors/claims' import PublishPage from './view' const select = (state) => ({ + myClaims: selectMyClaims(state) }) const perform = (dispatch) => ({ diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index 7a6e4806f..73c0df9e6 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -1,5 +1,6 @@ import React from 'react'; import lbry from 'lbry'; +import lbryuri from 'lbryuri' import {FormField, FormRow} from 'component/form.js'; import Link from 'component/link'; import rewards from 'rewards'; @@ -100,7 +101,7 @@ var PublishPage = React.createClass({ }; if (this.state.isFee) { - lbry.getUnusedAddress((address) => { + lbry.wallet_unused_address().then((address) => { metadata.fee = {}; metadata.fee[this.state.feeCurrency] = { amount: parseFloat(this.state.feeAmount), @@ -169,7 +170,7 @@ var PublishPage = React.createClass({ return; } - if (!lbry.nameIsValid(rawName, false)) { + if (!lbryuri.isValidName(rawName, false)) { this.refs.name.showError('LBRY names must contain only letters, numbers and dashes.'); return; } @@ -182,50 +183,45 @@ var PublishPage = React.createClass({ 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) { - // A new name has been typed already, so bail return; } - this.setState({ - 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 + if (!claimInfo) { this.setState({ - name: name, 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) { const newChannelName = (event.target.value.startsWith('@') ? event.target.value : '@' + event.target.value); - if (newChannelName.length > 1 && !lbry.nameIsValid(newChannelName.substr(1), false)) { + if (newChannelName.length > 1 && !lbryuri.isValidName(newChannelName.substr(1), false)) { this.refs.newChannelName.showError('LBRY channel names must contain only letters, numbers and dashes.'); return; } else { diff --git a/ui/js/page/search/view.jsx b/ui/js/page/search/view.jsx index 30f9b2aee..ac5486b12 100644 --- a/ui/js/page/search/view.jsx +++ b/ui/js/page/search/view.jsx @@ -8,14 +8,15 @@ import {BusyMessage} from 'component/common.js'; class SearchPage extends React.Component{ render() { - const isValidUri = (query) => true //FIXME + console.log('render search page') + const { query, } = this.props return (
- { isValidUri(query) ? + { lbryuri.isValid(query) ?

Exact URL ({ + daemonSettings: selectDaemonSettings(state) +}) + +const perform = (dispatch) => ({ + setDaemonSetting: (key, value) => dispatch(doSetDaemonSetting(key, value)), +}) + +export default connect(select, perform)(SettingsPage) diff --git a/ui/js/page/settings.js b/ui/js/page/settings/view.jsx similarity index 85% rename from ui/js/page/settings.js rename to ui/js/page/settings/view.jsx index 29dbee601..201a92d96 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings/view.jsx @@ -1,19 +1,11 @@ import React from 'react'; -import {FormField, FormRow} from '../component/form.js'; +import {FormField, FormRow} from 'component/form.js'; import SubHeader from 'component/subHeader' -import lbry from '../lbry.js'; +import lbry from 'lbry.js'; var SettingsPage = React.createClass({ - _onSettingSaveSuccess: function() { - // This is bad. - // document.dispatchEvent(new CustomEvent('globalNotice', { - // detail: { - // message: "Settings saved", - // }, - // })) - }, setDaemonSetting: function(name, value) { - lbry.setDaemonSetting(name, value, this._onSettingSaveSuccess) + this.props.setDaemonSetting(name, value) }, setClientSetting: function(name, value) { lbry.setClientSetting(name, value) @@ -51,21 +43,15 @@ var SettingsPage = React.createClass({ this.setDaemonSetting('max_download', Number(event.target.value)); }, getInitialState: function() { + const daemonSettings = this.props.daemonSettings + return { - settings: null, + isMaxUpload: daemonSettings && daemonSettings.max_upload != 0, + isMaxDownload: daemonSettings && daemonSettings.max_download != 0, showNsfw: lbry.getClientSetting('showNsfw'), 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) { lbry.setClientSetting('showNsfw', event.target.checked); }, @@ -73,8 +59,12 @@ var SettingsPage = React.createClass({ }, render: function() { - if (!this.state.daemonSettings) { - return null; + const { + daemonSettings + } = this.props + + if (!daemonSettings) { + return
Failed to load settings.
; } /*
@@ -84,7 +74,7 @@ var SettingsPage = React.createClass({
@@ -99,7 +89,7 @@ var SettingsPage = React.createClass({
@@ -125,7 +115,7 @@ var SettingsPage = React.createClass({

diff --git a/ui/js/page/showPage/index.js b/ui/js/page/showPage/index.js index bbaba13f6..563b9cff3 100644 --- a/ui/js/page/showPage/index.js +++ b/ui/js/page/showPage/index.js @@ -6,24 +6,27 @@ import { doResolveUri, } from 'actions/content' import { - selectCurrentUri, -} from 'selectors/app' -import { - selectCurrentUriClaim, + makeSelectClaimForUri, } from 'selectors/claims' import { - selectCurrentUriIsResolving, + makeSelectIsResolvingForUri, } from 'selectors/content' import ShowPage from './view' -const select = (state, props) => ({ - claim: selectCurrentUriClaim(state), - uri: selectCurrentUri(state), - isResolvingUri: selectCurrentUriIsResolving(state) -}) +const makeSelect = () => { + const selectClaim = makeSelectClaimForUri(), + selectIsResolving = makeSelectIsResolvingForUri(); + + const select = (state, props) => ({ + claim: selectClaim(state, props), + isResolvingUri: selectIsResolving(state, props) + }) + + return select +} const perform = (dispatch) => ({ resolveUri: (uri) => dispatch(doResolveUri(uri)) }) -export default connect(select, perform)(ShowPage) +export default connect(makeSelect, perform)(ShowPage) diff --git a/ui/js/page/showPage/view.jsx b/ui/js/page/showPage/view.jsx index 7e84d3d30..96983139c 100644 --- a/ui/js/page/showPage/view.jsx +++ b/ui/js/page/showPage/view.jsx @@ -1,7 +1,9 @@ import React from 'react'; +import lbryuri from 'lbryuri' import { BusyMessage, } from 'component/common'; +import ChannelPage from 'page/channel' import FilePage from 'page/filePage' class ShowPage extends React.Component{ @@ -21,7 +23,7 @@ class ShowPage extends React.Component{ uri, } = props - if(!isResolvingUri && !claim && uri) { + if(!isResolvingUri && claim === undefined && uri) { resolveUri(uri) } } @@ -35,22 +37,22 @@ class ShowPage extends React.Component{ let innerContent = ""; - if (isResolvingUri) { + if (isResolvingUri || !claim) { innerContent =

{uri}

- : + { isResolvingUri && } + { claim === null && There's nothing at this location. }
-
; + } - else if (claim && claim.whatever) { - innerContent = "channel" - // innerContent = + else if (claim.name.length && claim.name[0] === '@') { + innerContent = } else if (claim) { - innerContent = + innerContent = } return ( @@ -59,4 +61,4 @@ class ShowPage extends React.Component{ } } -export default ShowPage +export default ShowPage \ No newline at end of file diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index 883ff068f..e3070ed40 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -9,7 +9,6 @@ const defaultState = { upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), daemonReady: false, obscureNsfw: !lbry.getClientSetting('showNsfw'), - hidePrice: false, hasSignature: false, } diff --git a/ui/js/reducers/availability.js b/ui/js/reducers/availability.js index 6c0e28a8b..6fe6e4ddf 100644 --- a/ui/js/reducers/availability.js +++ b/ui/js/reducers/availability.js @@ -9,10 +9,8 @@ reducers[types.FETCH_AVAILABILITY_STARTED] = function(state, action) { uri, } = action.data const newFetching = Object.assign({}, state.fetching) - const newByUri = Object.assign({}, newFetching.byUri) - newByUri[uri] = true - newFetching.byUri = newByUri + newFetching[uri] = true return Object.assign({}, state, { fetching: newFetching, @@ -24,12 +22,11 @@ reducers[types.FETCH_AVAILABILITY_COMPLETED] = function(state, action) { uri, availability, } = action.data + const newFetching = Object.assign({}, state.fetching) - const newFetchingByUri = Object.assign({}, newFetching.byUri) const newAvailabilityByUri = Object.assign({}, state.byUri) - delete newFetchingByUri[uri] - newFetching.byUri = newFetchingByUri + delete newFetching[uri] newAvailabilityByUri[uri] = availability return Object.assign({}, state, { diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index c758052bc..6acf0f111 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -8,30 +8,69 @@ const defaultState = { reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { const { uri, + certificate, claim, } = 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, { - byUri: newByUri, + claimsByUri: newClaims }) } -reducers[types.FETCH_MY_CLAIMS_COMPLETED] = function(state, action) { - const { - claims, - } = action.data - const newMine = Object.assign({}, state.mine) - const newById = Object.assign({}, newMine.byId) - - claims.forEach(claim => { - newById[claim.claim_id] = claim +reducers[types.RESOLVE_URI_CANCELED] = function(state, action) { + const uri = action.data.uri + const newClaims = Object.assign({}, state.claimsByUri) + delete newClaims[uri] + return Object.assign({}, state, { + claimsByUri: newClaims }) - 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, { - mine: newMine, + claimsByChannel: newClaims }) } diff --git a/ui/js/reducers/content.js b/ui/js/reducers/content.js index 8fee4c234..117b6c830 100644 --- a/ui/js/reducers/content.js +++ b/ui/js/reducers/content.js @@ -30,7 +30,7 @@ reducers[types.RESOLVE_URI_STARTED] = function(state, action) { const oldResolving = state.resolvingUris || [] const newResolving = Object.assign([], oldResolving) - if (newResolving.indexOf(uri) == -1) newResolving.push(uri) + if (newResolving.indexOf(uri) === -1) newResolving.push(uri) return Object.assign({}, state, { resolvingUris: newResolving @@ -48,51 +48,14 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { ...resolvingUris.slice(index + 1) ] - const newState = Object.assign({}, state, { + return Object.assign({}, state, { resolvingUris: newResolvingUris, }) - - return Object.assign({}, state, newState) } -reducers[types.FETCH_DOWNLOADED_CONTENT_STARTED] = function(state, action) { - return Object.assign({}, state, { - fetchingDownloadedContent: true, - }) -} -reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) { - const { - fileInfos - } = action.data - const newDownloadedContent = Object.assign({}, state.downloadedContent, { - fileInfos - }) - - return Object.assign({}, state, { - downloadedContent: newDownloadedContent, - fetchingDownloadedContent: false, - }) -} - -reducers[types.FETCH_PUBLISHED_CONTENT_STARTED] = function(state, action) { - return Object.assign({}, state, { - fetchingPublishedContent: true, - }) -} - -reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { - const { - fileInfos - } = action.data - const newPublishedContent = Object.assign({}, state.publishedContent, { - fileInfos - }) - - return Object.assign({}, state, { - publishedContent: newPublishedContent, - fetchingPublishedContent: false, - }) +reducers[types.RESOLVE_URI_CANCELED] = function(state, action) { + return reducers[types.RESOLVE_URI_COMPLETED](state, action) } export default function reducer(state = defaultState, action) { diff --git a/ui/js/reducers/file_info.js b/ui/js/reducers/file_info.js index 292508a2b..811c829ef 100644 --- a/ui/js/reducers/file_info.js +++ b/ui/js/reducers/file_info.js @@ -5,13 +5,35 @@ const reducers = {} const defaultState = { } +reducers[types.FILE_LIST_STARTED] = function(state, action) { + return Object.assign({}, state, { + isFileListPending: true, + }) +} + +reducers[types.FILE_LIST_COMPLETED] = function(state, action) { + const { + fileInfos, + } = action.data + + const newFileInfos = Object.assign({}, state.fileInfos) + fileInfos.forEach((fileInfo) => { + newFileInfos[fileInfo.outpoint] = fileInfo + }) + + return Object.assign({}, state, { + isFileListPending: false, + fileInfos: newFileInfos + }) +} + reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) { const { - uri, + outpoint } = action.data const newFetching = Object.assign({}, state.fetching) - newFetching[uri] = true + newFetching[outpoint] = true return Object.assign({}, state, { fetching: newFetching, @@ -20,17 +42,18 @@ reducers[types.FETCH_FILE_INFO_STARTED] = function(state, action) { reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { const { - uri, fileInfo, + outpoint, } = action.data - const newByUri = Object.assign({}, state.byUri) + + const newFileInfos = Object.assign({}, state.fileInfos) const newFetching = Object.assign({}, state.fetching) - newByUri[uri] = fileInfo || {} - delete newFetching[uri] + newFileInfos[outpoint] = fileInfo + delete newFetching[outpoint] return Object.assign({}, state, { - byUri: newByUri, + fileInfos: newFileInfos, fetching: newFetching, }) } @@ -38,93 +61,74 @@ reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) { reducers[types.DOWNLOADING_STARTED] = function(state, action) { const { uri, + outpoint, fileInfo, } = action.data - const newByUri = Object.assign({}, state.byUri) - const newDownloading = Object.assign({}, state.downloading) - const newDownloadingByUri = Object.assign({}, newDownloading.byUri) - const newLoading = Object.assign({}, state.loading) - const newLoadingByUri = Object.assign({}, newLoading) - newDownloadingByUri[uri] = true - newDownloading.byUri = newDownloadingByUri - newByUri[uri] = fileInfo - delete newLoadingByUri[uri] - newLoading.byUri = newLoadingByUri + const newFileInfos = Object.assign({}, state.fileInfos) + const newDownloading = Object.assign({}, state.urisDownloading) + const newLoading = Object.assign({}, state.urisLoading) + + newDownloading[uri] = true + newFileInfos[outpoint] = fileInfo + delete newLoading[uri] return Object.assign({}, state, { - downloading: newDownloading, - byUri: newByUri, - loading: newLoading, + urisDownloading: newDownloading, + urisLoading: newLoading, + fileInfos: newFileInfos, }) } reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) { const { uri, + outpoint, fileInfo, } = action.data - const newByUri = Object.assign({}, state.byUri) - const newDownloading = Object.assign({}, state.downloading) - newByUri[uri] = fileInfo + const newFileInfos = Object.assign({}, state.fileInfos) + const newDownloading = Object.assign({}, state.urisDownloading) + + newFileInfos[outpoint] = fileInfo newDownloading[uri] = true return Object.assign({}, state, { - byUri: newByUri, - downloading: newDownloading + fileInfos: newFileInfos, + urisDownloading: newDownloading }) } reducers[types.DOWNLOADING_COMPLETED] = function(state, action) { const { uri, + outpoint, fileInfo, } = action.data - const newByUri = Object.assign({}, state.byUri) - const newDownloading = Object.assign({}, state.downloading) - const newDownloadingByUri = Object.assign({}, newDownloading.byUri) - newByUri[uri] = fileInfo - delete newDownloadingByUri[uri] - newDownloading.byUri = newDownloadingByUri + const newFileInfos = Object.assign({}, state.fileInfos) + const newDownloading = Object.assign({}, state.urisDownloading) + + newFileInfos[outpoint] = fileInfo + delete newDownloading[uri] return Object.assign({}, state, { - byUri: newByUri, - downloading: newDownloading, + fileInfos: newFileInfos, + urisDownloading: newDownloading, }) } -reducers[types.DELETE_FILE_STARTED] = function(state, action) { +reducers[types.FILE_DELETE] = function(state, action) { const { - uri, + outpoint, } = action.data - const newDeleting = Object.assign({}, state.deleting) - const newByUri = Object.assign({}, newDeleting.byUri) - newByUri[uri] = true - newDeleting.byUri = newByUri + const newFileInfos = Object.assign({}, state.fileInfos) + + delete newFileInfos[outpoint] return Object.assign({}, state, { - deleting: newDeleting, - }) -} - -reducers[types.DELETE_FILE_COMPLETED] = function(state, action) { - const { - uri, - } = action.data - const newDeleting = Object.assign({}, state.deleting) - const newDeletingByUri = Object.assign({}, newDeleting.byUri) - const newByUri = Object.assign({}, state.byUri) - - delete newDeletingByUri[uri] - newDeleting.byUri = newDeletingByUri - delete newByUri[uri] - - return Object.assign({}, state, { - deleting: newDeleting, - byUri: newByUri, + fileInfos: newFileInfos, }) } @@ -132,14 +136,13 @@ reducers[types.LOADING_VIDEO_STARTED] = function(state, action) { const { uri, } = action.data - const newLoading = Object.assign({}, state.loading) - const newByUri = Object.assign({}, newLoading.byUri) - newByUri[uri] = true - newLoading.byUri = newByUri + const newLoading = Object.assign({}, state.urisLoading) + + newLoading[uri] = true return Object.assign({}, state, { - loading: newLoading, + urisLoading: newLoading, }) } @@ -147,54 +150,13 @@ reducers[types.LOADING_VIDEO_FAILED] = function(state, action) { const { uri, } = action.data - const newLoading = Object.assign({}, state.loading) - const newByUri = Object.assign({}, newLoading.byUri) - delete newByUri[uri] - newLoading.byUri = newByUri + const newLoading = Object.assign({}, state.urisLoading) + + delete newLoading[uri] return Object.assign({}, state, { - loading: newLoading, - }) -} - -reducers[types.FETCH_DOWNLOADED_CONTENT_COMPLETED] = function(state, action) { - const { - fileInfos, - } = action.data - const newByUri = Object.assign({}, state.byUri) - - fileInfos.forEach(fileInfo => { - const uri = lbryuri.build({ - channelName: fileInfo.channel_name, - contentName: fileInfo.name, - }) - - newByUri[uri] = fileInfo - }) - - return Object.assign({}, state, { - byUri: newByUri - }) -} - -reducers[types.FETCH_PUBLISHED_CONTENT_COMPLETED] = function(state, action) { - const { - fileInfos - } = action.data - const newByUri = Object.assign({}, state.byUri) - - fileInfos.forEach(fileInfo => { - const uri = lbryuri.build({ - channelName: fileInfo.channel_name, - contentName: fileInfo.name, - }) - - newByUri[uri] = fileInfo - }) - - return Object.assign({}, state, { - byUri: newByUri + urisLoading: newLoading, }) } diff --git a/ui/js/reducers/certificates.js b/ui/js/reducers/settings.js similarity index 50% rename from ui/js/reducers/certificates.js rename to ui/js/reducers/settings.js index ebeaed986..68c684f7e 100644 --- a/ui/js/reducers/certificates.js +++ b/ui/js/reducers/settings.js @@ -1,25 +1,14 @@ import * as types from 'constants/action_types' const reducers = {} -const defaultState = { -} +const defaultState = {} -reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { - const { - uri, - certificate, - } = action.data - if (!certificate) return state - - const newByUri = Object.assign({}, state.byUri) - - newByUri[uri] = certificate +reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) { return Object.assign({}, state, { - byUri: newByUri, + daemonSettings: action.data.settings }) } - export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 7692adae7..f0af3b5a1 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -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( selectCurrentPage, - selectCurrentUri, - (page, uri) => { + selectCurrentParams, + (page, params) => { switch (page) { case 'search': return 'Search' @@ -67,7 +50,7 @@ export const selectPageTitle = createSelector( case 'rewards': return page.charAt(0).toUpperCase() + page.slice(1) case 'show': - return lbryuri.normalize(uri) + return lbryuri.normalize(params.uri) case 'downloaded': return 'Downloads & Purchases' case 'published': @@ -195,11 +178,6 @@ export const selectUpgradeDownloadItem = createSelector( (state) => state.downloadItem ) -export const selectSearchTerm = createSelector( - _selectState, - (state) => state.searchTerm -) - export const selectError = createSelector( _selectState, (state) => state.error @@ -213,14 +191,4 @@ export const selectDaemonReady = createSelector( export const selectObscureNsfw = createSelector( _selectState, (state) => !!state.obscureNsfw -) - -export const selectHidePrice = createSelector( - _selectState, - (state) => !!state.hidePrice -) - -export const selectHasSignature = createSelector( - _selectState, - (state) => !!state.hasSignature -) +) \ No newline at end of file diff --git a/ui/js/selectors/availability.js b/ui/js/selectors/availability.js index 0eaebc318..8b65c5ec6 100644 --- a/ui/js/selectors/availability.js +++ b/ui/js/selectors/availability.js @@ -4,7 +4,6 @@ import { import { selectDaemonReady, selectCurrentPage, - selectCurrentUri, } from 'selectors/app' const _selectState = state => state.availability @@ -14,29 +13,24 @@ export const selectAvailabilityByUri = createSelector( (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( _selectState, (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) => { - return selectFetchingAvailabilityByUri(state)[props.uri] + return selectFetchingAvailability(state)[props.uri] } export const makeSelectFetchingAvailabilityForUri = () => { @@ -44,16 +38,4 @@ export const makeSelectFetchingAvailabilityForUri = () => { selectFetchingAvailabilityForUri, (fetching) => fetching ) -} - -export const selectFetchingAvailabilityForCurrentUri = createSelector( - selectCurrentUri, - selectFetchingAvailabilityByUri, - (uri, byUri) => byUri[uri] -) - -export const selectAvailabilityForCurrentUri = createSelector( - selectCurrentUri, - selectAvailabilityByUri, - (uri, byUri) => byUri[uri] -) \ No newline at end of file +} \ No newline at end of file diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index a755eea18..9a76b872e 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -2,28 +2,17 @@ import { createSelector, } from 'reselect' import lbryuri from 'lbryuri' -import { - selectCurrentUri, -} from 'selectors/app' export const _selectState = state => state.claims || {} export const selectClaimsByUri = createSelector( _selectState, - (state) => state.byUri || {} + (state) => state.claimsByUri || {} ) -export const selectCurrentUriClaim = createSelector( - selectCurrentUri, - selectClaimsByUri, - (uri, byUri) => byUri[uri] -) - -export const selectCurrentUriClaimOutpoint = createSelector( - selectCurrentUriClaim, - (claim) => { - return claim ? `${claim.txid}:${claim.nout}` : null - } +export const selectAllClaimsByChannel = createSelector( + _selectState, + (state) => state.claimsByChannel || {} ) 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 claim = selectClaimForUri(state, props) 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 = () => { @@ -56,7 +56,7 @@ const selectSourceForUri = (state, props) => { const claim = selectClaimForUri(state, props) 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 = () => { @@ -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, - (state) => state.mine || {} + (state) => state.isClaimListMinePending ) -export const selectMyClaimsById = createSelector( - selectMyClaims, - (mine) => mine.byId || {} +export const selectMyClaims = createSelector( + _selectState, + (state) => state.myClaims || {} ) export const selectMyClaimsOutpoints = createSelector( - selectMyClaimsById, - (byId) => { - const outpoints = [] - Object.keys(byId).forEach(key => { - const claim = byId[key] - const outpoint = `${claim.txid}:${claim.nout}` - outpoints.push(outpoint) - }) + selectMyClaims, + (claims) => { + if (!claims) { + return [] + } - return outpoints + return Object.values(claims).map((claim) => { + return `${claim.txid}:${claim.nout}` + }) } ) diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js index 6ee54bf94..61efa2e1f 100644 --- a/ui/js/selectors/content.js +++ b/ui/js/selectors/content.js @@ -2,7 +2,6 @@ import { createSelector } from 'reselect' import { selectDaemonReady, selectCurrentPage, - selectCurrentUri, } from 'selectors/app' export const _selectState = state => state.content || {} @@ -17,56 +16,18 @@ export const selectFetchingFeaturedUris = createSelector( (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( _selectState, (state) => state.resolvingUris || [] ) - -export const selectCurrentUriIsResolving = createSelector( - selectCurrentUri, - selectResolvingUris, - (uri, resolvingUris) => resolvingUris.indexOf(uri) != -1 -) - const selectResolvingUri = (state, props) => { return selectResolvingUris(state).indexOf(props.uri) != -1 } -export const makeSelectResolvingUri = () => { +export const makeSelectIsResolvingForUri = () => { return createSelector( selectResolvingUri, (resolving) => resolving ) -} +} \ No newline at end of file diff --git a/ui/js/selectors/cost_info.js b/ui/js/selectors/cost_info.js index f48fa3438..242ec6b0d 100644 --- a/ui/js/selectors/cost_info.js +++ b/ui/js/selectors/cost_info.js @@ -1,8 +1,4 @@ import { createSelector } from 'reselect' -import { - selectCurrentUri, - selectCurrentPage, -} from 'selectors/app' export const _selectState = state => state.costInfo || {} @@ -11,24 +7,7 @@ export const selectAllCostInfoByUri = createSelector( (state) => state.byUri || {} ) -export const selectCurrentUriCostInfo = createSelector( - 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) => { +export const selectCostInfoForUri = (state, props) => { return selectAllCostInfoByUri(state)[props.uri] } diff --git a/ui/js/selectors/file_info.js b/ui/js/selectors/file_info.js index b41d5ae49..cf7895d56 100644 --- a/ui/js/selectors/file_info.js +++ b/ui/js/selectors/file_info.js @@ -1,72 +1,38 @@ +import lbry from 'lbry' import { createSelector, } from 'reselect' import { - selectCurrentUri, - selectCurrentPage, -} from 'selectors/app' -import { + selectClaimsByUri, + selectClaimListMineIsPending, selectMyClaimsOutpoints, } from 'selectors/claims' export const _selectState = state => state.fileInfo || {} -export const selectAllFileInfoByUri = createSelector( +export const selectAllFileInfos = createSelector( _selectState, - (state) => state.byUri || {} + (state) => state.fileInfos || {} ) -export const selectCurrentUriRawFileInfo = createSelector( - selectCurrentUri, - selectAllFileInfoByUri, - (uri, byUri) => byUri[uri] -) - -export const selectCurrentUriFileInfo = createSelector( - selectCurrentUriRawFileInfo, - (fileInfo) => fileInfo -) - -export const selectFetchingFileInfo = createSelector( +export const selectFileListIsPending = createSelector( _selectState, - (state) => state.fetching || {} + (state) => state.isFileListPending ) -export const selectFetchingCurrentUriFileInfo = createSelector( - selectCurrentUri, - selectFetchingFileInfo, - (uri, byUri) => !!byUri[uri] +export const selectFileListDownloadedOrPublishedIsPending = createSelector( + selectFileListIsPending, + selectClaimListMineIsPending, + (isFileListPending, isClaimListMinePending) => isFileListPending || isClaimListMinePending ) -export const selectDownloading = createSelector( - _selectState, - (state) => state.downloading || {} -) +export const selectFileInfoForUri = (state, props) => { + const claims = selectClaimsByUri(state), + claim = claims[props.uri], + fileInfos = selectAllFileInfos(state), + outpoint = claim ? `${claim.txid}:${claim.nout}` : undefined -export const selectDownloadingByUri = createSelector( - 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] + return outpoint && fileInfos ? fileInfos[outpoint] : undefined } export const makeSelectFileInfoForUri = () => { @@ -76,8 +42,13 @@ export const makeSelectFileInfoForUri = () => { ) } +export const selectUrisDownloading = createSelector( + _selectState, + (state) => state.urisDownloading || {} +) + const selectDownloadingForUri = (state, props) => { - const byUri = selectDownloadingByUri(state) + const byUri = selectUrisDownloading(state) return byUri[props.uri] } @@ -88,31 +59,13 @@ export const makeSelectDownloadingForUri = () => { ) } -export const selectLoading = createSelector( +export const selectUrisLoading = createSelector( _selectState, - (state) => state.loading || {} -) - -export const selectLoadingByUri = createSelector( - selectLoading, - (loading) => loading.byUri || {} -) - -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 + (state) => state.urisLoading || {} ) const selectLoadingForUri = (state, props) => { - const byUri = selectLoadingByUri(state) + const byUri = selectUrisLoading(state) return byUri[props.uri] } @@ -123,36 +76,38 @@ export const makeSelectLoadingForUri = () => { ) } -export const selectDownloadedFileInfo = createSelector( - selectAllFileInfoByUri, - (byUri) => { +export const selectFileInfosDownloaded = createSelector( + selectAllFileInfos, + selectMyClaimsOutpoints, + (fileInfos, myClaimOutpoints) => { const fileInfoList = [] - Object.keys(byUri).forEach(key => { - const fileInfo = byUri[key] - - if (fileInfo.completed || fileInfo.written_bytes) { + Object.values(fileInfos).forEach(fileInfo => { + if (fileInfo && myClaimOutpoints.indexOf(fileInfo.outpoint) === -1 && (fileInfo.completed || fileInfo.written_bytes)) { fileInfoList.push(fileInfo) } }) - return fileInfoList } ) -export const selectPublishedFileInfo = createSelector( - selectAllFileInfoByUri, - selectMyClaimsOutpoints, - (byUri, outpoints) => { - const fileInfos = [] - outpoints.forEach(outpoint => { - Object.keys(byUri).forEach(key => { - const fileInfo = byUri[key] - if (fileInfo.outpoint == outpoint) { - fileInfos.push(fileInfo) - } - }) - }) - - return fileInfos +export const selectFileInfosPendingPublish = createSelector( + _selectState, + (state) => { + return lbry.getPendingPublishes() + } +) + +export const selectFileInfosPublished = createSelector( + selectAllFileInfos, + selectFileInfosPendingPublish, + selectMyClaimsOutpoints, + (allFileInfos, pendingFileInfos, outpoints) => { + const fileInfos = [] + outpoints.forEach(outpoint => { + if (allFileInfos[outpoint]) { + fileInfos.push(allFileInfos[outpoint]) + } + }) + return [...fileInfos, ...pendingFileInfos] } ) diff --git a/ui/js/selectors/search.js b/ui/js/selectors/search.js index 12dec7658..6bfa70b7d 100644 --- a/ui/js/selectors/search.js +++ b/ui/js/selectors/search.js @@ -2,7 +2,6 @@ import { createSelector } from 'reselect' import { selectPageTitle, selectCurrentPage, - selectCurrentUri } from 'selectors/app' export const _selectState = state => state.search || {} @@ -43,8 +42,7 @@ export const selectWunderBarAddress = createSelector( export const selectWunderBarIcon = createSelector( selectCurrentPage, - selectCurrentUri, - (page, uri) => { + (page) => { switch (page) { case 'search': return 'icon-search' diff --git a/ui/js/selectors/settings.js b/ui/js/selectors/settings.js new file mode 100644 index 000000000..850259043 --- /dev/null +++ b/ui/js/selectors/settings.js @@ -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 +) \ No newline at end of file diff --git a/ui/js/store.js b/ui/js/store.js index 462c6a5d0..479fdb49d 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -7,13 +7,13 @@ import { } from 'redux-logger' import appReducer from 'reducers/app'; import availabilityReducer from 'reducers/availability' -import certificatesReducer from 'reducers/certificates' import claimsReducer from 'reducers/claims' import contentReducer from 'reducers/content'; import costInfoReducer from 'reducers/cost_info' import fileInfoReducer from 'reducers/file_info' import rewardsReducer from 'reducers/rewards' import searchReducer from 'reducers/search' +import settingsReducer from 'reducers/settings' import walletReducer from 'reducers/wallet' function isFunction(object) { @@ -49,13 +49,13 @@ function enableBatching(reducer) { const reducers = redux.combineReducers({ app: appReducer, availability: availabilityReducer, - certificates: certificatesReducer, claims: claimsReducer, fileInfo: fileInfoReducer, content: contentReducer, costInfo: costInfoReducer, rewards: rewardsReducer, search: searchReducer, + settings: settingsReducer, wallet: walletReducer, }); diff --git a/ui/package.json b/ui/package.json index e73d866f6..72d7bdde1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -22,7 +22,6 @@ "babel-cli": "^6.11.4", "babel-preset-es2015": "^6.13.2", "babel-preset-react": "^6.11.1", - "mediaelement": "^2.23.4", "node-sass": "^3.8.0", "plyr": "^2.0.12", "rc-progress": "^2.0.6", diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 3d85f4b22..f4ededba0 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -143,6 +143,14 @@ p font-style: italic; } +/*should be redone/moved*/ +.file-list__header { + .busy-indicator { + float: left; + margin-top: 12px; + } +} + .sort-section { display: block; margin-bottom: $spacing-vertical * 2/3; diff --git a/ui/scss/_mediaelement.scss b/ui/scss/_mediaelement.scss deleted file mode 100644 index 44488808f..000000000 --- a/ui/scss/_mediaelement.scss +++ /dev/null @@ -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%; - } -} \ No newline at end of file diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 14833dfd3..6b421196a 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -1,6 +1,5 @@ @import "_reset"; @import "_icons"; -@import "_mediaelement"; @import "_gui"; @import "component/_table"; @import "component/_button.scss"; @@ -20,6 +19,5 @@ @import "component/_snack-bar.scss"; @import "component/_video.scss"; @import "page/_developer.scss"; -@import "page/_watch.scss"; @import "page/_reward.scss"; @import "page/_show.scss"; diff --git a/ui/scss/component/_video.scss b/ui/scss/component/_video.scss index 9dd95ebe9..739dc1b26 100644 --- a/ui/scss/component/_video.scss +++ b/ui/scss/component/_video.scss @@ -3,6 +3,9 @@ video { box-sizing: border-box; max-height: 100%; max-width: 100%; + background-size: contain; + background-position: center center; + background-repeat: no-repeat; } .video { @@ -15,6 +18,7 @@ video { max-width: $width-page-constrained; max-height: $height-video-embedded; height: $height-video-embedded; + position: relative; /*for .plyr below*/ video { height: 100%; } @@ -24,6 +28,10 @@ video { &.video--active { /*background: none;*/ } + .plyr { + top: 50%; + transform: translateY(-50%); + } } .video__cover { diff --git a/ui/scss/page/_watch.scss b/ui/scss/page/_watch.scss deleted file mode 100644 index 6ed5459ae..000000000 --- a/ui/scss/page/_watch.scss +++ /dev/null @@ -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; -}