diff --git a/CHANGELOG.md b/CHANGELOG.md index 49434d180..65aecac57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,8 @@ Web UI version numbers should always match the corresponding version of LBRY App * Enable windows code signing of binary ### Changed - * - * + * Use directory selector instead of ugly text box on Settings page + * Use Electron's native file selector everywhere instead of WebKit file selector ### Fixed * Error modals now display full screen properly diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index c35042a90..8ede77fee 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -43,7 +43,6 @@ export function doChangePath(path) { path, } }) - } } @@ -58,6 +57,7 @@ export function doHistoryPush(params, title, relativeUrl) { let pathParts = window.location.pathname.split('/') pathParts[pathParts.length - 1] = relativeUrl.replace(/^\//, '') const url = pathParts.join('/') + title += " - LBRY" history.pushState(params, title, url) window.document.title = title } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index a565b6e22..40ff1b048 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -8,7 +8,7 @@ import { } from 'selectors/wallet' import { selectFileInfoForUri, - selectDownloadingByUri, + selectUrisDownloading, } from 'selectors/file_info' import { selectResolvingUris @@ -57,66 +57,9 @@ export function doResolveUri(uri) { export function doCancelResolveUri(uri) { return function(dispatch, getState) { lbry.cancelResolve({ uri }) - } -} - -export function doFetchDownloadedContent() { - return function(dispatch, getState) { - const state = getState() - dispatch({ - type: types.FETCH_DOWNLOADED_CONTENT_STARTED, - }) - - lbry.claim_list_mine().then((myClaimInfos) => { - lbry.file_list().then((fileInfos) => { - const myClaimOutpoints = myClaimInfos.map(({txid, nout}) => txid + ':' + nout); - - fileInfos.forEach(fileInfo => { - const uri = lbryuri.build({ - channelName: fileInfo.channel_name, - contentName: fileInfo.name, - }) - const claim = selectClaimsByUri(state)[uri] - if (!claim) dispatch(doResolveUri(uri)) - }) - - dispatch({ - type: types.FETCH_DOWNLOADED_CONTENT_COMPLETED, - data: { - fileInfos: fileInfos.filter(({outpoint}) => !myClaimOutpoints.includes(outpoint)), - } - }) - }); - }); - } -} - -export function doFetchPublishedContent() { - return function(dispatch, getState) { - const state = getState() - - dispatch({ - type: types.FETCH_PUBLISHED_CONTENT_STARTED, - }) - - lbry.claim_list_mine().then((claimInfos) => { - dispatch({ - type: types.FETCH_MY_CLAIMS_COMPLETED, - data: { - claims: claimInfos, - } - }) - lbry.file_list().then((fileInfos) => { - const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout) - - dispatch({ - type: types.FETCH_PUBLISHED_CONTENT_COMPLETED, - data: { - fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)), - } - }) - }) + type: types.RESOLVE_URI_CANCELED, + data: { uri } }) } } @@ -138,6 +81,14 @@ export function doFetchFeaturedUris() { featuredUris[category] = Uris[category] } }) + // + // dispatch({ + // type: types.FETCH_FEATURED_CONTENT_COMPLETED, + // data: { + // categories: ["FOO"], + // uris: { FOO: ["lbry://gtasoc"]}, + // } + // }) dispatch({ type: types.FETCH_FEATURED_CONTENT_COMPLETED, @@ -182,6 +133,7 @@ export function doUpdateLoadStatus(uri, outpoint) { type: types.DOWNLOADING_COMPLETED, data: { uri, + outpoint, fileInfo, } }) @@ -197,6 +149,7 @@ export function doUpdateLoadStatus(uri, outpoint) { type: types.DOWNLOADING_PROGRESSED, data: { uri, + outpoint, fileInfo, progress, } @@ -216,6 +169,7 @@ export function doDownloadFile(uri, streamInfo) { type: types.DOWNLOADING_STARTED, data: { uri, + outpoint: streamInfo.outpoint, fileInfo, } }) @@ -265,7 +219,7 @@ export function doPurchaseUri(uri) { const balance = selectBalance(state) const fileInfo = selectFileInfoForUri(state, { uri }) const costInfo = selectCostInfoForUri(state, { uri }) - const downloadingByUri = selectDownloadingByUri(state) + const downloadingByUri = selectUrisDownloading(state) const alreadyDownloading = !!downloadingByUri[uri] const { cost } = costInfo @@ -314,18 +268,28 @@ export function doFetchClaimsByChannel(uri) { } = resolutionInfo ? resolutionInfo : { claims_in_channel: [] } dispatch({ - type: types.FETCH_CHANNEL_CLAIMS_STARTED, + type: types.FETCH_CHANNEL_CLAIMS_COMPLETED, data: { uri, claims: claims_in_channel } }) - }).catch(() => { + }) + } +} + +export function doClaimListMine() { + return function(dispatch, getState) { + dispatch({ + type: types.CLAIM_LIST_MINE_STARTED + }) + + + lbry.claim_list_mine().then((claims) => { dispatch({ - type: types.FETC, + type: types.CLAIM_LIST_MINE_COMPLETED, data: { - uri, - claims: [] + claims } }) }) diff --git a/ui/js/actions/cost_info.js b/ui/js/actions/cost_info.js index b0f6af6d7..79ff4bdd5 100644 --- a/ui/js/actions/cost_info.js +++ b/ui/js/actions/cost_info.js @@ -1,6 +1,12 @@ import * as types from 'constants/action_types' import lbry from 'lbry' import lbryio from 'lbryio' +import { + doResolveUri +} from 'actions/content' +import { + selectResolvingUris, +} from 'selectors/content' import { selectClaimsByUri } from 'selectors/claims' @@ -12,21 +18,23 @@ export function doFetchCostInfoForUri(uri) { return function(dispatch, getState) { const state = getState(), claim = selectClaimsByUri(state)[uri], + isResolving = selectResolvingUris(state).indexOf(uri) !== -1, isGenerous = selectSettingsIsGenerous(state) - // - // function getCostGenerous(uri) { - // console.log('get cost generous: ' + uri) - // // If generous is on, the calculation is simple enough that we might as well do it here in the front end - // lbry.resolve({uri: uri}).then((resolutionInfo) => { - // console.log('resolve inside getCostGenerous ' + uri) - // console.log(resolutionInfo) - // if (!resolutionInfo) { - // return reject(new Error("Unused URI")); - // } + if (claim === null) { //claim doesn't exist, nothing to fetch a cost for + return + } + + if (!claim) { + setTimeout(() => { + dispatch(doFetchCostInfoForUri(uri)) + }, 1000) + if (!isResolving) { + dispatch(doResolveUri(uri)) + } + return + } - // }); - // } function begin() { dispatch({ diff --git a/ui/js/actions/file_info.js b/ui/js/actions/file_info.js index f7d2b08f3..23ba50e71 100644 --- a/ui/js/actions/file_info.js +++ b/ui/js/actions/file_info.js @@ -1,10 +1,16 @@ import * as types from 'constants/action_types' import lbry from 'lbry' +import { + doClaimListMine +} from 'actions/content' import { selectClaimsByUri, + selectClaimListMineIsPending, } from 'selectors/claims' import { - selectLoadingByUri, + selectFileListIsPending, + selectAllFileInfos, + selectUrisLoading, } from 'selectors/file_info' import { doCloseModal, @@ -19,23 +25,45 @@ export function doFetchFileInfo(uri) { const state = getState() const claim = selectClaimsByUri(state)[uri] const outpoint = claim ? `${claim.txid}:${claim.nout}` : null - const alreadyFetching = !!selectLoadingByUri(state)[uri] + const alreadyFetching = !!selectUrisLoading(state)[uri] if (!alreadyFetching) { dispatch({ type: types.FETCH_FILE_INFO_STARTED, data: { - uri, outpoint, } }) - lbry.file_list({outpoint: outpoint, full_status: true}).then(([fileInfo]) => { + lbry.file_list({outpoint: outpoint, full_status: true}).then(fileInfos => { + dispatch({ type: types.FETCH_FILE_INFO_COMPLETED, data: { - uri, - fileInfo, + outpoint, + fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null, + } + }) + }) + } + } +} + +export function doFileList() { + return function(dispatch, getState) { + const state = getState() + const isPending = selectFileListIsPending(state) + + if (!isPending) { + dispatch({ + type: types.FILE_LIST_STARTED, + }) + + lbry.file_list().then((fileInfos) => { + dispatch({ + type: types.FILE_LIST_COMPLETED, + data: { + fileInfos, } }) }) @@ -55,51 +83,39 @@ export function doOpenFileInFolder(fileInfo) { } } -export function doDeleteFile(uri, fileInfo, deleteFromComputer) { +export function doDeleteFile(outpoint, deleteFromComputer) { return function(dispatch, getState) { + dispatch({ - type: types.DELETE_FILE_STARTED, + type: types.FILE_DELETE, data: { - uri, - fileInfo, - deleteFromComputer, + outpoint } }) - const successCallback = () => { - dispatch({ - type: types.DELETE_FILE_COMPLETED, - data: { - uri, - } - }) - 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/component/auth.js b/ui/js/component/auth.js index c6ad8ae19..0ff99c26f 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -217,7 +217,7 @@ class CodeRequiredStage extends React.Component { }) if (!this.state.address) { - lbry.getUnusedAddress((address) => { + lbry.wallet_unused_address().then((address) => { setLocal('wallet_address', address); this.setState({ address: address }); }); diff --git a/ui/js/component/file-selector.js b/ui/js/component/file-selector.js new file mode 100644 index 000000000..4670f4830 --- /dev/null +++ b/ui/js/component/file-selector.js @@ -0,0 +1,58 @@ +import React from 'react'; + +const {remote} = require('electron'); +class FileSelector extends React.Component { + static propTypes = { + type: React.PropTypes.oneOf(['file', 'directory']), + initPath: React.PropTypes.string, + onFileChosen: React.PropTypes.func, + } + + static defaultProps = { + type: 'file', + } + + componentWillMount() { + this.setState({ + path: this.props.initPath || null, + }); + } + + handleButtonClick() { + remote.dialog.showOpenDialog({ + properties: [this.props.type == 'file' ? 'openFile' : 'openDirectory'], + }, (paths) => { + if (!paths) { // User hit cancel, so do nothing + return; + } + + const path = paths[0]; + this.setState({ + path: path, + }); + if (this.props.onFileChosen) { + this.props.onFileChosen(path); + } + }); + } + + render() { + return ( +
+ + {' '} + + {this.state.path ? + this.state.path : + 'No File Chosen'} + +
+ ); + } +}; + +export default FileSelector; diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index c3628821d..a570cd413 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -19,6 +19,7 @@ import { import { doCloseModal, doOpenModal, + doHistoryBack, } from 'actions/app' import { doFetchAvailability @@ -55,7 +56,10 @@ const perform = (dispatch) => ({ closeModal: () => dispatch(doCloseModal()), openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)), openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)), - deleteFile: (fileInfo, deleteFromComputer) => dispatch(doDeleteFile(fileInfo, deleteFromComputer)), + deleteFile: (fileInfo, deleteFromComputer) => { + dispatch(doHistoryBack()) + dispatch(doDeleteFile(fileInfo, deleteFromComputer)) + }, openModal: (modal) => dispatch(doOpenModal(modal)), startDownload: (uri) => dispatch(doPurchaseUri(uri)), loadVideo: (uri) => dispatch(doLoadVideo(uri)) diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 64e0ac219..44eb4ade3 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -72,10 +72,19 @@ class FileActions extends React.Component { let content - console.log('file actions render') - console.log(this.props) + if (downloading) { - if (!fileInfo && isAvailable === undefined) { + const + progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, + label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', + labelWithIcon = {label}; + + content =
+
{labelWithIcon}
+ {labelWithIcon} +
+ + } else if (!fileInfo && isAvailable === undefined) { content = @@ -93,18 +102,6 @@ class FileActions extends React.Component { content = { startDownload(uri) } } />; - } else if (downloading) { - - const - progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0, - label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', - labelWithIcon = {label}; - - content =
-
{labelWithIcon}
- {labelWithIcon} -
- } else if (fileInfo && fileInfo.download_path) { content = openInShell(fileInfo)} />; } else { @@ -136,7 +133,7 @@ class FileActions extends React.Component { contentLabel="Not enough credits" type="confirm" confirmButtonLabel="Remove" - onConfirmed={() => deleteFile(uri, fileInfo, deleteChecked)} + onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)} onAborted={closeModal}>

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

diff --git a/ui/js/component/fileCard/index.js b/ui/js/component/fileCard/index.js index e3e11057e..89ebc5040 100644 --- a/ui/js/component/fileCard/index.js +++ b/ui/js/component/fileCard/index.js @@ -34,7 +34,6 @@ const makeSelect = () => { claim: selectClaimForUri(state, props), fileInfo: selectFileInfoForUri(state, props), obscureNsfw: selectObscureNsfw(state), - hasSignature: false, metadata: selectMetadataForUri(state, props), isResolvingUri: selectResolvingUri(state, props), }) diff --git a/ui/js/component/fileCard/view.jsx b/ui/js/component/fileCard/view.jsx index 1b721a703..918476a53 100644 --- a/ui/js/component/fileCard/view.jsx +++ b/ui/js/component/fileCard/view.jsx @@ -2,12 +2,12 @@ import React from 'react'; import lbry from 'lbry.js'; import lbryuri from 'lbryuri.js'; import Link from 'component/link'; -import {Thumbnail, TruncatedText,} from 'component/common'; +import {Thumbnail, TruncatedText, Icon} from 'component/common'; import FilePrice from 'component/filePrice' import UriIndicator from 'component/uriIndicator'; class FileCard extends React.Component { - componentDidMount() { + componentWillMount() { this.resolve(this.props) } @@ -29,7 +29,15 @@ class FileCard extends React.Component { } componentWillUnmount() { - this.props.cancelResolveUri(this.props.uri) + const { + isResolvingUri, + cancelResolveUri, + uri + } = this.props + + if (isResolvingUri) { + cancelResolveUri(uri) + } } handleMouseOver() { @@ -47,6 +55,8 @@ class FileCard extends React.Component { render() { const { + claim, + fileInfo, metadata, isResolvingUri, navigate, @@ -61,6 +71,8 @@ class FileCard extends React.Component { description = "Loading..." } else if (metadata && metadata.description) { description = metadata.description + } else if (claim === null) { + description = 'This address contains no content.' } return ( @@ -70,7 +82,10 @@ class FileCard extends React.Component {
{title}
- { !isResolvingUri && } + + + { fileInfo ? {' '} : '' } +
diff --git a/ui/js/component/fileList/view.jsx b/ui/js/component/fileList/view.jsx index 6130a5ffb..eb3b620b8 100644 --- a/ui/js/component/fileList/view.jsx +++ b/ui/js/component/fileList/view.jsx @@ -60,7 +60,6 @@ class FileList extends React.Component { handleSortChanged, fetching, fileInfos, - hidePrices, } = this.props const { sortBy, @@ -72,7 +71,7 @@ class FileList extends React.Component { contentName: fileInfo.name, channelName: fileInfo.channel_name, }) - content.push() + content.push() }) return (
diff --git a/ui/js/component/filePrice/index.js b/ui/js/component/filePrice/index.js index 4c8bf9b7a..726ceb3ee 100644 --- a/ui/js/component/filePrice/index.js +++ b/ui/js/component/filePrice/index.js @@ -12,6 +12,7 @@ import FilePrice from './view' const makeSelect = () => { const selectCostInfoForUri = makeSelectCostInfoForUri() + const select = (state, props) => ({ costInfo: selectCostInfoForUri(state, props), }) diff --git a/ui/js/component/filePrice/view.jsx b/ui/js/component/filePrice/view.jsx index 56174c8a2..4edc3a3e7 100644 --- a/ui/js/component/filePrice/view.jsx +++ b/ui/js/component/filePrice/view.jsx @@ -4,7 +4,7 @@ import { } from 'component/common' class FilePrice extends React.Component{ - componentDidMount() { + componentWillMount() { this.fetchCost(this.props) } diff --git a/ui/js/component/fileTile/view.jsx b/ui/js/component/fileTile/view.jsx index c015c6327..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, diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 64946c31f..f73d1cf4c 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -1,7 +1,9 @@ import React from 'react'; +import FileSelector from './file-selector.js'; import {Icon} from './common.js'; var formFieldCounter = 0, + formFieldFileSelectorTypes = ['file', 'directory'], formFieldNestedLabelTypes = ['radio', 'checkbox']; function formFieldId() { @@ -36,12 +38,34 @@ export class FormField extends React.Component { } else if (this.props.type == 'text-number') { this._element = 'input'; this._type = 'text'; + } else if (formFieldFileSelectorTypes.includes(this.props.type)) { + this._element = 'input'; + this._type = 'hidden'; } else { // Non field, e.g.