From efa1a29d4f1f914b755049ee26424c05a0dd810f Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 23 Dec 2016 01:57:01 -0500 Subject: [PATCH 01/93] Rename SearchResultRow to FileTile and convert styles to CSS Also moves the component to its own file (file-tile.js) so it can be used outside the Discover page. --- js/component/file-tile.js | 87 +++++++++++++++++++++++++++++++++++++++ js/page/discover.js | 7 ++-- scss/_gui.scss | 36 ++++++++++++++++ 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 js/component/file-tile.js diff --git a/js/component/file-tile.js b/js/component/file-tile.js new file mode 100644 index 000000000..8778d3755 --- /dev/null +++ b/js/component/file-tile.js @@ -0,0 +1,87 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import {Link, DownloadLink, WatchLink} from '../component/link.js'; +import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; + +let FileTile = React.createClass({ + getInitialState: function() { + return { + downloading: false, + isHovered: false, + cost: null, + costIncludesData: null, + } + }, + handleMouseOver: function() { + this.setState({ + isHovered: true, + }); + }, + handleMouseOut: function() { + this.setState({ + isHovered: false, + }); + }, + componentWillMount: function() { + if ('cost' in this.props) { + this.setState({ + cost: this.props.cost, + costIncludesData: this.props.costIncludesData, + }); + } else { + lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => { + this.setState({ + cost: cost, + costIncludesData: includesData, + }); + }); + } + }, + render: function() { + var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; + return ( +
+
+
+ +
+
+ {this.state.cost !== null + ? + + + : null} +
lbry://{this.props.name}
+

+ + + {this.props.title} + + +

+
+ {this.props.mediaType == 'video' ? : null} + +
+

+ + {this.props.description} + +

+
+
+ { + !obscureNsfw || !this.state.isHovered ? null : +
+

+ This content is Not Safe For Work. + To view adult content, please change your . +

+
+ } +
+ ); + } +}); + +export default FileTile; \ No newline at end of file diff --git a/js/page/discover.js b/js/page/discover.js index af9792236..9bb26827d 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -1,6 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; +import FileTile from '../component/file-tile.js'; import {Link, ToolTipLink, DownloadLink, WatchLink} from '../component/link.js'; import {Thumbnail, CreditAmount, TruncatedText, BusyMessage} from '../component/common.js'; @@ -222,9 +223,9 @@ var FeaturedContentItem = React.createClass({ } return (
- +
); } }); diff --git a/scss/_gui.scss b/scss/_gui.scss index 965d2eb9e..5b64a29f0 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -367,3 +367,39 @@ input[type="text"], input[type="search"] word-break: break-all; } + +.file-tile--compact { + height: 180px; +} + +.file-tile__row { + height: 24px * 7; + overflow-y: hidden; +} + +.file-tile__thumbnail { + max-width: 100%; + max-height: 24px * 7; + display: block; + margin-left: auto; + margin-right: auto; +} + +.file-tile__title { + font-weight: bold; +} + +.file-tile__title--compact { + font-size: 1.25em; + line-height: 1.15; +} + +.file-tile__cost { + float: right; +} + +.file-tile__description { + color: #444; + margin-top: 12px; + font-size: 0.9em; +} From 97f6f3bdf8a5dfac26bd0d1bed11bf2658e138a6 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 23 Dec 2016 02:02:05 -0500 Subject: [PATCH 02/93] Style and formatting fixes in FileTile --- js/component/file-tile.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 8778d3755..37154f9f1 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -38,7 +38,7 @@ let FileTile = React.createClass({ } }, render: function() { - var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; + let obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; return (
@@ -70,15 +70,14 @@ let FileTile = React.createClass({

- { - !obscureNsfw || !this.state.isHovered ? null : -
+ {obscureNsfw && this.state.isHovered + ?

This content is Not Safe For Work. To view adult content, please change your .

- } + : null}
); } From 64d7b680c35ed65af440b4cfdf8a7ba10fa912f8 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 25 Dec 2016 00:12:50 -0500 Subject: [PATCH 03/93] Add propTypes and getDefaultProps() to FileTile --- js/component/file-tile.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 37154f9f1..8feddfbce 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -4,6 +4,15 @@ import {Link, DownloadLink, WatchLink} from '../component/link.js'; import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; let FileTile = React.createClass({ + propTypes: { + name: React.PropTypes.string.isRequired, + mediaType: React.PropTypes.string.isRequired, + title: React.PropTypes.string.isRequired, + description: React.PropTypes.string, + compact: React.PropTypes.boolean, + cost: React.PropTypes.number, + costIncludesData: React.PropTypes.boolean, + }, getInitialState: function() { return { downloading: false, @@ -12,6 +21,11 @@ let FileTile = React.createClass({ costIncludesData: null, } }, + getDefaultProps: function() { + return { + compact: false, + } + }, handleMouseOver: function() { this.setState({ isHovered: true, From f424e2e439d767be8daa0ec0894c366c0c32b7c2 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 26 Dec 2016 00:59:15 -0500 Subject: [PATCH 04/93] Show Download link only if file is not published by user --- js/component/file-tile.js | 6 ++-- js/page/my_files.js | 73 ++------------------------------------- 2 files changed, 6 insertions(+), 73 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 8feddfbce..f86c7ac93 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -75,8 +75,10 @@ let FileTile = React.createClass({
{this.props.mediaType == 'video' ? : null} - -
+ {!this.props.isMine + ? + : null} +

{this.props.description} diff --git a/js/page/my_files.js b/js/page/my_files.js index 2d6b28e66..f2381555b 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -3,6 +3,7 @@ import lbry from '../lbry.js'; import {Link, WatchLink} from '../component/link.js'; import {Menu, MenuItem} from '../component/menu.js'; import FormField from '../component/form.js'; +import FileTile from '../component/file-tile.js'; import Modal from '../component/modal.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; @@ -93,77 +94,7 @@ var moreButtonColumnStyle = { marginLeft: 'auto', marginRight: 'auto', }; -var MyFilesRow = React.createClass({ - onPauseResumeClicked: function() { - if (this.props.stopped) { - lbry.startFile(this.props.lbryUri); - } else { - lbry.stopFile(this.props.lbryUri); - } - }, - render: function() { - //@TODO: Convert progress bar to reusable component - var progressBarWidth = 230; - - if (this.props.completed) { - var pauseLink = null; - var curProgressBarStyle = {display: 'none'}; - } else { - var pauseLink = { this.onPauseResumeClicked() }} />; - - var curProgressBarStyle = Object.assign({}, progressBarStyle); - curProgressBarStyle.width = Math.floor(this.props.ratioLoaded * progressBarWidth) + 'px'; - curProgressBarStyle.borderRightWidth = progressBarWidth - Math.ceil(this.props.ratioLoaded * progressBarWidth) + 2; - } - - if (this.props.showWatchButton) { - var watchButton = - } else { - var watchButton = null; - } - - return ( -

-
-
- -
-
-

{this.props.pending ? this.props.title : {this.props.title}}

- {this.props.pending ? This file is pending confirmation - : ( -
-
- { ' ' } - {this.props.completed - ? (this.props.isMine - ? 'Published' - : 'Download complete') - : (parseInt(this.props.ratioLoaded * 100) + '%')} -
{ pauseLink }
-
{ watchButton }
-
- ) - } -
-
- {this.props.pending ? null : -
- - -
- } -
-
-
- ); - } -}); var MyFilesPage = React.createClass({ _fileTimeout: null, @@ -354,7 +285,7 @@ var MyFilesPage = React.createClass({ var mediaType = lbry.getMediaType(metadata.content_type, file_name); var showWatchButton = (mediaType == 'video'); - content.push(); From 1b51b4e3291b09224d4f2cb2b5f9893e38100029 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 1 Jan 2017 23:29:11 -0500 Subject: [PATCH 05/93] Add support for displaying progress in DownloadLink --- js/component/link.js | 27 +++++++++++---------------- scss/_gui.scss | 31 ++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 99ce1e627..e8b01d9b3 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -83,22 +83,20 @@ export let DownloadLink = React.createClass({ propTypes: { type: React.PropTypes.string, streamName: React.PropTypes.string, + sdHash: React.PropTypes.string, label: React.PropTypes.string, - downloadingLabel: React.PropTypes.string, button: React.PropTypes.string, - style: React.PropTypes.object, hidden: React.PropTypes.bool, }, getDefaultProps: function() { return { icon: 'icon-download', label: 'Download', - downloadingLabel: 'Downloading...', + downloading: false, } }, getInitialState: function() { return { - downloading: false, filePath: null, modal: null, } @@ -109,23 +107,17 @@ export let DownloadLink = React.createClass({ }) }, handleClick: function() { - this.setState({ - downloading: true - }); - lbry.getCostInfoForName(this.props.streamName, ({cost}) => { lbry.getBalance((balance) => { if (cost > balance) { this.setState({ modal: 'notEnoughCredits', - downloading: false }); } else { lbry.getStream(this.props.streamName, (streamInfo) => { if (streamInfo === null || typeof streamInfo !== 'object') { this.setState({ modal: 'timedOut', - downloading: false, }); } else { this.setState({ @@ -139,11 +131,15 @@ export let DownloadLink = React.createClass({ }); }, render: function() { - var label = (!this.state.downloading ? this.props.label : this.props.downloadingLabel); + const label = 'progress' in this.props ? `${parseInt(this.props.progress * 100)}% complete` : this.props.label; return ( - + + {'progress' in this.props + ? + : null}

Downloading to:

@@ -168,7 +164,6 @@ export let WatchLink = React.createClass({ streamName: React.PropTypes.string, label: React.PropTypes.string, button: React.PropTypes.string, - style: React.PropTypes.object, hidden: React.PropTypes.bool, }, handleClick: function() { @@ -207,7 +202,7 @@ export let WatchLink = React.createClass({ }, render: function() { return ( - +
@@ -215,7 +210,7 @@ export let WatchLink = React.createClass({ onConfirmed={this.closeModal}> You don't have enough LBRY credits to pay for this stream. - +
); } }); diff --git a/scss/_gui.scss b/scss/_gui.scss index 5b64a29f0..f200ac239 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -1,6 +1,6 @@ @import "global"; -@mixin text-link($color: $color-primary, $hover-opacity: 0.70) { +@mixin text-link($color: $color-primary, $hover-opacity: 0.70, $mirror: false) { color: $color; .icon { @@ -27,6 +27,19 @@ text-decoration: none; } } + + @if $mirror == false { + color: $color; + } + @else { + color: $color-bg; + background-color: $color; + position: absolute; + white-space: nowrap; + overflow: hidden; + top: 0px; + left: 0px; + } } .icon-fixed-width { @@ -138,6 +151,9 @@ input[type="text"], input[type="search"] } .button-container { + position: relative; + display: inline-block; + + .button-container { margin-left: 12px; @@ -182,6 +198,19 @@ input[type="text"], input[type="search"] box-shadow: $default-box-shadow; padding: 0 12px; } +.button-download +{ + padding: 0 6px; + + text-decoration: none !important; + + &:not(.button-download--mirror) { + @include text-link(darken($color-primary, 1%)); + } + &.button-download--mirror { + @include text-link(darken($color-primary, 1%), $mirror: true); + } +} .button-cancel { padding: 0 12px; From ce82f8cc6f3aa18918e255d793211ff40ff919f5 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 2 Jan 2017 02:01:55 -0500 Subject: [PATCH 06/93] Make FileTile look up its own download status if not provided --- js/component/file-tile.js | 48 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index f86c7ac93..67bbf2962 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -4,6 +4,9 @@ import {Link, DownloadLink, WatchLink} from '../component/link.js'; import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; let FileTile = React.createClass({ + _isMounted: false, + _statusCheckInterval: 5000, + propTypes: { name: React.PropTypes.string.isRequired, mediaType: React.PropTypes.string.isRequired, @@ -13,12 +16,38 @@ let FileTile = React.createClass({ cost: React.PropTypes.number, costIncludesData: React.PropTypes.boolean, }, + updateFileInfo: function(progress=null) { + const updateStatusCallback = ((result) => { + if (!this._isMounted || 'fileInfo' in this.props) { + /** + * The component was unmounted, or a file info data structure has now been provided by the + * containing component. + */ + return; + } + + this.setState({ + fileInfo: result || null, + }); + + setTimeout(() => { this.updateFileInfo() }, this._statusCheckInterval); + }); + + if ('sdHash' in this.props) { + lbry.getFileInfoBySdHash(this.props.sdHash, updateStatusCallback); + } else if ('name' in this.props) { + lbry.getFileInfoByName(this.props.name, updateStatusCallback); + } else { + throw new Error("No progress, stream name or sd hash passed to FileTile"); + } + }, getInitialState: function() { return { downloading: false, isHovered: false, cost: null, costIncludesData: null, + fileInfo: 'fileInfo' in this.props ? this.props.fileInfo : null, } }, getDefaultProps: function() { @@ -51,8 +80,23 @@ let FileTile = React.createClass({ }); } }, + componentDidMount: function() { + this._isMounted = true; + this.updateFileInfo(); + }, + componentWillUnmount: function() { + this._isMounted = false; + }, render: function() { - let obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; + const obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; + + let downloadLinkExtraProps = {}; + if (this.state.fileInfo !== null) { + const {written_bytes, total_bytes, completed} = this.state.fileInfo; + downloadLinkExtraProps['progress'] = written_bytes / total_bytes; + downloadLinkExtraProps['downloading'] = !completed; + } + return (
@@ -76,7 +120,7 @@ let FileTile = React.createClass({
{this.props.mediaType == 'video' ? : null} {!this.props.isMine - ? + ? : null}

From bcaad75df430d4dbc80f2400420af6cb738edd44 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 6 Jan 2017 06:27:23 -0500 Subject: [PATCH 07/93] Improve and refactor Download links - Use a single "state" prop to track download progress (not-started, downloading, or done) - Accept a file path as a prop an "Open" link after the download is complete - Trim some outdated code; improve CSS class names --- js/component/link.js | 70 +++++++++++++++++++++++++++++++------------- scss/_gui.scss | 5 ++-- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index e8b01d9b3..a299f4986 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -86,27 +86,12 @@ export let DownloadLink = React.createClass({ sdHash: React.PropTypes.string, label: React.PropTypes.string, button: React.PropTypes.string, + state: React.PropTypes.oneOf(['not-started', 'downloading', 'done']), + progress: React.PropTypes.number, + path: React.PropTypes.string, hidden: React.PropTypes.bool, }, - getDefaultProps: function() { - return { - icon: 'icon-download', - label: 'Download', - downloading: false, - } - }, - getInitialState: function() { - return { - filePath: null, - modal: null, - } - }, - closeModal: function() { - this.setState({ - modal: null, - }) - }, - handleClick: function() { + tryDownload: function() { lbry.getCostInfoForName(this.props.streamName, ({cost}) => { lbry.getBalance((balance) => { if (cost > balance) { @@ -130,8 +115,53 @@ export let DownloadLink = React.createClass({ }); }); }, + getDefaultProps: function() { + return { + state: 'not-started', + } + }, + getInitialState: function() { + return { + filePath: null, + modal: null, + } + }, + closeModal: function() { + this.setState({ + modal: null, + }) + }, + handleClick: function() { + if (this.props.state == 'not-started') { + this.tryDownload(); + } else if (this.props.state == 'done') { + lbry.revealFile(this.props.path); + } + }, render: function() { - const label = 'progress' in this.props ? `${parseInt(this.props.progress * 100)}% complete` : this.props.label; + let linkBlock; + if (this.props.state == 'not-started') { + linkBlock = ( + + ); + } else if (this.props.state == 'downloading') { + const label = `${parseInt(this.props.progress * 100)}% complete`; + linkBlock = ( + + + + + ); + } else if (this.props.state == 'done') { + linkBlock = ( + + ); + } else { + throw new Error(`Unknown download state ${this.props.state} passed to DownloadLink`); + } + return ( Date: Fri, 6 Jan 2017 06:27:43 -0500 Subject: [PATCH 08/93] FileTile improvements and refactoring - Now accepts a single metadata object for all metadata fields so the surrounding components don't have to break out the individual fields into props. - Now tracks whether the file was published by the user, and if there's a copy on their machine (will look up using API calls if needed) - Use the new "state" prop for DownloadLink - General refactoring and cleanup --- js/component/file-tile.js | 81 ++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 67bbf2962..caa4e374c 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -8,16 +8,18 @@ let FileTile = React.createClass({ _statusCheckInterval: 5000, propTypes: { - name: React.PropTypes.string.isRequired, - mediaType: React.PropTypes.string.isRequired, - title: React.PropTypes.string.isRequired, - description: React.PropTypes.string, - compact: React.PropTypes.boolean, + metadata: React.PropTypes.object.isRequired, + name: React.PropTypes.string, + sdHash: React.PropTypes.string, + available: React.PropTypes.bool, + isMine: React.PropTypes.bool, + local: React.PropTypes.bool, + path: React.PropTypes.string, cost: React.PropTypes.number, - costIncludesData: React.PropTypes.boolean, + costIncludesData: React.PropTypes.bool, }, updateFileInfo: function(progress=null) { - const updateStatusCallback = ((result) => { + const updateStatusCallback = ((fileInfo) => { if (!this._isMounted || 'fileInfo' in this.props) { /** * The component was unmounted, or a file info data structure has now been provided by the @@ -27,7 +29,8 @@ let FileTile = React.createClass({ } this.setState({ - fileInfo: result || null, + fileInfo: fileInfo || null, + local: !!fileInfo, }); setTimeout(() => { this.updateFileInfo() }, this._statusCheckInterval); @@ -35,12 +38,38 @@ let FileTile = React.createClass({ if ('sdHash' in this.props) { lbry.getFileInfoBySdHash(this.props.sdHash, updateStatusCallback); + this.getIsMineIfNeeded(this.props.sdHash); } else if ('name' in this.props) { - lbry.getFileInfoByName(this.props.name, updateStatusCallback); + lbry.getFileInfoByName(this.props.name, (fileInfo) => { + this.getIsMineIfNeeded(fileInfo.sd_hash); + + updateStatusCallback(fileInfo); + }); } else { throw new Error("No progress, stream name or sd hash passed to FileTile"); } }, + getIsMineIfNeeded: function(sdHash) { + if (this.state.isMine !== null) { + // The info was already provided by this.props.isMine + return; + } + + lbry.getMyClaims((claimsInfo) => { + for (let {value} of claimsInfo) { + if (JSON.parse(value).sources.lbry_sd_hash == sdHash) { + this.setState({ + isMine: true, + }); + return; + } + } + + this.setState({ + isMine: false, + }); + }); + }, getInitialState: function() { return { downloading: false, @@ -48,6 +77,8 @@ let FileTile = React.createClass({ cost: null, costIncludesData: null, fileInfo: 'fileInfo' in this.props ? this.props.fileInfo : null, + isMine: 'isMine' in this.props ? this.props.isMine : null, + local: 'local' in this.props ? this.props.local : null, } }, getDefaultProps: function() { @@ -66,6 +97,8 @@ let FileTile = React.createClass({ }); }, componentWillMount: function() { + this.updateFileInfo(); + if ('cost' in this.props) { this.setState({ cost: this.props.cost, @@ -82,29 +115,39 @@ let FileTile = React.createClass({ }, componentDidMount: function() { this._isMounted = true; - this.updateFileInfo(); }, componentWillUnmount: function() { this._isMounted = false; }, render: function() { + if (this.state.isMine === null || this.state.local === null) { + // Can't render until we know whether we own the file and if we have a local copy + return null; + } + const obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; let downloadLinkExtraProps = {}; - if (this.state.fileInfo !== null) { - const {written_bytes, total_bytes, completed} = this.state.fileInfo; - downloadLinkExtraProps['progress'] = written_bytes / total_bytes; - downloadLinkExtraProps['downloading'] = !completed; + if (this.state.fileInfo === null) { + downloadLinkExtraProps.state = 'not-started'; + } else if (!this.state.fileInfo.completed) { + downloadLinkExtraProps.state = 'downloading'; + + const {written_bytes, total_bytes, path} = this.state.fileInfo; + downloadLinkExtraProps.progress = written_bytes / total_bytes; + } else { + downloadLinkExtraProps.state = 'done'; + downloadLinkExtraProps.path = this.state.fileInfo.download_path; } return (

- +
- {this.state.cost !== null + {this.state.cost !== null && !this.state.local ? @@ -113,19 +156,19 @@ let FileTile = React.createClass({

- {this.props.title} + {this.props.metadata.title}

- {this.props.mediaType == 'video' ? : null} + {this.props.metadata.content_type.startsWith('video/') ? : null} {!this.props.isMine ? : null}

- {this.props.description} + {this.props.metadata.description}

From a36dd5cfe45836682959c453c17efc106b5dc169 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 6 Jan 2017 06:37:59 -0500 Subject: [PATCH 09/93] Convert search results to use FileTile --- js/page/discover.js | 128 +------------------------------------------- 1 file changed, 2 insertions(+), 126 deletions(-) diff --git a/js/page/discover.js b/js/page/discover.js index 9bb26827d..e9a41c00e 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -42,12 +42,9 @@ var SearchNoResults = React.createClass({ var SearchResults = React.createClass({ render: function() { var rows = []; - this.props.results.forEach(function(result) { - console.log(result); - var mediaType = lbry.getMediaType(result.value.content_type); + this.props.results.forEach(function({name, sources, value}) { rows.push( - + ); }); return ( @@ -56,127 +53,6 @@ var SearchResults = React.createClass({ } }); -var - searchRowStyle = { - height: (24 * 7) + 'px', - overflowY: 'hidden' - }, - searchRowCompactStyle = { - height: '180px', - }, - searchRowImgStyle = { - maxWidth: '100%', - maxHeight: (24 * 7) + 'px', - display: 'block', - marginLeft: 'auto', - marginRight: 'auto' - }, - searchRowTitleStyle = { - fontWeight: 'bold' - }, - searchRowTitleCompactStyle = { - fontSize: '1.25em', - lineHeight: '1.15', - }, - searchRowCostStyle = { - float: 'right', - }, - searchRowDescriptionStyle = { - color : '#444', - marginTop: '12px', - fontSize: '0.9em' - }; - - -var SearchResultRow = React.createClass({ - getInitialState: function() { - return { - downloading: false, - isHovered: false, - cost: null, - costIncludesData: null, - } - }, - handleMouseOver: function() { - this.setState({ - isHovered: true, - }); - }, - handleMouseOut: function() { - this.setState({ - isHovered: false, - }); - }, - componentWillMount: function() { - if ('cost' in this.props) { - this.setState({ - cost: this.props.cost, - costIncludesData: this.props.costIncludesData, - }); - } else { - lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => { - this.setState({ - cost: cost, - costIncludesData: includesData, - }); - }); - } - }, - render: function() { - var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; - if (!this.props.compact) { - var style = searchRowStyle; - var titleStyle = searchRowTitleStyle; - } else { - var style = Object.assign({}, searchRowStyle, searchRowCompactStyle); - var titleStyle = Object.assign({}, searchRowTitleStyle, searchRowTitleCompactStyle); - } - - return ( -
-
-
- -
-
- {this.state.cost !== null - ? - - - : null} - -

- - - {this.props.title} - - -

-
- {this.props.mediaType == 'video' ? : null} - -
-

- - {this.props.description} - -

-
-
- { - !obscureNsfw || !this.state.isHovered ? null : -
-

- This content is Not Safe For Work. - To view adult content, please change your . -

-
- } -
- ); - } -}); - var featuredContentItemContainerStyle = { position: 'relative', }; From 4bd29ed515317b536a3940ecf3dd549a2eb16b1b Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 6 Jan 2017 06:39:33 -0500 Subject: [PATCH 10/93] Convert FeaturedContentItem to pass one metadata prop into FileTile --- js/page/discover.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/page/discover.js b/js/page/discover.js index e9a41c00e..6f86e2924 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -99,9 +99,7 @@ var FeaturedContentItem = React.createClass({ } return (
- +
); } }); From ac32ec366a6bfbad89b0509a728f73af52590c6f Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 6 Jan 2017 06:41:31 -0500 Subject: [PATCH 11/93] Update MyFilesPage to work with updated FileTile Also did a little cleanup/refactoring --- js/page/my_files.js | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/js/page/my_files.js b/js/page/my_files.js index f2381555b..eed82b670 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -122,7 +122,7 @@ var MyFilesPage = React.createClass({ return { filesInfo: null, publishedFilesSdHashes: null, - filesAvailable: {}, + filesAvailable: null, sortBy: 'date', }; }, @@ -260,8 +260,7 @@ var MyFilesPage = React.createClass({ const filesInfoSorted = this._sortFunctions[this.state.sortBy](this.state.filesInfo); for (let fileInfo of filesInfoSorted) { - let {completed, written_bytes, total_bytes, lbry_uri, file_name, download_path, - stopped, metadata, sd_hash} = fileInfo; + let {completed, lbry_uri, sd_hash, metadata, download_path, stopped, pending} = fileInfo; if (!metadata || seenUris[lbry_uri]) { continue; @@ -269,26 +268,9 @@ var MyFilesPage = React.createClass({ seenUris[lbry_uri] = true; - let {title, thumbnail} = metadata; - - if (!fileInfo.pending && typeof metadata == 'object') { - var {title, thumbnail} = metadata; - var pending = false; - } else { - var title = null; - var thumbnail = null; - var pending = true; - } - - var ratioLoaded = written_bytes / total_bytes; - - var mediaType = lbry.getMediaType(metadata.content_type, file_name); - var showWatchButton = (mediaType == 'video'); - - content.push(); + content.push(); } } return ( From 5fcedd0d01404d17cd427449760431049928a259 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 8 Jan 2017 22:36:49 -0500 Subject: [PATCH 12/93] Update props in FileTile - Add fileInfo - Remove path (now calculated from fileInfo) --- js/component/file-tile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index caa4e374c..c515a6699 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -9,12 +9,12 @@ let FileTile = React.createClass({ propTypes: { metadata: React.PropTypes.object.isRequired, + fileInfo: React.PropTypes.string, name: React.PropTypes.string, sdHash: React.PropTypes.string, available: React.PropTypes.bool, isMine: React.PropTypes.bool, local: React.PropTypes.bool, - path: React.PropTypes.string, cost: React.PropTypes.number, costIncludesData: React.PropTypes.bool, }, From 1e61af3b24159351cea63664e35c47fcf4f69248 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 8 Jan 2017 22:41:04 -0500 Subject: [PATCH 13/93] Rename things in FileTile - updateStatusCallback -> updateFileInfoCallback - this._statusCheckInterval -> this._fileInfoCheckInterval --- js/component/file-tile.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index c515a6699..8690ba5fd 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -5,7 +5,7 @@ import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; let FileTile = React.createClass({ _isMounted: false, - _statusCheckInterval: 5000, + _fileInfoCheckInterval: 5000, propTypes: { metadata: React.PropTypes.object.isRequired, @@ -19,7 +19,7 @@ let FileTile = React.createClass({ costIncludesData: React.PropTypes.bool, }, updateFileInfo: function(progress=null) { - const updateStatusCallback = ((fileInfo) => { + const updateFileInfoCallback = ((fileInfo) => { if (!this._isMounted || 'fileInfo' in this.props) { /** * The component was unmounted, or a file info data structure has now been provided by the @@ -33,17 +33,17 @@ let FileTile = React.createClass({ local: !!fileInfo, }); - setTimeout(() => { this.updateFileInfo() }, this._statusCheckInterval); + setTimeout(() => { this.updateFileInfo() }, this._fileInfoCheckInterval); }); if ('sdHash' in this.props) { - lbry.getFileInfoBySdHash(this.props.sdHash, updateStatusCallback); + lbry.getFileInfoBySdHash(this.props.sdHash, updateFileInfoCallback); this.getIsMineIfNeeded(this.props.sdHash); } else if ('name' in this.props) { lbry.getFileInfoByName(this.props.name, (fileInfo) => { this.getIsMineIfNeeded(fileInfo.sd_hash); - updateStatusCallback(fileInfo); + updateFileInfoCallback(fileInfo); }); } else { throw new Error("No progress, stream name or sd hash passed to FileTile"); From d067a6e006bb5aa4718c0bd027795776a1aa86eb Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Sun, 8 Jan 2017 23:56:21 -0500 Subject: [PATCH 14/93] Refactor and clean up Link component --- js/component/link.js | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index a299f4986..cab18a39b 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -5,25 +5,44 @@ import {Icon, ToolTip} from './common.js'; export let Link = React.createClass({ + propTypes: { + label: React.PropTypes.string, + icon: React.PropTypes.string, + button: React.PropTypes.string, + badge: React.PropTypes.string, + hidden: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + hidden: false, + disabled: false, + }; + }, handleClick: function() { if (this.props.onClick) { this.props.onClick(); } }, render: function() { - var href = this.props.href ? this.props.href : 'javascript:;', - icon = this.props.icon ? : '', - className = (this.props.className ? this.props.className : '') + - (this.props.button ? ' button-block button-' + this.props.button : '') + - (this.props.hidden ? ' hidden' : '') + - (this.props.disabled ? ' disabled' : ''); + if (this.props.hidden) { + return null; + } + + const className = (this.props.className || '') + + (this.props.button ? ' button-block button-' + this.props.button : '') + + (!this.props.className && !this.props.button ? 'button-text' : '') + + (this.props.disabled ? ' disabled' : ''); return ( - - {this.props.icon ? icon : '' } + + {'icon' in this.props + ? + : null} {this.props.label} - {this.props.badge ? {this.props.badge} : '' } + {'badge' in this.props + ? {this.props.badge} + : null} ); } From 9f743700b79fe7185ef3e3fb835bde2d97a49c62 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 02:24:32 -0500 Subject: [PATCH 15/93] Refactor Menu component Instead of having the menu keeping track of whether it's open, we now leave it up to the surrounding component to mount and unmount it. This avoids bubbling issues when listening for events on an external toggle button. We also now use the recommended callback style for refs instead of named refs. --- js/component/menu.js | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/js/component/menu.js b/js/component/menu.js index a3670646d..ee3901b78 100644 --- a/js/component/menu.js +++ b/js/component/menu.js @@ -8,26 +8,14 @@ export let menuStyle = { }; export let Menu = React.createClass({ - handleWindowClick: function(e) { - if (this.props.toggleButton && ReactDOM.findDOMNode(this.props.toggleButton).contains(e.target)) { - // Toggle button was clicked - this.setState({ - open: !this.state.open - }); - } else if (this.state.open && !this.refs.div.contains(e.target)) { - // Menu is open and user clicked outside of it - this.setState({ - open: false - }); - } - }, propTypes: { - openButton: React.PropTypes.element, + onClickOut: React.PropTypes.func.isRequired, }, - getInitialState: function() { - return { - open: false, - }; + handleWindowClick: function(e) { + if (!this._div.contains(e.target)) { + // Menu is open and user clicked outside of it + this.props.onClickOut(); + } }, componentDidMount: function() { window.addEventListener('click', this.handleWindowClick, false); @@ -36,8 +24,10 @@ export let Menu = React.createClass({ window.removeEventListener('click', this.handleWindowClick, false); }, render: function() { + const {onClickOut, ...other} = this.props; return ( -
+
this._div = div} className={'menu ' + (this.props.className || '')} + {... other}> {this.props.children}
); From 0560f13ec41f954f2c861cc5adf0bfed88a38b51 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 03:03:13 -0500 Subject: [PATCH 16/93] Convert Menu styles to CSS --- js/component/menu.js | 10 +--------- scss/_gui.scss | 12 ++++++++++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/js/component/menu.js b/js/component/menu.js index ee3901b78..cf76ec047 100644 --- a/js/component/menu.js +++ b/js/component/menu.js @@ -2,11 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import {Icon} from './common.js'; -// Generic menu styles -export let menuStyle = { - whiteSpace: 'nowrap' -}; - export let Menu = React.createClass({ propTypes: { onClickOut: React.PropTypes.func.isRequired, @@ -34,9 +29,6 @@ export let Menu = React.createClass({ } }); -export let menuItemStyle = { - display: 'block', -}; export let MenuItem = React.createClass({ propTypes: { href: React.PropTypes.string, @@ -53,7 +45,7 @@ export let MenuItem = React.createClass({ var icon = (this.props.icon ? : null); return ( - {this.props.iconPosition == 'left' ? icon : null} {this.props.label} diff --git a/scss/_gui.scss b/scss/_gui.scss index a13a5cf5f..a131f0c2d 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -397,6 +397,18 @@ input[type="text"], input[type="search"] word-break: break-all; } +.menu { + white-space: nowrap; +} + +.menu__menu-item { + display: block; + text-decoration: none !important; + &:hover { + text-decoration: underline !important; + } +} + .file-tile--compact { height: 180px; From 78cb69a5eff8ec38c33b7fc91733c053909b5c75 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 03:26:48 -0500 Subject: [PATCH 17/93] Update menu formatting --- scss/_global.scss | 1 + scss/_gui.scss | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scss/_global.scss b/scss/_global.scss index ad5854a36..50b6c44c8 100644 --- a/scss/_global.scss +++ b/scss/_global.scss @@ -8,6 +8,7 @@ $color-text-dark: #000; $color-help: rgba(0,0,0,.6); $color-canvas: #f5f5f5; $color-bg: #ffffff; +$color-bg-alt: #D9D9D9; $color-money: #216C2A; $color-meta-light: #505050; diff --git a/scss/_gui.scss b/scss/_gui.scss index a131f0c2d..cf5f72de0 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -194,7 +194,7 @@ input[type="text"], input[type="search"] } .button-alt { - background-color: rgba(0,0,0,.15); + background-color: $color-bg-alt; box-shadow: $default-box-shadow; padding: 0 12px; } @@ -398,7 +398,12 @@ input[type="text"], input[type="search"] } .menu { + position: fixed; white-space: nowrap; + background-color: $color-bg-alt; + box-shadow: $default-box-shadow; + padding: $spacing-vertical; + border-radius: 2px; } .menu__menu-item { From 18192cd93b25a1a0208607f6f23133bbe36ec41e Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 03:54:52 -0500 Subject: [PATCH 18/93] Add Stage 2 Babel preset Needed for rest spread operator (e.g. let {foo, ...bar} = baz) --- package.json | 3 ++- webpack.config.js | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 40ef3d434..e8868f571 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "babel-core": "^6.18.2", "babel-loader": "^6.2.8", "babel-plugin-react-require": "^3.0.0", + "babel-polyfill": "^6.20.0", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", - "babel-polyfill": "^6.20.0", + "babel-preset-stage-2": "^6.18.0", "eslint": "^3.10.2", "eslint-config-airbnb": "^13.0.0", "eslint-loader": "^1.6.1", diff --git a/webpack.config.js b/webpack.config.js index 34b4bdffc..d13ac540c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,12 +25,12 @@ module.exports = { loaders: [ { test: /\.css$/, loader: "style!css" }, { - test: /\.jsx?$/, - // Enable caching for improved performance during development - // It uses default OS directory by default. If you need - // something more custom, pass a path to it. - // I.e., babel?cacheDirectory= - loader: 'babel?cacheDirectory' + test: /\.jsx?$/, + loader: 'babel', + query: { + cacheDirectory: true, + presets:[ 'es2015', 'react', 'stage-3' ] + } } ] } From 80272ab8f053a926f23b6988048d625874b9a5c4 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 04:09:20 -0500 Subject: [PATCH 19/93] Update prop names and prop processing logic in Icon --- js/component/common.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/js/component/common.js b/js/component/common.js index 465b2bd41..82ea34bc4 100644 --- a/js/component/common.js +++ b/js/component/common.js @@ -5,14 +5,15 @@ import $clamp from 'clamp'; //component/icon.js export let Icon = React.createClass({ propTypes: { - style: React.PropTypes.object, - fixed: React.PropTypes.bool, + icon: React.PropTypes.string.isRequired, className: React.PropTypes.string, + fixed: React.PropTypes.bool, }, render: function() { - var className = ('icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') + this.props.icon + ' ' + - (this.props.className || '')); - return + const {fixed, className, ...other} = this.props; + const spanClassName = ('icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') + + this.props.icon + ' ' + (this.props.className || '')); + return } }); From a2641c1a98b996af8ba0743aa3e24b99c877cf97 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 05:43:57 -0500 Subject: [PATCH 20/93] Add support for custom conteint in Before you could only specify an icon and text label. --- js/component/file-tile.js | 2 +- js/component/link.js | 25 ++++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 8690ba5fd..88ce9f55d 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -163,7 +163,7 @@ let FileTile = React.createClass({
{this.props.metadata.content_type.startsWith('video/') ? : null} {!this.props.isMine - ? + ? : null}

diff --git a/js/component/link.js b/js/component/link.js index cab18a39b..a667dabdd 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -33,16 +33,27 @@ export let Link = React.createClass({ (!this.props.className && !this.props.button ? 'button-text' : '') + (this.props.disabled ? ' disabled' : ''); + let content; + if (this.props.children) { // Custom content + content = this.props.children; + } else { + content = ( + + {'icon' in this.props + ? + : null} + {this.props.label} + {'badge' in this.props + ? {this.props.badge} + : null} + + ); + } + return ( - {'icon' in this.props - ? - : null} - {this.props.label} - {'badge' in this.props - ? {this.props.badge} - : null} + {content} ); } From 78e9a02b7026f4ca5b1292976aa466e11d019da7 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 05:48:04 -0500 Subject: [PATCH 21/93] Add DropDown component and convert DownloadLink to use it --- js/component/link.js | 83 ++++++++++++++++++++++++++++++++++++++- js/page/my_files.js | 92 +------------------------------------------- 2 files changed, 83 insertions(+), 92 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index a667dabdd..71cb50ff3 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -1,6 +1,8 @@ import React from 'react'; import lbry from '../lbry.js'; +import FormField from './form.js'; import Modal from './modal.js'; +import {Menu, MenuItem} from './menu.js'; import {Icon, ToolTip} from './common.js'; @@ -109,17 +111,62 @@ export let ToolTipLink = React.createClass({ } }); +export let DropDown = React.createClass({ + propTypes: { + onCaretClick: React.PropTypes.func, + }, + handleCaretClicked: function(event) { + /** + * The menu handles caret clicks via a window event listener, so we just need to prevent clicks + * on the caret from bubbling up to the link + */ + this.setState({ + menuOpen: !this.state.menuOpen, + }); + event.stopPropagation(); + return false; + }, + closeMenu: function(event) { + this.setState({ + menuOpen: false, + }); + }, + getInitialState: function() { + return { + menuOpen: false, + }; + }, + render: function() { + const {onCaretClick, ...other} = this.props; + return ( +

+ + {this.props.label} + + + {this.state.menuOpen + ? + {this.props.children} + + : null} +
+ ); + } +}); + export let DownloadLink = React.createClass({ propTypes: { type: React.PropTypes.string, streamName: React.PropTypes.string, sdHash: React.PropTypes.string, + metadata: React.PropTypes.object, label: React.PropTypes.string, button: React.PropTypes.string, state: React.PropTypes.oneOf(['not-started', 'downloading', 'done']), progress: React.PropTypes.number, path: React.PropTypes.string, hidden: React.PropTypes.bool, + deleteChecked: React.PropTypes.bool, }, tryDownload: function() { lbry.getCostInfoForName(this.props.streamName, ({cost}) => { @@ -145,6 +192,30 @@ export let DownloadLink = React.createClass({ }); }); }, + openMenu: function() { + this.setState({ + menuOpen: !this.state.menuOpen, + }); + }, + handleDeleteCheckboxClicked: function(event) { + this.setState({ + deleteChecked: event.target.checked, + }); + }, + handleRevealClicked: function() { + lbry.revealFile(this.props.path); + }, + handleRemoveClicked: function() { + this.setState({ + modal: 'confirmRemove', + }); + }, + handleRemoveConfirmed: function() { + lbry.deleteFile(this.props.sdHash || this.props.streamName, this.state.deleteChecked); + this.setState({ + modal: null, + }); + }, getDefaultProps: function() { return { state: 'not-started', @@ -154,6 +225,7 @@ export let DownloadLink = React.createClass({ return { filePath: null, modal: null, + menuOpen: false, } }, closeModal: function() { @@ -186,7 +258,10 @@ export let DownloadLink = React.createClass({ ); } else if (this.props.state == 'done') { linkBlock = ( - + + + + ); } else { throw new Error(`Unknown download state ${this.props.state} passed to DownloadLink`); @@ -213,6 +288,12 @@ export let DownloadLink = React.createClass({ onConfirmed={this.closeModal}> LBRY was unable to download the stream lbry://{this.props.streamName}. + +

Are you sure you'd like to remove {this.props.metadata.title} from LBRY?

+ + +
); } diff --git a/js/page/my_files.js b/js/page/my_files.js index eed82b670..b0b510c5d 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -1,101 +1,11 @@ import React from 'react'; import lbry from '../lbry.js'; -import {Link, WatchLink} from '../component/link.js'; -import {Menu, MenuItem} from '../component/menu.js'; +import {Link} from '../component/link.js'; import FormField from '../component/form.js'; import FileTile from '../component/file-tile.js'; import Modal from '../component/modal.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; -var moreMenuStyle = { - position: 'absolute', - display: 'block', - top: '26px', - right: '13px', -}; -var MyFilesRowMoreMenu = React.createClass({ - propTypes: { - title: React.PropTypes.string.isRequired, - path: React.PropTypes.string.isRequired, - completed: React.PropTypes.bool.isRequired, - lbryUri: React.PropTypes.string.isRequired, - }, - handleRevealClicked: function() { - lbry.revealFile(this.props.path); - }, - handleRemoveClicked: function() { - lbry.deleteFile(this.props.lbryUri, false); - }, - handleDeleteClicked: function() { - this.setState({ - modal: 'confirmDelete', - }); - }, - handleDeleteConfirmed: function() { - lbry.deleteFile(this.props.lbryUri); - this.setState({ - modal: null, - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - getInitialState: function() { - return { - modal: null, - }; - }, - render: function() { - return ( -
- -
- {/* @TODO: Switch to OS specific wording */} - - -
-
- - Are you sure you'd like to delete {this.props.title}? This will {this.props.completed ? ' stop the download and ' : ''} - permanently remove the file from your system. - -
- ); - } -}); - -var moreButtonColumnStyle = { - height: '120px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - moreButtonContainerStyle = { - display: 'block', - position: 'relative', - }, - moreButtonStyle = { - fontSize: '1.3em', - }, - progressBarStyle = { - height: '15px', - width: '230px', - backgroundColor: '#444', - border: '2px solid #eee', - display: 'inline-block', - }, - artStyle = { - maxHeight: '100px', - maxWidth: '100%', - display: 'block', - marginLeft: 'auto', - marginRight: 'auto', - }; - - var MyFilesPage = React.createClass({ _fileTimeout: null, _fileInfoCheckRate: 300, From 8d9e63ad5394031fa62fa90210e8ea5522fb0f67 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 9 Jan 2017 06:31:42 -0500 Subject: [PATCH 22/93] Customize "Open in Folder" message based on platform --- js/component/link.js | 3 ++- js/main.js | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 71cb50ff3..1d1916776 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -257,9 +257,10 @@ export let DownloadLink = React.createClass({ ); } else if (this.props.state == 'done') { + const openInFolderMessage = localStorage.getItem('platform') == 'Darwin' ? 'Open in Finder' : 'Open in Folder'; linkBlock = ( - + ); diff --git a/js/main.js b/js/main.js index d5cb1ab57..b087fc969 100644 --- a/js/main.js +++ b/js/main.js @@ -26,7 +26,14 @@ var init = function() { } }); } else { - ReactDOM.render(, canvas); + if (localStorage.getItem('platform') === null) { + lbry.getVersionInfo(({os_system}) => { + localStorage.setItem('platform', os_system); + ReactDOM.render(, canvas); + }); + } else { + ReactDOM.render(, canvas); + } } }}/>, canvas From 6ea5e41368b6d89c2dce1f653d448c028cc0662f Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 01:10:27 -0500 Subject: [PATCH 23/93] Use in-browser platform check in DownloadLink --- js/component/link.js | 2 +- js/main.js | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 1d1916776..b05324a86 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -257,7 +257,7 @@ export let DownloadLink = React.createClass({ ); } else if (this.props.state == 'done') { - const openInFolderMessage = localStorage.getItem('platform') == 'Darwin' ? 'Open in Finder' : 'Open in Folder'; + const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder'; linkBlock = ( diff --git a/js/main.js b/js/main.js index b087fc969..d5cb1ab57 100644 --- a/js/main.js +++ b/js/main.js @@ -26,14 +26,7 @@ var init = function() { } }); } else { - if (localStorage.getItem('platform') === null) { - lbry.getVersionInfo(({os_system}) => { - localStorage.setItem('platform', os_system); - ReactDOM.render(, canvas); - }); - } else { - ReactDOM.render(, canvas); - } + ReactDOM.render(, canvas); } }}/>, canvas From f8ae201e766c3cf33a43ff2828d9155e6afe57fb Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 01:28:21 -0500 Subject: [PATCH 24/93] Add initial value for this.state.deleteChecked in DownloadLink --- js/component/link.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/component/link.js b/js/component/link.js index b05324a86..3f40b7555 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -226,6 +226,7 @@ export let DownloadLink = React.createClass({ filePath: null, modal: null, menuOpen: false, + deleteChecked: false, } }, closeModal: function() { From 8a1f0b76b1e0ec195d0c884df2afbecd2d46c12e Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 01:59:32 -0500 Subject: [PATCH 25/93] Correct how SD hashes are extracted in SearchResults --- js/page/discover.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/page/discover.js b/js/page/discover.js index 6f86e2924..3c79d96c5 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -42,9 +42,9 @@ var SearchNoResults = React.createClass({ var SearchResults = React.createClass({ render: function() { var rows = []; - this.props.results.forEach(function({name, sources, value}) { + this.props.results.forEach(function({name, value}) { rows.push( - + ); }); return ( From 986ce927f04ac5aef2ea5263d1ec5088ca676d25 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 02:15:12 -0500 Subject: [PATCH 26/93] In DownloadLink, offer dropdown in "downloading" state --- js/component/link.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 3f40b7555..15d7f374e 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -242,6 +242,13 @@ export let DownloadLink = React.createClass({ } }, render: function() { + const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder'; + + const dropDownItems = [ + , + , + ]; + let linkBlock; if (this.props.state == 'not-started') { linkBlock = ( @@ -251,18 +258,20 @@ export let DownloadLink = React.createClass({ const label = `${parseInt(this.props.progress * 100)}% complete`; linkBlock = ( - - - + + {dropDownItems} + + + {dropDownItems} + + ); } else if (this.props.state == 'done') { - const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder'; linkBlock = ( - - + {dropDownItems} ); } else { From 8ae5b6e0edd20a3b2ace84487e97c0555a078960 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 02:40:10 -0500 Subject: [PATCH 27/93] In DownloadLink, immediately switch to "0% Downloaded" on click Before, it would only change after several seconds when the download actually started. --- js/component/link.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 15d7f374e..cad6b5e60 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -169,22 +169,28 @@ export let DownloadLink = React.createClass({ deleteChecked: React.PropTypes.bool, }, tryDownload: function() { + this.setState({ + attemptingDownload: true, + }); lbry.getCostInfoForName(this.props.streamName, ({cost}) => { lbry.getBalance((balance) => { if (cost > balance) { this.setState({ modal: 'notEnoughCredits', + attemptingDownload: false, }); } else { lbry.getStream(this.props.streamName, (streamInfo) => { if (streamInfo === null || typeof streamInfo !== 'object') { this.setState({ modal: 'timedOut', + attemptingDownload: false, }); } else { this.setState({ modal: 'downloadStarted', filePath: streamInfo.path, + attemptingDownload: false, }); } }); @@ -227,6 +233,7 @@ export let DownloadLink = React.createClass({ modal: null, menuOpen: false, deleteChecked: false, + attemptingDownload: false, } }, closeModal: function() { @@ -250,12 +257,9 @@ export let DownloadLink = React.createClass({ ]; let linkBlock; - if (this.props.state == 'not-started') { - linkBlock = ( - - ); - } else if (this.props.state == 'downloading') { - const label = `${parseInt(this.props.progress * 100)}% complete`; + if (this.state.attemptingDownload || this.props.state == 'downloading') { + const progress = this.state.attemptingDownload ? 0 : this.props.progress; + const label = `${parseInt(progress * 100)}% complete`; linkBlock = ( + onClick={this.handleClick} style={{width: `${progress * 100}%`}}> {dropDownItems} ); + } else if (this.props.state == 'not-started') { + linkBlock = ( + + ); } else if (this.props.state == 'done') { linkBlock = ( From 802d8c6777c88418716390be8e0f7849ba6136de Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 02:48:14 -0500 Subject: [PATCH 28/93] Remove "Download Started" modal --- js/component/link.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index cad6b5e60..b33fc0531 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -188,7 +188,6 @@ export let DownloadLink = React.createClass({ }); } else { this.setState({ - modal: 'downloadStarted', filePath: streamInfo.path, attemptingDownload: false, }); @@ -288,17 +287,6 @@ export let DownloadLink = React.createClass({ return ( - - {'progress' in this.props - ? - : null} - -

Downloading to:

-
{this.state.filePath}
-
You don't have enough LBRY credits to pay for this stream. From b524eec82669af19daef5efdc1d85131864d5df5 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 10 Jan 2017 03:10:36 -0500 Subject: [PATCH 29/93] Hide caret on "Downloading" dropdown when download hasn't actually started --- js/component/link.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index b33fc0531..559662d2b 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -114,6 +114,12 @@ export let ToolTipLink = React.createClass({ export let DropDown = React.createClass({ propTypes: { onCaretClick: React.PropTypes.func, + showCaret: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + showCaret: true, + }; }, handleCaretClicked: function(event) { /** @@ -142,7 +148,9 @@ export let DropDown = React.createClass({
{this.props.label} - + {this.props.showCaret + ? + : null} {this.state.menuOpen ? @@ -262,11 +270,12 @@ export let DownloadLink = React.createClass({ linkBlock = ( + onClick={this.handleClick} showCaret={!this.state.attemptingDownload}> {dropDownItems} + onClick={this.handleClick} showCaret={!this.state.attemptingDownload} + style={{width: `${progress * 100}%`}}> {dropDownItems} From 94c22961c6c595cbbf37aad04a52344c8cf7cc4a Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 01:41:27 -0500 Subject: [PATCH 30/93] Show "Connecting" instead of "0% Complete" before a download starts Also remove now-unneeded showCaret prop from --- js/component/link.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 559662d2b..ed8ab4a28 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -114,12 +114,6 @@ export let ToolTipLink = React.createClass({ export let DropDown = React.createClass({ propTypes: { onCaretClick: React.PropTypes.func, - showCaret: React.PropTypes.bool, - }, - getDefaultProps: function() { - return { - showCaret: true, - }; }, handleCaretClicked: function(event) { /** @@ -148,9 +142,7 @@ export let DropDown = React.createClass({
{this.props.label} - {this.props.showCaret - ? - : null} + {this.state.menuOpen ? @@ -264,18 +256,19 @@ export let DownloadLink = React.createClass({ ]; let linkBlock; - if (this.state.attemptingDownload || this.props.state == 'downloading') { - const progress = this.state.attemptingDownload ? 0 : this.props.progress; - const label = `${parseInt(progress * 100)}% complete`; + if (this.state.attemptingDownload) { + linkBlock = + } else if (this.props.state == 'downloading') { + const label = `${parseInt(this.props.progress * 100)}% complete`; linkBlock = ( + onClick={this.handleClick}> {dropDownItems} + onClick={this.handleClick} style={{width: `${this.props.progress * 100}%`}}> {dropDownItems} From 0796dbf285a57d0095c9c415acd69217b6f9fc99 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 01:48:02 -0500 Subject: [PATCH 31/93] Add keys to DownloadLink menu items --- js/component/link.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index ed8ab4a28..9814d3920 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -251,8 +251,8 @@ export let DownloadLink = React.createClass({ const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder'; const dropDownItems = [ - , - , + , + , ]; let linkBlock; From 769ee1202025f56eaa6820f2442eda3926641693 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 01:53:06 -0500 Subject: [PATCH 32/93] Add ARIA labels to all modals in link.js --- js/component/link.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 9814d3920..c667f9eee 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -289,6 +289,7 @@ export let DownloadLink = React.createClass({ return ( + {linkBlock} You don't have enough LBRY credits to pay for this stream. @@ -297,8 +298,9 @@ export let DownloadLink = React.createClass({ onConfirmed={this.closeModal}> LBRY was unable to download the stream lbry://{this.props.streamName}. - +

Are you sure you'd like to remove {this.props.metadata.title} from LBRY?

From cddc1ecd220dd7315940fe31f899f8bcf35295a9 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 02:30:43 -0500 Subject: [PATCH 33/93] Remove files from UI immediately (don't wait for daemon to confirm) --- js/component/file-tile.js | 16 +++++++++++++--- js/component/link.js | 15 ++++++++++----- js/page/my_files.js | 2 +- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 88ce9f55d..208de9f9b 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -17,6 +17,7 @@ let FileTile = React.createClass({ local: React.PropTypes.bool, cost: React.PropTypes.number, costIncludesData: React.PropTypes.bool, + hideOnRemove: React.PropTypes.bool, }, updateFileInfo: function(progress=null) { const updateFileInfoCallback = ((fileInfo) => { @@ -73,6 +74,7 @@ let FileTile = React.createClass({ getInitialState: function() { return { downloading: false, + removeConfirmed: false, isHovered: false, cost: null, costIncludesData: null, @@ -84,6 +86,7 @@ let FileTile = React.createClass({ getDefaultProps: function() { return { compact: false, + hideOnRemove: false, } }, handleMouseOver: function() { @@ -96,6 +99,11 @@ let FileTile = React.createClass({ isHovered: false, }); }, + handleRemoveConfirmed: function() { + this.setState({ + removeConfirmed: true, + }); + }, componentWillMount: function() { this.updateFileInfo(); @@ -120,8 +128,9 @@ let FileTile = React.createClass({ this._isMounted = false; }, render: function() { - if (this.state.isMine === null || this.state.local === null) { - // Can't render until we know whether we own the file and if we have a local copy + // Can't render until we know whether we own the file and if we have a local copy + if (this.state.isMine === null || this.state.local === null || + (this.props.hideOnRemove && this.state.removeConfirmed)) { return null; } @@ -163,7 +172,8 @@ let FileTile = React.createClass({
{this.props.metadata.content_type.startsWith('video/') ? : null} {!this.props.isMine - ? + ? : null}

diff --git a/js/component/link.js b/js/component/link.js index c667f9eee..3d4b54f6c 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -167,6 +167,7 @@ export let DownloadLink = React.createClass({ path: React.PropTypes.string, hidden: React.PropTypes.bool, deleteChecked: React.PropTypes.bool, + onRemoveConfirmed: React.PropTypes.func, }, tryDownload: function() { this.setState({ @@ -217,13 +218,18 @@ export let DownloadLink = React.createClass({ }, handleRemoveConfirmed: function() { lbry.deleteFile(this.props.sdHash || this.props.streamName, this.state.deleteChecked); + if (this.props.onRemoveConfirmed) { + this.props.onRemoveConfirmed(); + } this.setState({ modal: null, + attemptingRemove: true, }); }, getDefaultProps: function() { return { state: 'not-started', + hideOnDelete: false, } }, getInitialState: function() { @@ -233,6 +239,7 @@ export let DownloadLink = React.createClass({ menuOpen: false, deleteChecked: false, attemptingDownload: false, + attemptingRemove: false, } }, closeModal: function() { @@ -256,7 +263,9 @@ export let DownloadLink = React.createClass({ ]; let linkBlock; - if (this.state.attemptingDownload) { + if (this.state.attemptingRemove || this.props.state == 'not-started') { + linkBlock = ; + } else if (this.state.attemptingDownload) { linkBlock = } else if (this.props.state == 'downloading') { @@ -273,10 +282,6 @@ export let DownloadLink = React.createClass({ ); - } else if (this.props.state == 'not-started') { - linkBlock = ( - - ); } else if (this.props.state == 'done') { linkBlock = ( diff --git a/js/page/my_files.js b/js/page/my_files.js index b0b510c5d..a26308cf4 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -178,7 +178,7 @@ var MyFilesPage = React.createClass({ seenUris[lbry_uri] = true; - content.push(); } From 8d3e58530681d1fea1ece09036614c9990cc038e Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 02:35:14 -0500 Subject: [PATCH 34/93] When user chooses Open, open files instead of revealing --- js/component/link.js | 2 +- js/lbry.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 3d4b54f6c..28f348d24 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -251,7 +251,7 @@ export let DownloadLink = React.createClass({ if (this.props.state == 'not-started') { this.tryDownload(); } else if (this.props.state == 'done') { - lbry.revealFile(this.props.path); + lbry.openFile(this.props.path); } }, render: function() { diff --git a/js/lbry.js b/js/lbry.js index 9563d4195..c53b00f28 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -272,8 +272,12 @@ lbry.deleteFile = function(name, deleteTargetFile=true, callback) { }, callback); } +lbry.openFile = function(path, callback) { + lbry.call('open_file', {path: path}, callback); +} + lbry.revealFile = function(path, callback) { - lbry.call('reveal', { path: path }, callback); + lbry.call('reveal', {path: path}, callback); } lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) { From e1ec3bb9797193ef96950428862e8fb2b430063f Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 02:45:23 -0500 Subject: [PATCH 35/93] Fix nsfw check in FileTile --- js/component/file-tile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 208de9f9b..2477e0841 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -128,13 +128,12 @@ let FileTile = React.createClass({ this._isMounted = false; }, render: function() { - // Can't render until we know whether we own the file and if we have a local copy if (this.state.isMine === null || this.state.local === null || (this.props.hideOnRemove && this.state.removeConfirmed)) { return null; } - const obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; + const obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.metadata.nsfw; let downloadLinkExtraProps = {}; if (this.state.fileInfo === null) { From 95675cd80235847623537c910c6c8bca6b5a0925 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 11 Jan 2017 14:33:42 -0500 Subject: [PATCH 36/93] Fix clamp.js import --- js/component/common.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/component/common.js b/js/component/common.js index 82ea34bc4..0c01e7293 100644 --- a/js/component/common.js +++ b/js/component/common.js @@ -1,6 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; -import $clamp from 'clamp'; +import $clamp from 'clamp-js'; //component/icon.js export let Icon = React.createClass({ diff --git a/package.json b/package.json index e8868f571..b36fac6f7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "babel-cli": "^6.11.4", "babel-preset-es2015": "^6.13.2", "babel-preset-react": "^6.11.1", - "clamp": "^1.0.1", + "clamp-js": "^0.7.0", "mediaelement": "^2.23.4", "node-sass": "^3.8.0", "react": "^15.4.0", From b7f23aa0dd03bb1d84a8eb4b29e798a4cc3e3fe2 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 12 Jan 2017 21:03:34 -0500 Subject: [PATCH 37/93] big refactor of file actions/buttons/tiles --- js/component/common.js | 2 +- js/component/file-actions.js | 229 +++++++++++++++++++++++++ js/component/file-tile.js | 227 +++++++++++-------------- js/component/link.js | 266 ------------------------------ js/component/menu.js | 85 ++++++---- js/lbry.js | 53 +++++- js/page/discover.js | 75 ++------- js/page/my_files.js | 5 +- js/page/show.js | 8 +- scss/_canvas.scss | 21 +-- scss/_global.scss | 5 +- scss/_gui.scss | 108 +++--------- scss/all.scss | 5 +- scss/component/_file-actions.scss | 29 ++++ scss/component/_file-tile.scss | 27 +++ scss/component/_menu.scss | 21 +++ scss/{ => component}/_table.scss | 2 + 17 files changed, 563 insertions(+), 605 deletions(-) create mode 100644 js/component/file-actions.js create mode 100644 scss/component/_file-actions.scss create mode 100644 scss/component/_file-tile.scss create mode 100644 scss/component/_menu.scss rename scss/{ => component}/_table.scss (97%) diff --git a/js/component/common.js b/js/component/common.js index 0c01e7293..40fb0ce7e 100644 --- a/js/component/common.js +++ b/js/component/common.js @@ -152,6 +152,6 @@ export let Thumbnail = React.createClass({ this._isMounted = false; }, render: function() { - return + return }, }); diff --git a/js/component/file-actions.js b/js/component/file-actions.js new file mode 100644 index 000000000..0a347bdbc --- /dev/null +++ b/js/component/file-actions.js @@ -0,0 +1,229 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import {Link} from '../component/link.js'; +import {Icon} from '../component/common.js'; +import Modal from './modal.js'; +import FormField from './form.js'; +import {DropDownMenu, DropDownMenuItem} from './menu.js'; + +let WatchLink = React.createClass({ + propTypes: { + streamName: React.PropTypes.string, + }, + handleClick: function() { + this.setState({ + loading: true, + }) + lbry.getCostInfoForName(this.props.streamName, ({cost}) => { + lbry.getBalance((balance) => { + if (cost > balance) { + this.setState({ + modal: 'notEnoughCredits', + loading: false, + }); + } else { + window.location = '?watch=' + this.props.streamName; + } + }); + }); + }, + getInitialState: function() { + return { + modal: null, + loading: false, + }; + }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, + render: function() { + return ( +

+ + + You don't have enough LBRY credits to pay for this stream. + +
+ ); + } +}); + +export let FileActions = React.createClass({ + _isMounted: false, + _fileInfoSubscribeId: null, + + propTypes: { + streamName: React.PropTypes.string, + sdHash: React.PropTypes.string, + metadata: React.PropTypes.object, + path: React.PropTypes.string, + hidden: React.PropTypes.bool, + onRemoveConfirmed: React.PropTypes.func, + deleteChecked: React.PropTypes.bool, + }, + getInitialState: function() { + return { + fileInfo: null, + modal: null, + menuOpen: false, + deleteChecked: false, + attemptingDownload: false, + attemptingRemove: false, + } + }, + onFileInfoUpdate: function(fileInfo) { + if (this._isMounted) { + this.setState({ + fileInfo: fileInfo ? fileInfo : false, + attemptingDownload: fileInfo ? false : this.state.attemptingDownload + }); + } + }, + tryDownload: function() { + this.setState({ + attemptingDownload: true, + attemptingRemove: false + }); + lbry.getCostInfoForName(this.props.streamName, ({cost}) => { + lbry.getBalance((balance) => { + if (cost > balance) { + this.setState({ + modal: 'notEnoughCredits', + attemptingDownload: false, + }); + } else { + lbry.getStream(this.props.streamName, (streamInfo) => { + if (streamInfo === null || typeof streamInfo !== 'object') { + this.setState({ + modal: 'timedOut', + attemptingDownload: false, + }); + } + }); + } + }); + }); + }, + closeModal: function() { + this.setState({ + modal: null, + }) + }, + onDownloadClick: function() { + if (!this.state.fileInfo && !this.state.attemptingDownload) { + this.tryDownload(); + } + }, + onOpenClick: function() { + if (this.state.fileInfo && this.state.fileInfo.completed) { + lbry.openFile(this.state.fileInfo.download_path); + } + }, + handleDeleteCheckboxClicked: function(event) { + this.setState({ + deleteChecked: event.target.checked, + }); + }, + handleRevealClicked: function() { + if (this.state.fileInfo && this.state.fileInfo.download_path) { + lbry.revealFile(this.state.fileInfo.download_path); + } + }, + handleRemoveClicked: function() { + this.setState({ + modal: 'confirmRemove', + }); + }, + handleRemoveConfirmed: function() { + lbry.deleteFile(this.props.sdHash || this.props.streamName, this.state.deleteChecked); + if (this.props.onRemoveConfirmed) { + this.props.onRemoveConfirmed(); + } + this.setState({ + modal: null, + fileInfo: false, + attemptingRemove: true, + attemptingDownload: false + }); + }, + openMenu: function() { + this.setState({ + menuOpen: !this.state.menuOpen, + }); + }, + componentDidMount: function() { + this._isMounted = true; + + if ('sdHash' in this.props) { + alert('render by sd hash is broken'); + lbry.fileInfoSubscribeByStreamHash(this.props.sdHash, this.fileInfoU); + } else if ('streamName' in this.props) { + this._fileInfoSubscribeId = lbry.fileInfoSubscribeByName(this.props.streamName, this.onFileInfoUpdate); + } else { + throw new Error("No stream name or sd hash passed to FileTile"); + } + }, + componentWillUnmount: function() { + this._isMounted = false; + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.name, this._fileInfoSubscribeId); + } + }, + render: function() { + if (this.state.fileInfo === null) + { + return
; + } + const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', + showMenu = !this.state.attemptingRemove && this.state.fileInfo !== null; + + let linkBlock; + if (this.state.attemptingRemove || (this.state.fileInfo === false && !this.state.attemptingDownload)) { + linkBlock = ; + } else if (this.state.attemptingDownload || !this.state.fileInfo.completed) { + const + progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0, + label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', + labelWithIcon = {label}; + + linkBlock = +
+
{labelWithIcon}
+ {labelWithIcon} +
; + } else { + linkBlock = ; + } + + return ( +
+ {this.props.metadata.content_type.startsWith('video/') ? : null} + {this.state.fileInfo !== null || this.state.fileInfo.isMine ? +
{linkBlock}
+ : null} + { showMenu ? + + + + : '' } + + You don't have enough LBRY credits to pay for this stream. + + + LBRY was unable to download the stream lbry://{this.props.streamName}. + + +

Are you sure you'd like to remove {this.props.metadata.title} from LBRY?

+ + +
+
+ ); + } +}); \ No newline at end of file diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 2477e0841..293a63c9c 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -1,188 +1,149 @@ import React from 'react'; import lbry from '../lbry.js'; -import {Link, DownloadLink, WatchLink} from '../component/link.js'; +import {Link} from '../component/link.js'; +import {FileActions} from '../component/file-actions.js'; import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; -let FileTile = React.createClass({ +let FilePrice = React.createClass({ _isMounted: false, - _fileInfoCheckInterval: 5000, propTypes: { - metadata: React.PropTypes.object.isRequired, - fileInfo: React.PropTypes.string, - name: React.PropTypes.string, - sdHash: React.PropTypes.string, - available: React.PropTypes.bool, - isMine: React.PropTypes.bool, - local: React.PropTypes.bool, - cost: React.PropTypes.number, - costIncludesData: React.PropTypes.bool, - hideOnRemove: React.PropTypes.bool, + name: React.PropTypes.string }, - updateFileInfo: function(progress=null) { - const updateFileInfoCallback = ((fileInfo) => { - if (!this._isMounted || 'fileInfo' in this.props) { - /** - * The component was unmounted, or a file info data structure has now been provided by the - * containing component. - */ - return; - } - this.setState({ - fileInfo: fileInfo || null, - local: !!fileInfo, - }); - - setTimeout(() => { this.updateFileInfo() }, this._fileInfoCheckInterval); - }); - - if ('sdHash' in this.props) { - lbry.getFileInfoBySdHash(this.props.sdHash, updateFileInfoCallback); - this.getIsMineIfNeeded(this.props.sdHash); - } else if ('name' in this.props) { - lbry.getFileInfoByName(this.props.name, (fileInfo) => { - this.getIsMineIfNeeded(fileInfo.sd_hash); - - updateFileInfoCallback(fileInfo); - }); - } else { - throw new Error("No progress, stream name or sd hash passed to FileTile"); - } - }, - getIsMineIfNeeded: function(sdHash) { - if (this.state.isMine !== null) { - // The info was already provided by this.props.isMine - return; - } - - lbry.getMyClaims((claimsInfo) => { - for (let {value} of claimsInfo) { - if (JSON.parse(value).sources.lbry_sd_hash == sdHash) { - this.setState({ - isMine: true, - }); - return; - } - } - - this.setState({ - isMine: false, - }); - }); - }, getInitialState: function() { return { - downloading: false, - removeConfirmed: false, - isHovered: false, cost: null, costIncludesData: null, - fileInfo: 'fileInfo' in this.props ? this.props.fileInfo : null, - isMine: 'isMine' in this.props ? this.props.isMine : null, - local: 'local' in this.props ? this.props.local : null, } }, - getDefaultProps: function() { - return { - compact: false, - hideOnRemove: false, - } - }, - handleMouseOver: function() { - this.setState({ - isHovered: true, - }); - }, - handleMouseOut: function() { - this.setState({ - isHovered: false, - }); - }, - handleRemoveConfirmed: function() { - this.setState({ - removeConfirmed: true, - }); - }, - componentWillMount: function() { - this.updateFileInfo(); - if ('cost' in this.props) { - this.setState({ - cost: this.props.cost, - costIncludesData: this.props.costIncludesData, - }); - } else { - lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => { + componentDidMount: function() { + this._isMounted = true; + + lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => { + if (this._isMounted) { this.setState({ cost: cost, costIncludesData: includesData, }); + } + }); + }, + + componentWillUnmount: function() { + this._isMounted = false; + }, + + render: function() { + if (this.state.cost === null) + { + return null; + } + + return ( + + + + ); + } +}); + +let FileTile = React.createClass({ + _isMounted: false, + + propTypes: { + name: React.PropTypes.string, + sdHash: React.PropTypes.string, + showPrice: React.PropTypes.bool, + obscureNsfw: React.PropTypes.bool, + hideOnRemove: React.PropTypes.bool + }, + + getInitialState: function() { + return { + metadata: null, + title: null, + showNsfwHelp: false, + isRemoved: false + } + }, + getDefaultProps: function() { + return { + hideOnRemove: false, + obscureNsfw: !lbry.getClientSetting('showNsfw'), + showPrice: true + } + }, + handleMouseOver: function() { + if (this.props.obscureNsfw && this.state.metadata && this.state.metadata.nsfw) { + this.setState({ + showNsfwHelp: true, }); } }, + handleMouseOut: function() { + if (this.state.showNsfwHelp) { + this.setState({ + showNsfwHelp: false, + }); + } + }, + onRemove: function() { + this.setState({ + isRemoved: true, + }); + }, componentDidMount: function() { this._isMounted = true; + + lbry.resolveName(this.props.name, (metadata) => { + if (this._isMounted) { + this.setState({ + metadata: metadata, + title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name), + }); + } + }); }, componentWillUnmount: function() { this._isMounted = false; }, render: function() { - if (this.state.isMine === null || this.state.local === null || - (this.props.hideOnRemove && this.state.removeConfirmed)) { + if (this.state.metadata === null || (this.props.hideOnRemove && this.state.isRemoved)) { return null; } - const obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.metadata.nsfw; + const obscureNsfw = this.props.obscureNsfw && this.state.metadata.nsfw; - let downloadLinkExtraProps = {}; - if (this.state.fileInfo === null) { - downloadLinkExtraProps.state = 'not-started'; - } else if (!this.state.fileInfo.completed) { - downloadLinkExtraProps.state = 'downloading'; - - const {written_bytes, total_bytes, path} = this.state.fileInfo; - downloadLinkExtraProps.progress = written_bytes / total_bytes; - } else { - downloadLinkExtraProps.state = 'done'; - downloadLinkExtraProps.path = this.state.fileInfo.download_path; - } return ( -
+
- +
- {this.state.cost !== null && !this.state.local - ? - - + { this.props.showPrice + ? : null} -

+

- - {this.props.metadata.title} + + {this.state.metadata.title}

-
- {this.props.metadata.content_type.startsWith('video/') ? : null} - {!this.props.isMine - ? - : null} -
+

- {this.props.metadata.description} + {this.state.metadata.description}

- {obscureNsfw && this.state.isHovered + {this.state.showNsfwHelp ?

This content is Not Safe For Work. diff --git a/js/component/link.js b/js/component/link.js index 28f348d24..b0d7f53ec 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -1,11 +1,6 @@ import React from 'react'; -import lbry from '../lbry.js'; -import FormField from './form.js'; -import Modal from './modal.js'; -import {Menu, MenuItem} from './menu.js'; import {Icon, ToolTip} from './common.js'; - export let Link = React.createClass({ propTypes: { label: React.PropTypes.string, @@ -110,264 +105,3 @@ export let ToolTipLink = React.createClass({ ); } }); - -export let DropDown = React.createClass({ - propTypes: { - onCaretClick: React.PropTypes.func, - }, - handleCaretClicked: function(event) { - /** - * The menu handles caret clicks via a window event listener, so we just need to prevent clicks - * on the caret from bubbling up to the link - */ - this.setState({ - menuOpen: !this.state.menuOpen, - }); - event.stopPropagation(); - return false; - }, - closeMenu: function(event) { - this.setState({ - menuOpen: false, - }); - }, - getInitialState: function() { - return { - menuOpen: false, - }; - }, - render: function() { - const {onCaretClick, ...other} = this.props; - return ( -

- - {this.props.label} - - - {this.state.menuOpen - ? - {this.props.children} - - : null} -
- ); - } -}); - -export let DownloadLink = React.createClass({ - propTypes: { - type: React.PropTypes.string, - streamName: React.PropTypes.string, - sdHash: React.PropTypes.string, - metadata: React.PropTypes.object, - label: React.PropTypes.string, - button: React.PropTypes.string, - state: React.PropTypes.oneOf(['not-started', 'downloading', 'done']), - progress: React.PropTypes.number, - path: React.PropTypes.string, - hidden: React.PropTypes.bool, - deleteChecked: React.PropTypes.bool, - onRemoveConfirmed: React.PropTypes.func, - }, - tryDownload: function() { - this.setState({ - attemptingDownload: true, - }); - lbry.getCostInfoForName(this.props.streamName, ({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - attemptingDownload: false, - }); - } else { - lbry.getStream(this.props.streamName, (streamInfo) => { - if (streamInfo === null || typeof streamInfo !== 'object') { - this.setState({ - modal: 'timedOut', - attemptingDownload: false, - }); - } else { - this.setState({ - filePath: streamInfo.path, - attemptingDownload: false, - }); - } - }); - } - }); - }); - }, - openMenu: function() { - this.setState({ - menuOpen: !this.state.menuOpen, - }); - }, - handleDeleteCheckboxClicked: function(event) { - this.setState({ - deleteChecked: event.target.checked, - }); - }, - handleRevealClicked: function() { - lbry.revealFile(this.props.path); - }, - handleRemoveClicked: function() { - this.setState({ - modal: 'confirmRemove', - }); - }, - handleRemoveConfirmed: function() { - lbry.deleteFile(this.props.sdHash || this.props.streamName, this.state.deleteChecked); - if (this.props.onRemoveConfirmed) { - this.props.onRemoveConfirmed(); - } - this.setState({ - modal: null, - attemptingRemove: true, - }); - }, - getDefaultProps: function() { - return { - state: 'not-started', - hideOnDelete: false, - } - }, - getInitialState: function() { - return { - filePath: null, - modal: null, - menuOpen: false, - deleteChecked: false, - attemptingDownload: false, - attemptingRemove: false, - } - }, - closeModal: function() { - this.setState({ - modal: null, - }) - }, - handleClick: function() { - if (this.props.state == 'not-started') { - this.tryDownload(); - } else if (this.props.state == 'done') { - lbry.openFile(this.props.path); - } - }, - render: function() { - const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder'; - - const dropDownItems = [ - , - , - ]; - - let linkBlock; - if (this.state.attemptingRemove || this.props.state == 'not-started') { - linkBlock = ; - } else if (this.state.attemptingDownload) { - linkBlock = - } else if (this.props.state == 'downloading') { - const label = `${parseInt(this.props.progress * 100)}% complete`; - linkBlock = ( - - - {dropDownItems} - - - {dropDownItems} - - - ); - } else if (this.props.state == 'done') { - linkBlock = ( - - {dropDownItems} - - ); - } else { - throw new Error(`Unknown download state ${this.props.state} passed to DownloadLink`); - } - - return ( - - {linkBlock} - - You don't have enough LBRY credits to pay for this stream. - - - LBRY was unable to download the stream lbry://{this.props.streamName}. - - -

Are you sure you'd like to remove {this.props.metadata.title} from LBRY?

- - -
-
- ); - } -}); - -export let WatchLink = React.createClass({ - propTypes: { - type: React.PropTypes.string, - streamName: React.PropTypes.string, - label: React.PropTypes.string, - button: React.PropTypes.string, - hidden: React.PropTypes.bool, - }, - handleClick: function() { - this.setState({ - loading: true, - }) - lbry.getCostInfoForName(this.props.streamName, ({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - loading: false, - }); - } else { - window.location = '?watch=' + this.props.streamName; - } - }); - }); - }, - getInitialState: function() { - return { - modal: null, - loading: false, - }; - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - getDefaultProps: function() { - return { - icon: 'icon-play', - label: 'Watch', - } - }, - render: function() { - return ( -
- - - You don't have enough LBRY credits to pay for this stream. - -
- ); - } -}); diff --git a/js/component/menu.js b/js/component/menu.js index cf76ec047..618f3165c 100644 --- a/js/component/menu.js +++ b/js/component/menu.js @@ -1,35 +1,8 @@ import React from 'react'; -import ReactDOM from 'react-dom'; import {Icon} from './common.js'; +import {Link} from '../component/link.js'; -export let Menu = React.createClass({ - propTypes: { - onClickOut: React.PropTypes.func.isRequired, - }, - handleWindowClick: function(e) { - if (!this._div.contains(e.target)) { - // Menu is open and user clicked outside of it - this.props.onClickOut(); - } - }, - componentDidMount: function() { - window.addEventListener('click', this.handleWindowClick, false); - }, - componentWillUnmount: function() { - window.removeEventListener('click', this.handleWindowClick, false); - }, - render: function() { - const {onClickOut, ...other} = this.props; - return ( -
this._div = div} className={'menu ' + (this.props.className || '')} - {... other}> - {this.props.children} -
- ); - } -}); - -export let MenuItem = React.createClass({ +export let DropDownMenuItem = React.createClass({ propTypes: { href: React.PropTypes.string, label: React.PropTypes.string, @@ -45,7 +18,7 @@ export let MenuItem = React.createClass({ var icon = (this.props.icon ? : null); return ( - {this.props.iconPosition == 'left' ? icon : null} {this.props.label} @@ -54,3 +27,55 @@ export let MenuItem = React.createClass({ ); } }); + +export let DropDownMenu = React.createClass({ + _isWindowClickBound: false, + _menuDiv: null, + + getInitialState: function() { + return { + menuOpen: false, + }; + }, + componentWillUnmount: function() { + if (this._isWindowClickBound) { + window.removeEventListener('click', this.handleWindowClick, false); + } + }, + onMenuIconClick: function() { + this.setState({ + menuOpen: !this.state.menuOpen, + }); + if (!this.state.menuOpen && !this._isWindowClickBound) { + this._isWindowClickBound = true; + window.addEventListener('click', this.handleWindowClick, false); + } + return false; + }, + handleWindowClick: function(e) { + if (this.state.menuOpen && + (!this._menuDiv || !this._menuDiv.contains(e.target))) { + console.log('menu closing disabled due to auto close on click, fix me'); + return; + this.setState({ + menuOpen: false + }); + } + }, + render: function() { + if (!this.state.menuOpen && this._isWindowClickBound) { + this._isWindowClickBound = false; + window.removeEventListener('click', this.handleWindowClick, false); + } + return ( +
+ this._menuButton = span} icon="icon-ellipsis-v" onClick={this.onMenuIconClick} /> + {this.state.menuOpen + ?
this._menuDiv = div} className="menu"> + {this.props.children} +
+ : null} +
+ ); + } +}); \ No newline at end of file diff --git a/js/lbry.js b/js/lbry.js index c53b00f28..49abcaa95 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -132,7 +132,7 @@ lbry.getNewAddress = function(callback) { lbry.call('get_new_address', {}, callback); } -lbry.checkAddressIsMine = function(address, callback) { +lbry.checkAddressIsMine = function(address, callback) { lbry.call('address_is_mine', {address: address}, callback); } @@ -457,5 +457,56 @@ lbry.stop = function(callback) { lbry.call('stop', {}, callback); }; +lbry.fileInfo = {}; +lbry._fileInfoSubscribeIdCounter = 0; +lbry._fileInfoSubscribeCallbacks = {}; +lbry._fileInfoSubscribeInterval = 5000; +lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not + + +lbry._updateClaimOwnershipCache = function(claimId) { + lbry.getMyClaims((claimsInfo) => { + lbry._claimIdOwnershipCache[claimId] = !!claimsInfo.reduce(function(match, claimInfo) { + return match || claimInfo.claim_id == claimId; + }); + }); +}; + +lbry._updateSubscribedFileInfoByName = function(name) { + lbry.getFileInfoByName(name, (fileInfo) => { + if (fileInfo) { + if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { + lbry._updateClaimOwnershipCache(fileInfo.claim_id); + } + fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; + } + this._fileInfoSubscribeCallbacks[name].forEach(function(callback) { + callback(fileInfo); + }); + }); + setTimeout(() => { this._updateSubscribedFileInfoByName(name) }, lbry._fileInfoSubscribeInterval); +} + +lbry.fileInfoSubscribeByName = function(name, callback) { + if (!lbry._fileInfoSubscribeCallbacks[name]) + { + lbry._fileInfoSubscribeCallbacks[name] = []; + } + + const subscribeId = ++lbry._fileInfoSubscribeIdCounter; + lbry._fileInfoSubscribeCallbacks[name][subscribeId] = callback; + lbry._updateSubscribedFileInfoByName(name); + return subscribeId; +} + +// lbry.fileInfoSubscribeByStreamHash = function(sdHash, callback) { +// lbry.getFileInfoBySdHash(this.props.sdHash, this.updateFileInfoCallback); +// this.getIsMineIfNeeded(this.props.sdHash); +// setTimeout(() => { this.updateFileInfo() }, this._fileInfoCheckInterval); +// } + +lbry.fileInfoUnsubscribe = function(name, subscribeId) { + delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; +} export default lbry; diff --git a/js/page/discover.js b/js/page/discover.js index 3c79d96c5..873e36f5f 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -2,8 +2,8 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; import FileTile from '../component/file-tile.js'; -import {Link, ToolTipLink, DownloadLink, WatchLink} from '../component/link.js'; -import {Thumbnail, CreditAmount, TruncatedText, BusyMessage} from '../component/common.js'; +import {Link, ToolTipLink} from '../component/link.js'; +import {BusyMessage} from '../component/common.js'; var fetchResultsStyle = { color: '#888', @@ -53,57 +53,6 @@ var SearchResults = React.createClass({ } }); -var featuredContentItemContainerStyle = { - position: 'relative', -}; - -var FeaturedContentItem = React.createClass({ - resolveSearch: false, - - propTypes: { - name: React.PropTypes.string, - }, - - getInitialState: function() { - return { - metadata: null, - title: null, - cost: null, - overlayShowing: false, - }; - }, - - componentWillUnmount: function() { - this.resolveSearch = false; - }, - - componentDidMount: function() { - this._isMounted = true; - - lbry.resolveName(this.props.name, (metadata) => { - if (!this._isMounted) { - return; - } - - this.setState({ - metadata: metadata, - title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name), - }); - }); - }, - - render: function() { - if (this.state.metadata === null) { - // Still waiting for metadata, skip render - return null; - } - - return (
- -
); - } -}); - var featuredContentLegendStyle = { fontSize: '12px', color: '#aaa', @@ -116,21 +65,21 @@ var FeaturedContent = React.createClass({

Featured Content

- - - - - + + + + +

Community Content

- - - - - + + + + +
); diff --git a/js/page/my_files.js b/js/page/my_files.js index a26308cf4..d3f488eab 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -178,7 +178,7 @@ var MyFilesPage = React.createClass({ seenUris[lbry_uri] = true; - content.push(); } @@ -199,5 +199,4 @@ var MyFilesPage = React.createClass({ } }); - -export default MyFilesPage; +export default MyFilesPage; \ No newline at end of file diff --git a/js/page/show.js b/js/page/show.js index 7486b0c93..0bd1dde57 100644 --- a/js/page/show.js +++ b/js/page/show.js @@ -2,7 +2,8 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; import {CreditAmount, Thumbnail} from '../component/common.js'; -import {Link, DownloadLink, WatchLink} from '../component/link.js'; +import {FileActions} from '../component/file-actions.js'; +import {Link} from '../component/link.js'; var formatItemImgStyle = { maxWidth: '100%', @@ -62,10 +63,7 @@ var FormatItem = React.createClass({
-
- {mediaType == 'video' ? : null} - -
+
diff --git a/scss/_canvas.scss b/scss/_canvas.scss index fd24a2236..7a6184a95 100644 --- a/scss/_canvas.scss +++ b/scss/_canvas.scss @@ -56,7 +56,7 @@ $drawer-width: 240px; #drawer-handle { padding: $spacing-vertical / 2; - max-height: $header-height - $spacing-vertical; + max-height: $height-header - $spacing-vertical; text-align: center; } @@ -76,10 +76,10 @@ $drawer-width: 240px; background: $color-primary; color: white; &.header-no-subnav { - height: $header-height; + height: $height-header; } &.header-with-subnav { - height: $header-height * 2; + height: $height-header * 2; } position: fixed; top: 0; @@ -87,7 +87,7 @@ $drawer-width: 240px; width: 100%; z-index: 2; box-sizing: border-box; - h1 { font-size: 1.8em; line-height: $header-height - $spacing-vertical; display: inline-block; float: left; } + h1 { font-size: 1.8em; line-height: $height-header - $spacing-vertical; display: inline-block; float: left; } &.header-scrolled { box-shadow: $default-box-shadow; @@ -120,7 +120,7 @@ nav.sub-header display: inline-block; margin: 0 15px; padding: 0 5px; - line-height: $header-height - $spacing-vertical - $sub-header-selected-underline-height; + line-height: $height-header - $spacing-vertical - $sub-header-selected-underline-height; color: #e8e8e8; &:first-child { @@ -147,13 +147,13 @@ nav.sub-header background: $color-canvas; &.no-sub-nav { - min-height: calc(100vh - 60px); //should be -$header-height, but I'm dumb I guess? It wouldn't work - main { margin-top: $header-height; } + min-height: calc(100vh - 60px); //should be -$height-header, but I'm dumb I guess? It wouldn't work + main { margin-top: $height-header; } } &.with-sub-nav { - min-height: calc(100vh - 120px); //should be -$header-height, but I'm dumb I guess? It wouldn't work - main { margin-top: $header-height * 2; } + min-height: calc(100vh - 120px); //should be -$height-header, but I'm dumb I guess? It wouldn't work + main { margin-top: $height-header * 2; } } main { @@ -206,9 +206,6 @@ $header-icon-size: 1.5em; box-shadow: $default-box-shadow; border-radius: 2px; } -.card-compact { - padding: 22px; -} .card-obscured { position: relative; diff --git a/scss/_global.scss b/scss/_global.scss index 50b6c44c8..b59b5821f 100644 --- a/scss/_global.scss +++ b/scss/_global.scss @@ -2,6 +2,8 @@ $spacing-vertical: 24px; +$padding-button: 12px; + $color-primary: #155B4A; $color-light-alt: hsl(hue($color-primary), 15, 85); $color-text-dark: #000; @@ -18,7 +20,8 @@ $mobile-width-threshold: 801px; $max-content-width: 1000px; $max-text-width: 660px; -$header-height: $spacing-vertical * 2.5; +$height-header: $spacing-vertical * 2.5; +$height-button: $spacing-vertical * 1.5; $default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); diff --git a/scss/_gui.scss b/scss/_gui.scss index cf5f72de0..137c60b24 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -1,6 +1,6 @@ @import "global"; -@mixin text-link($color: $color-primary, $hover-opacity: 0.70, $mirror: false) { +@mixin text-link($color: $color-primary, $hover-opacity: 0.70) { color: $color; .icon { @@ -28,18 +28,7 @@ } } - @if $mirror == false { - color: $color; - } - @else { - color: $color-bg; - background-color: $color; - position: absolute; - white-space: nowrap; - overflow: hidden; - top: 0px; - left: 0px; - } + color: $color; } .icon-fixed-width { @@ -156,16 +145,15 @@ input[type="text"], input[type="search"] + .button-container { - margin-left: 12px; + margin-left: $padding-button; } } -.button-block +.button-block, .faux-button-block { - cursor: pointer; display: inline-block; - height: $spacing-vertical * 1.5; - line-height: $spacing-vertical * 1.5; + height: $height-button; + line-height: $height-button; text-decoration: none; border: 0 none; text-align: center; @@ -184,37 +172,28 @@ input[type="text"], input[type="search"] padding-left: 5px; } } +.button-block +{ + cursor: pointer; +} .button-primary { color: white; background-color: $color-primary; box-shadow: $default-box-shadow; - padding: 0 12px; + padding: 0 $padding-button; } .button-alt { background-color: $color-bg-alt; box-shadow: $default-box-shadow; - padding: 0 12px; + padding: 0 $padding-button; } -.button-download -{ - padding: 0 6px; - - text-decoration: none !important; - - &.button-download--bg { - @include text-link(darken($color-primary, 1%)); - } - &.button-download--fg { - @include text-link(darken($color-primary, 1%), $mirror: true); - } -} .button-cancel { - padding: 0 12px; + padding: 0 $padding-button; } .button-text { @@ -378,11 +357,6 @@ input[type="text"], input[type="search"] background: rgba(#000, .88); } -.error-modal { - max-width: none; - width: 400px; -} - .error-modal__content { display: flex; padding: 0px 8px 10px 10px; @@ -397,56 +371,12 @@ input[type="text"], input[type="search"] word-break: break-all; } -.menu { - position: fixed; - white-space: nowrap; - background-color: $color-bg-alt; - box-shadow: $default-box-shadow; - padding: $spacing-vertical; - border-radius: 2px; + +.error-modal { + max-width: none; + width: 400px; } - -.menu__menu-item { - display: block; - text-decoration: none !important; - &:hover { - text-decoration: underline !important; - } -} - - -.file-tile--compact { - height: 180px; -} - -.file-tile__row { - height: 24px * 7; +.error-modal__error-list { /*shitty hack/temp fix for long errors making modals unusable*/ + max-height: 400px; overflow-y: hidden; } - -.file-tile__thumbnail { - max-width: 100%; - max-height: 24px * 7; - display: block; - margin-left: auto; - margin-right: auto; -} - -.file-tile__title { - font-weight: bold; -} - -.file-tile__title--compact { - font-size: 1.25em; - line-height: 1.15; -} - -.file-tile__cost { - float: right; -} - -.file-tile__description { - color: #444; - margin-top: 12px; - font-size: 0.9em; -} diff --git a/scss/all.scss b/scss/all.scss index e02fd6d3d..4055cc708 100644 --- a/scss/all.scss +++ b/scss/all.scss @@ -3,6 +3,9 @@ @import "_icons"; @import "_mediaelement"; @import "_canvas"; -@import "_table"; @import "_gui"; +@import "component/_table"; +@import "component/_file-actions.scss"; +@import "component/_file-tile.scss"; +@import "component/_menu.scss"; @import "page/_developer.scss"; \ No newline at end of file diff --git a/scss/component/_file-actions.scss b/scss/component/_file-actions.scss new file mode 100644 index 000000000..e17ed80d3 --- /dev/null +++ b/scss/component/_file-actions.scss @@ -0,0 +1,29 @@ +@import "../global"; + +$color-download: #444; + +.file-actions--stub +{ + height: $height-button; +} + +.file-actions__download-status-bar +{ + padding-right: $padding-button; + padding-left: $padding-button; + position: relative; + color: $color-download; +} +.file-actions__download-status-bar-overlay +{ + padding-right: $padding-button; + padding-left: $padding-button; + background: $color-download; + color: white; + position: absolute; + white-space: nowrap; + overflow: hidden; + z-index: 1; + top: 0px; + left: 0px; +} \ No newline at end of file diff --git a/scss/component/_file-tile.scss b/scss/component/_file-tile.scss new file mode 100644 index 000000000..11a071232 --- /dev/null +++ b/scss/component/_file-tile.scss @@ -0,0 +1,27 @@ +@import "../global"; + +.file-tile__row { + height: $spacing-vertical * 7; +} + +.file-tile__thumbnail { + max-width: 100%; + max-height: $spacing-vertical * 7; + display: block; + margin-left: auto; + margin-right: auto; +} + +.file-tile__title { + font-weight: bold; +} + +.file-tile__cost { + float: right; +} + +.file-tile__description { + color: #444; + margin-top: 12px; + font-size: 0.9em; +} \ No newline at end of file diff --git a/scss/component/_menu.scss b/scss/component/_menu.scss new file mode 100644 index 000000000..d46926bba --- /dev/null +++ b/scss/component/_menu.scss @@ -0,0 +1,21 @@ +@import "../global"; + +$border-radius-menu: 2px; + +.menu { + position: absolute; + white-space: nowrap; + background-color: white; + box-shadow: $default-box-shadow; + border-radius: $border-radius-menu; + padding-top: $spacing-vertical / 2; + padding-bottom: $spacing-vertical / 2; +} + +.menu__menu-item { + display: block; + padding: $spacing-vertical / 4 $spacing-vertical / 2; + &:hover { + background: $color-bg-alt; + } +} \ No newline at end of file diff --git a/scss/_table.scss b/scss/component/_table.scss similarity index 97% rename from scss/_table.scss rename to scss/component/_table.scss index 899010d60..9d60cf6e8 100644 --- a/scss/_table.scss +++ b/scss/component/_table.scss @@ -1,3 +1,5 @@ +@import "../global"; + table.table-standard { word-wrap: break-word; max-width: 100%; From e0eb36d0321f503751972706d0a038d2342fd9a1 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 12 Jan 2017 22:23:12 -0500 Subject: [PATCH 38/93] more file tile refactor and fix file info unsubscribe --- js/component/common.js | 2 +- js/component/file-actions.js | 2 +- js/component/file-tile.js | 90 ++++++++++++++++++++++-------------- js/lbry.js | 2 +- js/page/discover.js | 2 +- 5 files changed, 60 insertions(+), 38 deletions(-) diff --git a/js/component/common.js b/js/component/common.js index 40fb0ce7e..a342074c1 100644 --- a/js/component/common.js +++ b/js/component/common.js @@ -124,7 +124,7 @@ export let Thumbnail = React.createClass({ _isMounted: false, propTypes: { - src: React.PropTypes.string.isRequired, + src: React.PropTypes.string, }, handleError: function() { if (this.state.imageUrl != this._defaultImageUri) { diff --git a/js/component/file-actions.js b/js/component/file-actions.js index 0a347bdbc..3d72f31f3 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -168,7 +168,7 @@ export let FileActions = React.createClass({ componentWillUnmount: function() { this._isMounted = false; if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.name, this._fileInfoSubscribeId); + lbry.fileInfoUnsubscribe(this.props.streamName, this._fileInfoSubscribeId); } }, render: function() { diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 293a63c9c..cf0dff4ee 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -49,11 +49,10 @@ let FilePrice = React.createClass({ } }); -let FileTile = React.createClass({ - _isMounted: false, - +/*should be merged into FileTile once FileTile is refactored to take a single id*/ +let FileTileStream = React.createClass({ propTypes: { - name: React.PropTypes.string, + metadata: React.PropTypes.object, sdHash: React.PropTypes.string, showPrice: React.PropTypes.bool, obscureNsfw: React.PropTypes.bool, @@ -62,12 +61,11 @@ let FileTile = React.createClass({ getInitialState: function() { return { - metadata: null, - title: null, showNsfwHelp: false, isRemoved: false } }, + getDefaultProps: function() { return { hideOnRemove: false, @@ -76,7 +74,7 @@ let FileTile = React.createClass({ } }, handleMouseOver: function() { - if (this.props.obscureNsfw && this.state.metadata && this.state.metadata.nsfw) { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { this.setState({ showNsfwHelp: true, }); @@ -94,34 +92,20 @@ let FileTile = React.createClass({ isRemoved: true, }); }, - componentDidMount: function() { - this._isMounted = true; - - lbry.resolveName(this.props.name, (metadata) => { - if (this._isMounted) { - this.setState({ - metadata: metadata, - title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name), - }); - } - }); - }, - componentWillUnmount: function() { - this._isMounted = false; - }, render: function() { - if (this.state.metadata === null || (this.props.hideOnRemove && this.state.isRemoved)) { + if (this.props.metadata === null || (this.props.hideOnRemove && this.state.isRemoved)) { return null; } - const obscureNsfw = this.props.obscureNsfw && this.state.metadata.nsfw; - + const metadata = this.props.metadata || {}, + obscureNsfw = this.props.obscureNsfw && metadata.nsfw, + title = metadata.title ? metadata.title : ('lbry://' + this.props.name); return (
- +
{ this.props.showPrice @@ -131,29 +115,67 @@ let FileTile = React.createClass({

- {this.state.metadata.title} + {title}

- +

- {this.state.metadata.description} + {metadata.description}

{this.state.showNsfwHelp ?
-

- This content is Not Safe For Work. - To view adult content, please change your . -

-
+

+ This content is Not Safe For Work. + To view adult content, please change your . +

+
: null}
); } }); +let FileTile = React.createClass({ + _isMounted: false, + + propTypes: { + name: React.PropTypes.string + }, + + getInitialState: function() { + return { + sdHash: null, + metadata: null + } + }, + + componentDidMount: function() { + this._isMounted = true; + + lbry.resolveName(this.props.name, (metadata) => { + if (this._isMounted) { + this.setState({ + sdHash: metadata.sources.lbry_sd_hash, + metadata: metadata, + }); + } + }); + }, + componentWillUnmount: function() { + this._isMounted = false; + }, + render: function() { + if (this.state.metadata === null || this.state.sdHash === null) { + return null; + } + + return ; + } +}); + export default FileTile; \ No newline at end of file diff --git a/js/lbry.js b/js/lbry.js index 49abcaa95..e826ba023 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -506,7 +506,7 @@ lbry.fileInfoSubscribeByName = function(name, callback) { // } lbry.fileInfoUnsubscribe = function(name, subscribeId) { - delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; + lbry._fileInfoSubscribeCallbacks[name] = lbry._fileInfoSubscribeCallbacks[name].splice(subscribeId, 1); } export default lbry; diff --git a/js/page/discover.js b/js/page/discover.js index 873e36f5f..d627df4d3 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -44,7 +44,7 @@ var SearchResults = React.createClass({ var rows = []; this.props.results.forEach(function({name, value}) { rows.push( - + ); }); return ( From 167541b92cbd56838cf4b33331dfca6f0d880060 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 12 Jan 2017 22:36:03 -0500 Subject: [PATCH 39/93] search bar displays pre-set value when ?discover=arg is passed --- js/app.js | 5 +++-- js/component/header.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/js/app.js b/js/app.js index c87017c28..cab3045e2 100644 --- a/js/app.js +++ b/js/app.js @@ -190,7 +190,8 @@ var App = React.createClass({ }, render: function() { var mainContent = this.getMainContent(), - headerLinks = this.getHeaderLinks(); + headerLinks = this.getHeaderLinks(), + searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : ''; return ( this.state.viewingPage == 'watch' ? @@ -198,7 +199,7 @@ var App = React.createClass({
-
+
{mainContent}

{ this.state.title }

-
@@ -70,7 +70,7 @@ var SubHeader = React.createClass({ render: function() { var links = [], viewingUrl = '?' + this.props.viewingPage; - + for (let link of Object.keys(this.props.links)) { links.push( From 4fd98287608d9a5f456144f5e7fe75233db19456 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 12 Jan 2017 23:05:43 -0500 Subject: [PATCH 40/93] holy fuck javascript sucks --- js/lbry.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/js/lbry.js b/js/lbry.js index e826ba023..d79bb8454 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -480,17 +480,21 @@ lbry._updateSubscribedFileInfoByName = function(name) { } fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; } - this._fileInfoSubscribeCallbacks[name].forEach(function(callback) { - callback(fileInfo); + Object.keys(this._fileInfoSubscribeCallbacks[name]).forEach(function(subscribeId) { + lbry._fileInfoSubscribeCallbacks[name][subscribeId](fileInfo); }); }); - setTimeout(() => { this._updateSubscribedFileInfoByName(name) }, lbry._fileInfoSubscribeInterval); + if (Object.keys(this._fileInfoSubscribeCallbacks[name]).length) { + setTimeout(() => { + this._updateSubscribedFileInfoByName(name) + }, lbry._fileInfoSubscribeInterval); + } } lbry.fileInfoSubscribeByName = function(name, callback) { if (!lbry._fileInfoSubscribeCallbacks[name]) { - lbry._fileInfoSubscribeCallbacks[name] = []; + lbry._fileInfoSubscribeCallbacks[name] = {}; } const subscribeId = ++lbry._fileInfoSubscribeIdCounter; @@ -506,7 +510,7 @@ lbry.fileInfoSubscribeByName = function(name, callback) { // } lbry.fileInfoUnsubscribe = function(name, subscribeId) { - lbry._fileInfoSubscribeCallbacks[name] = lbry._fileInfoSubscribeCallbacks[name].splice(subscribeId, 1); + delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; } -export default lbry; +export default lbry; \ No newline at end of file From 6c67cee3d96e21b30e287baa5c5022f74cbf174d Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 12 Jan 2017 23:12:53 -0500 Subject: [PATCH 41/93] fix duplicate keys on search results --- js/page/discover.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/js/page/discover.js b/js/page/discover.js index d627df4d3..dcbf190e9 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -41,11 +41,15 @@ var SearchNoResults = React.createClass({ var SearchResults = React.createClass({ render: function() { - var rows = []; + var rows = [], + seenNames = {}; //fix this when the search API returns claim IDs this.props.results.forEach(function({name, value}) { - rows.push( - - ); + if (!seenNames[name]) { + seenNames[name] = name; + rows.push( + + ); + } }); return (
{rows}
From 70d390ea2e20e5f30b62611f0c7ffc7d3d2ef257 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 12 Jan 2017 23:15:44 -0500 Subject: [PATCH 42/93] no more overflow-y on tiles, let's cap titles for now --- js/component/file-tile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index cf0dff4ee..a958f4a39 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -114,7 +114,7 @@ let FileTileStream = React.createClass({

- + {title} From b70b2a859dedf14018f7b8c06029c64a45031b21 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Fri, 13 Jan 2017 10:44:23 -0500 Subject: [PATCH 43/93] Fix handling of window click events in DropDownMenu Wasn't doing e.stopPropagation() when mounting the handler for clicks outside the menu. --- js/component/link.js | 4 ++-- js/component/menu.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index b0d7f53ec..b9ce82e18 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -15,9 +15,9 @@ export let Link = React.createClass({ disabled: false, }; }, - handleClick: function() { + handleClick: function(e) { if (this.props.onClick) { - this.props.onClick(); + this.props.onClick(e); } }, render: function() { diff --git a/js/component/menu.js b/js/component/menu.js index 618f3165c..c3c10047f 100644 --- a/js/component/menu.js +++ b/js/component/menu.js @@ -42,21 +42,20 @@ export let DropDownMenu = React.createClass({ window.removeEventListener('click', this.handleWindowClick, false); } }, - onMenuIconClick: function() { + onMenuIconClick: function(e) { this.setState({ menuOpen: !this.state.menuOpen, }); if (!this.state.menuOpen && !this._isWindowClickBound) { this._isWindowClickBound = true; window.addEventListener('click', this.handleWindowClick, false); + e.stopPropagation(); } return false; }, handleWindowClick: function(e) { if (this.state.menuOpen && (!this._menuDiv || !this._menuDiv.contains(e.target))) { - console.log('menu closing disabled due to auto close on click, fix me'); - return; this.setState({ menuOpen: false }); From 3b7d093201b44cb0aeaed5ebe4c0918b7e076f4b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 13 Jan 2017 12:13:46 -0500 Subject: [PATCH 44/93] my_files fixes --- js/app.js | 2 +- js/component/file-actions.js | 2 +- js/component/file-tile.js | 17 ++++++----------- js/page/discover.js | 2 +- js/page/my_files.js | 16 +++++----------- js/page/publish.js | 3 +-- 6 files changed, 15 insertions(+), 27 deletions(-) diff --git a/js/app.js b/js/app.js index cab3045e2..0d7c3848a 100644 --- a/js/app.js +++ b/js/app.js @@ -4,7 +4,7 @@ import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; import ReportPage from './page/report.js'; -import MyFilesPage from './page/my_files.js'; +import {MyFilesPage} from './page/my_files.js'; import StartPage from './page/start.js'; import ClaimCodePage from './page/claim_code.js'; import ReferralPage from './page/referral.js'; diff --git a/js/component/file-actions.js b/js/component/file-actions.js index 3d72f31f3..a89cdfab2 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -178,7 +178,7 @@ export let FileActions = React.createClass({ } const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', showMenu = !this.state.attemptingRemove && this.state.fileInfo !== null; - + let linkBlock; if (this.state.attemptingRemove || (this.state.fileInfo === false && !this.state.attemptingDownload)) { linkBlock = ; diff --git a/js/component/file-tile.js b/js/component/file-tile.js index a958f4a39..a7ee08eb4 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -50,27 +50,25 @@ let FilePrice = React.createClass({ }); /*should be merged into FileTile once FileTile is refactored to take a single id*/ -let FileTileStream = React.createClass({ +export let FileTileStream = React.createClass({ propTypes: { metadata: React.PropTypes.object, sdHash: React.PropTypes.string, - showPrice: React.PropTypes.bool, + hidePrice: React.PropTypes.bool, obscureNsfw: React.PropTypes.bool, hideOnRemove: React.PropTypes.bool }, - getInitialState: function() { return { showNsfwHelp: false, isRemoved: false } }, - getDefaultProps: function() { return { hideOnRemove: false, obscureNsfw: !lbry.getClientSetting('showNsfw'), - showPrice: true + hidePrice: false } }, handleMouseOver: function() { @@ -100,7 +98,6 @@ let FileTileStream = React.createClass({ const metadata = this.props.metadata || {}, obscureNsfw = this.props.obscureNsfw && metadata.nsfw, title = metadata.title ? metadata.title : ('lbry://' + this.props.name); - return (
@@ -108,7 +105,7 @@ let FileTileStream = React.createClass({
- { this.props.showPrice + { !this.props.hidePrice ? : null} @@ -140,7 +137,7 @@ let FileTileStream = React.createClass({ } }); -let FileTile = React.createClass({ +export let FileTile = React.createClass({ _isMounted: false, propTypes: { @@ -176,6 +173,4 @@ let FileTile = React.createClass({ return ; } -}); - -export default FileTile; \ No newline at end of file +}); \ No newline at end of file diff --git a/js/page/discover.js b/js/page/discover.js index dcbf190e9..3e0bf65e2 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; -import FileTile from '../component/file-tile.js'; +import {FileTile} from '../component/file-tile.js'; import {Link, ToolTipLink} from '../component/link.js'; import {BusyMessage} from '../component/common.js'; diff --git a/js/page/my_files.js b/js/page/my_files.js index d3f488eab..6571e7291 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -2,11 +2,10 @@ import React from 'react'; import lbry from '../lbry.js'; import {Link} from '../component/link.js'; import FormField from '../component/form.js'; -import FileTile from '../component/file-tile.js'; -import Modal from '../component/modal.js'; +import {FileTileStream} from '../component/file-tile.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; -var MyFilesPage = React.createClass({ +export let MyFilesPage = React.createClass({ _fileTimeout: null, _fileInfoCheckRate: 300, _fileInfoCheckNum: 0, @@ -170,17 +169,14 @@ var MyFilesPage = React.createClass({ const filesInfoSorted = this._sortFunctions[this.state.sortBy](this.state.filesInfo); for (let fileInfo of filesInfoSorted) { - let {completed, lbry_uri, sd_hash, metadata, download_path, stopped, pending} = fileInfo; + let {lbry_uri, sd_hash, metadata} = fileInfo; if (!metadata || seenUris[lbry_uri]) { continue; } seenUris[lbry_uri] = true; - - content.push(); + content.push(); } } return ( @@ -197,6 +193,4 @@ var MyFilesPage = React.createClass({ ); } -}); - -export default MyFilesPage; \ No newline at end of file +}); \ No newline at end of file diff --git a/js/page/publish.js b/js/page/publish.js index 70e1b851d..8e3ede775 100644 --- a/js/page/publish.js +++ b/js/page/publish.js @@ -91,8 +91,7 @@ var PublishPage = React.createClass({ if (this.refs.file.getValue() !== '') { publishArgs.file_path = this._tempFilePath; } - - console.log(publishArgs); + lbry.publish(publishArgs, (message) => { this.handlePublishStarted(); }, null, (error) => { From 7f135275df0f6e0cecf5d4f8b9a933570c1acae7 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 13 Jan 2017 17:18:37 -0500 Subject: [PATCH 45/93] file list refactor --- js/app.js | 6 +- js/component/file-actions.js | 24 ++--- js/component/file-tile.js | 20 +--- js/lbry.js | 28 ++--- js/page/file-list.js | 194 ++++++++++++++++++++++++++++++++++ js/page/my_files.js | 196 ----------------------------------- scss/_gui.scss | 11 +- 7 files changed, 225 insertions(+), 254 deletions(-) create mode 100644 js/page/file-list.js delete mode 100644 js/page/my_files.js diff --git a/js/app.js b/js/app.js index 0d7c3848a..68c03a648 100644 --- a/js/app.js +++ b/js/app.js @@ -4,7 +4,6 @@ import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; import ReportPage from './page/report.js'; -import {MyFilesPage} from './page/my_files.js'; import StartPage from './page/start.js'; import ClaimCodePage from './page/claim_code.js'; import ReferralPage from './page/referral.js'; @@ -14,6 +13,7 @@ import PublishPage from './page/publish.js'; import DiscoverPage from './page/discover.js'; import SplashScreen from './component/splash.js'; import DeveloperPage from './page/developer.js'; +import {FileListDownloaded, FileListPublished} from './page/file-list.js'; import Drawer from './component/drawer.js'; import Header from './component/header.js'; import Modal from './component/modal.js'; @@ -164,9 +164,9 @@ var App = React.createClass({ case 'report': return ; case 'downloaded': - return ; + return ; case 'published': - return ; + return ; case 'start': return ; case 'claim': diff --git a/js/component/file-actions.js b/js/component/file-actions.js index a89cdfab2..9dc70c476 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -56,11 +56,10 @@ export let FileActions = React.createClass({ propTypes: { streamName: React.PropTypes.string, - sdHash: React.PropTypes.string, + sdHash: React.PropTypes.string.isRequired, metadata: React.PropTypes.object, path: React.PropTypes.string, hidden: React.PropTypes.bool, - onRemoveConfirmed: React.PropTypes.func, deleteChecked: React.PropTypes.bool, }, getInitialState: function() { @@ -137,9 +136,10 @@ export let FileActions = React.createClass({ }); }, handleRemoveConfirmed: function() { - lbry.deleteFile(this.props.sdHash || this.props.streamName, this.state.deleteChecked); - if (this.props.onRemoveConfirmed) { - this.props.onRemoveConfirmed(); + if (this.props.streamName) { + lbry.deleteFile(this.props.streamName, this.state.deleteChecked); + } else { + alert('this file cannot be deleted because lbry is a retarded piece of shit'); } this.setState({ modal: null, @@ -155,20 +155,12 @@ export let FileActions = React.createClass({ }, componentDidMount: function() { this._isMounted = true; - - if ('sdHash' in this.props) { - alert('render by sd hash is broken'); - lbry.fileInfoSubscribeByStreamHash(this.props.sdHash, this.fileInfoU); - } else if ('streamName' in this.props) { - this._fileInfoSubscribeId = lbry.fileInfoSubscribeByName(this.props.streamName, this.onFileInfoUpdate); - } else { - throw new Error("No stream name or sd hash passed to FileTile"); - } + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); }, componentWillUnmount: function() { this._isMounted = false; if (this._fileInfoSubscribeId) { - lbry.fileInfoUnsubscribe(this.props.streamName, this._fileInfoSubscribeId); + lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); } }, render: function() { @@ -178,7 +170,7 @@ export let FileActions = React.createClass({ } const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', showMenu = !this.state.attemptingRemove && this.state.fileInfo !== null; - + let linkBlock; if (this.state.attemptingRemove || (this.state.fileInfo === false && !this.state.attemptingDownload)) { linkBlock = ; diff --git a/js/component/file-tile.js b/js/component/file-tile.js index a7ee08eb4..27b65c691 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -55,8 +55,7 @@ export let FileTileStream = React.createClass({ metadata: React.PropTypes.object, sdHash: React.PropTypes.string, hidePrice: React.PropTypes.bool, - obscureNsfw: React.PropTypes.bool, - hideOnRemove: React.PropTypes.bool + obscureNsfw: React.PropTypes.bool }, getInitialState: function() { return { @@ -66,7 +65,6 @@ export let FileTileStream = React.createClass({ }, getDefaultProps: function() { return { - hideOnRemove: false, obscureNsfw: !lbry.getClientSetting('showNsfw'), hidePrice: false } @@ -85,19 +83,11 @@ export let FileTileStream = React.createClass({ }); } }, - onRemove: function() { - this.setState({ - isRemoved: true, - }); - }, render: function() { - if (this.props.metadata === null || (this.props.hideOnRemove && this.state.isRemoved)) { - return null; - } - const metadata = this.props.metadata || {}, obscureNsfw = this.props.obscureNsfw && metadata.nsfw, title = metadata.title ? metadata.title : ('lbry://' + this.props.name); + return (
@@ -116,7 +106,7 @@ export let FileTileStream = React.createClass({

- +

{metadata.description} @@ -141,7 +131,7 @@ export let FileTile = React.createClass({ _isMounted: false, propTypes: { - name: React.PropTypes.string + name: React.PropTypes.string.isRequired }, getInitialState: function() { @@ -167,7 +157,7 @@ export let FileTile = React.createClass({ this._isMounted = false; }, render: function() { - if (this.state.metadata === null || this.state.sdHash === null) { + if (!this.state.metadata || !this.state.sdHash) { return null; } diff --git a/js/lbry.js b/js/lbry.js index d79bb8454..925f13b34 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -472,43 +472,37 @@ lbry._updateClaimOwnershipCache = function(claimId) { }); }; -lbry._updateSubscribedFileInfoByName = function(name) { - lbry.getFileInfoByName(name, (fileInfo) => { +lbry._updateSubscribedFileInfo = function(sdHash) { + lbry.getFileInfoBySdHash(sdHash, (fileInfo) => { if (fileInfo) { if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { lbry._updateClaimOwnershipCache(fileInfo.claim_id); } fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; } - Object.keys(this._fileInfoSubscribeCallbacks[name]).forEach(function(subscribeId) { - lbry._fileInfoSubscribeCallbacks[name][subscribeId](fileInfo); + Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).forEach(function(subscribeId) { + lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId](fileInfo); }); }); - if (Object.keys(this._fileInfoSubscribeCallbacks[name]).length) { + if (Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).length) { setTimeout(() => { - this._updateSubscribedFileInfoByName(name) + this._updateSubscribedFileInfo(sdHash) }, lbry._fileInfoSubscribeInterval); } } -lbry.fileInfoSubscribeByName = function(name, callback) { - if (!lbry._fileInfoSubscribeCallbacks[name]) +lbry.fileInfoSubscribe = function(sdHash, callback) { + if (!lbry._fileInfoSubscribeCallbacks[sdHash]) { - lbry._fileInfoSubscribeCallbacks[name] = {}; + lbry._fileInfoSubscribeCallbacks[sdHash] = {}; } const subscribeId = ++lbry._fileInfoSubscribeIdCounter; - lbry._fileInfoSubscribeCallbacks[name][subscribeId] = callback; - lbry._updateSubscribedFileInfoByName(name); + lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId] = callback; + lbry._updateSubscribedFileInfo(sdHash); return subscribeId; } -// lbry.fileInfoSubscribeByStreamHash = function(sdHash, callback) { -// lbry.getFileInfoBySdHash(this.props.sdHash, this.updateFileInfoCallback); -// this.getIsMineIfNeeded(this.props.sdHash); -// setTimeout(() => { this.updateFileInfo() }, this._fileInfoCheckInterval); -// } - lbry.fileInfoUnsubscribe = function(name, subscribeId) { delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; } diff --git a/js/page/file-list.js b/js/page/file-list.js new file mode 100644 index 000000000..9b4a7674f --- /dev/null +++ b/js/page/file-list.js @@ -0,0 +1,194 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import {Link} from '../component/link.js'; +import FormField from '../component/form.js'; +import {FileTileStream} from '../component/file-tile.js'; +import {BusyMessage, Thumbnail} from '../component/common.js'; + + +export let FileListDownloaded = React.createClass({ + _isMounted: false, + + getInitialState: function() { + return { + fileInfos: null, + }; + }, + componentDidMount: function() { + this._isMounted = true; + document.title = "Downloaded Files"; + + let publishedFilesSdHashes = []; + lbry.getMyClaims((claimsInfo) => { + + if (!this._isMounted) { return; } + + for (let claimInfo of claimsInfo) { + let metadata = JSON.parse(claimInfo.value); + publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash); + } + + lbry.getFilesInfo((fileInfos) => { + if (!this._isMounted) { return; } + + this.setState({ + fileInfos: fileInfos.filter(({sd_hash}) => { + return publishedFilesSdHashes.indexOf(sd_hash) == -1; + }) + }); + }); + }); + }, + render: function() { + if (this.state.fileInfos === null) { + return ( +

+ +
+ ); + } else if (!this.state.fileInfos.length) { + return ( +
+ You haven't downloaded anything from LBRY yet. Go ! +
+ ); + } else { + return ( +
+ +
+ ); + } + } +}); + +export let FileListPublished = React.createClass({ + _isMounted: false, + + getInitialState: function () { + return { + fileInfos: null, + }; + }, + componentDidMount: function () { + this._isMounted = true; + document.title = "Published Files"; + + lbry.getMyClaims((claimsInfo) => { + /** + * Build newFileInfos as a sparse array and drop elements in at the same position they + * occur in claimsInfo, so the order is preserved even if the API calls inside this loop + * return out of order. + */ + let newFileInfos = Array(claimsInfo.length), + claimInfoProcessedCount = 0; + + for (let [i, claimInfo] of claimsInfo.entries()) { + let metadata = JSON.parse(claimInfo.value); + lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => { + claimInfoProcessedCount++; + if (fileInfo !== false) { + newFileInfos[i] = fileInfo; + } + if (claimInfoProcessedCount >= claimsInfo.length) { + /** + * newfileInfos may have gaps from claims that don't have associated files in + * lbrynet, so filter out any missing elements + */ + this.setState({ + fileInfos: newFileInfos.filter(function () { + return true + }), + }); + } + }); + } + }); + }, + render: function () { + if (this.state.fileInfos === null) { + return ( +
+ +
+ ); + } + else if (!this.state.fileInfos.length) { + return ( +
+ You haven't published anything to LBRY yet. Try ! +
+ ); + } + else { + return ( +
+ +
+ ); + } + } +}); + +export let FileList = React.createClass({ + _sortFunctions: { + date: function(fileInfos) { + return fileInfos.reverse(); + }, + title: function(fileInfos) { + return fileInfos.sort(function(a, b) { + return ((a.metadata ? a.metadata.title.toLowerCase() : a.name) > + (b.metadata ? b.metadata.title.toLowerCase() : b.name)); + }); + }, + filename: function(fileInfos) { + return fileInfos.sort(function(a, b) { + return (a.file_name.toLowerCase() > + b.file_name.toLowerCase()); + }); + }, + }, + propTypes: { + fileInfos: React.PropTypes.array.isRequired + }, + getInitialState: function() { + return { + sortBy: 'date', + }; + }, + handleSortChanged: function(event) { + this.setState({ + sortBy: event.target.value, + }); + }, + render: function() { + var content = [], + seenUris = {}; + + const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); + for (let fileInfo of fileInfosSorted) { + let {lbry_uri, sd_hash, metadata} = fileInfo; + + if (!metadata || seenUris[lbry_uri]) { + continue; + } + + seenUris[lbry_uri] = true; + content.push(); + } + + return ( +
+ + Sort by { ' ' } + + + + + + + {content} +
+ ); + } +}); \ No newline at end of file diff --git a/js/page/my_files.js b/js/page/my_files.js deleted file mode 100644 index 6571e7291..000000000 --- a/js/page/my_files.js +++ /dev/null @@ -1,196 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; -import {FileTileStream} from '../component/file-tile.js'; -import {BusyMessage, Thumbnail} from '../component/common.js'; - -export let MyFilesPage = React.createClass({ - _fileTimeout: null, - _fileInfoCheckRate: 300, - _fileInfoCheckNum: 0, - _sortFunctions: { - date: function(filesInfo) { - return filesInfo.reverse(); - }, - title: function(filesInfo) { - return filesInfo.sort(function(a, b) { - return ((a.metadata ? a.metadata.title.toLowerCase() : a.name) > - (b.metadata ? b.metadata.title.toLowerCase() : b.name)); - }); - }, - filename: function(filesInfo) { - return filesInfo.sort(function(a, b) { - return (a.file_name.toLowerCase() > - b.file_name.toLowerCase()); - }); - }, - }, - - getInitialState: function() { - return { - filesInfo: null, - publishedFilesSdHashes: null, - filesAvailable: null, - sortBy: 'date', - }; - }, - getDefaultProps: function() { - return { - show: null, - }; - }, - componentDidMount: function() { - document.title = "My Files"; - }, - componentWillMount: function() { - if (this.props.show == 'downloaded') { - this.getPublishedFilesSdHashes(() => { - this.updateFilesInfo(); - }); - } else { - this.updateFilesInfo(); - } - }, - getPublishedFilesSdHashes: function(callback) { - // Determines which files were published by the user and saves their SD hashes in - // this.state.publishedFilesSdHashes. Used on the Downloads page to filter out claims published - // by the user. - var publishedFilesSdHashes = []; - lbry.getMyClaims((claimsInfo) => { - for (let claimInfo of claimsInfo) { - let metadata = JSON.parse(claimInfo.value); - publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash); - } - - this.setState({ - publishedFilesSdHashes: publishedFilesSdHashes, - }); - callback(); - }); - }, - componentWillUnmount: function() { - if (this._fileTimeout) - { - clearTimeout(this._fileTimeout); - } - }, - handleSortChanged: function(event) { - this.setState({ - sortBy: event.target.value, - }); - }, - updateFilesInfo: function() { - this._fileInfoCheckNum += 1; - - if (this.props.show == 'published') { - // We're in the Published tab, so populate this.state.filesInfo with data from the user's claims - lbry.getMyClaims((claimsInfo) => { - /** - * Build newFilesInfo as a sparse array and drop elements in at the same position they - * occur in claimsInfo, so the order is preserved even if the API calls inside this loop - * return out of order. - */ - - let newFilesInfo = Array(claimsInfo.length); - let claimInfoProcessedCount = 0; - for (let [i, claimInfo] of claimsInfo.entries()) { - let metadata = JSON.parse(claimInfo.value); - lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => { - claimInfoProcessedCount++; - if (fileInfo !== false) { - newFilesInfo[i] = fileInfo; - } - if (claimInfoProcessedCount >= claimsInfo.length) { - /** - * newFilesInfo may have gaps from claims that don't have associated files in - * lbrynet, so filter out any missing elements - */ - this.setState({ - filesInfo: newFilesInfo.filter(function() { return true }), - }); - - this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000); - } - }); - } - }); - } else { - // We're in the Downloaded tab, so populate this.state.filesInfo with files the user has in - // lbrynet, with published files filtered out. - lbry.getFilesInfo((filesInfo) => { - this.setState({ - filesInfo: filesInfo.filter(({sd_hash}) => { - return this.state.publishedFilesSdHashes.indexOf(sd_hash) == -1; - }), - }); - - let newFilesAvailable; - if (!(this._fileInfoCheckNum % this._fileInfoCheckRate)) { - // Time to update file availability status - - newFilesAvailable = {}; - let filePeersCheckCount = 0; - for (let {sd_hash} of filesInfo) { - lbry.getPeersForBlobHash(sd_hash, (peers) => { - filePeersCheckCount++; - newFilesAvailable[sd_hash] = peers.length >= 0; - if (filePeersCheckCount >= filesInfo.length) { - this.setState({ - filesAvailable: newFilesAvailable, - }); - } - }); - } - } - - this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000); - }) - } - }, - render: function() { - if (this.state.filesInfo === null || (this.props.show == 'downloaded' && this.state.publishedFileSdHashes === null)) { - return ( -
- -
- ); - } else if (!this.state.filesInfo.length) { - return ( -
- {this.props.show == 'downloaded' - ? You haven't downloaded anything from LBRY yet. Go ! - : You haven't published anything to LBRY yet.} -
- ); - } else { - var content = [], - seenUris = {}; - - const filesInfoSorted = this._sortFunctions[this.state.sortBy](this.state.filesInfo); - for (let fileInfo of filesInfoSorted) { - let {lbry_uri, sd_hash, metadata} = fileInfo; - - if (!metadata || seenUris[lbry_uri]) { - continue; - } - - seenUris[lbry_uri] = true; - content.push(); - } - } - return ( -
- - Sort by { ' ' } - - - - - - - {content} -
- ); - } -}); \ No newline at end of file diff --git a/scss/_gui.scss b/scss/_gui.scss index 137c60b24..7a464a794 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -347,12 +347,6 @@ input[type="text"], input[type="search"] margin: 0px 6px; } -.error-modal__error-list { - border: 1px solid #eee; - padding: 8px; - list-style: none; -} - .error-modal-overlay { background: rgba(#000, .88); } @@ -371,12 +365,15 @@ input[type="text"], input[type="search"] word-break: break-all; } - .error-modal { max-width: none; width: 400px; } .error-modal__error-list { /*shitty hack/temp fix for long errors making modals unusable*/ + border: 1px solid #eee; + padding: 8px; + list-style: none; max-height: 400px; + max-width: 400px; overflow-y: hidden; } From 70db4c937e09ffc315b5cab5f28b3c36cc5fbd20 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 13 Jan 2017 17:47:48 -0500 Subject: [PATCH 46/93] shitty file remove --- js/component/file-tile.js | 28 +++++++++++++++++++++++++++- js/lbry.js | 1 - 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 27b65c691..42f79d16c 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -51,16 +51,20 @@ let FilePrice = React.createClass({ /*should be merged into FileTile once FileTile is refactored to take a single id*/ export let FileTileStream = React.createClass({ + _fileInfoSubscribeId: null, + _isMounted: null, + propTypes: { metadata: React.PropTypes.object, sdHash: React.PropTypes.string, + hideOnRemove: React.PropTypes.bool, hidePrice: React.PropTypes.bool, obscureNsfw: React.PropTypes.bool }, getInitialState: function() { return { showNsfwHelp: false, - isRemoved: false + isHidden: false } }, getDefaultProps: function() { @@ -69,6 +73,24 @@ export let FileTileStream = React.createClass({ hidePrice: false } }, + componentDidMount: function() { + this._isMounted = true; + if (this.props.hideOnRemove) { + lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); + } + }, + componentWillUnmount: function() { + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); + } + }, + onFileInfoUpdate: function(fileInfo) { + if (!fileInfo && this._isMounted && this.props.hideOnRemove) { + this.setState({ + isHidden: true + }); + } + }, handleMouseOver: function() { if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { this.setState({ @@ -84,6 +106,10 @@ export let FileTileStream = React.createClass({ } }, render: function() { + if (this.state.isHidden) { + return null; + } + const metadata = this.props.metadata || {}, obscureNsfw = this.props.obscureNsfw && metadata.nsfw, title = metadata.title ? metadata.title : ('lbry://' + this.props.name); diff --git a/js/lbry.js b/js/lbry.js index 925f13b34..d264b1a31 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -463,7 +463,6 @@ lbry._fileInfoSubscribeCallbacks = {}; lbry._fileInfoSubscribeInterval = 5000; lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not - lbry._updateClaimOwnershipCache = function(claimId) { lbry.getMyClaims((claimsInfo) => { lbry._claimIdOwnershipCache[claimId] = !!claimsInfo.reduce(function(match, claimInfo) { From e9f00eec23461740cbcc875baee9d5943bdb47ac Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 16 Jan 2017 00:29:28 -0500 Subject: [PATCH 47/93] Pad buttons using margin on content instead of padding on container Allows button-style progress bars to set width without having to account for padding --- js/component/file-actions.js | 2 +- js/component/link.js | 20 ++++++++------------ scss/_gui.scss | 11 +++++------ scss/component/_file-actions.scss | 4 ---- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/js/component/file-actions.js b/js/component/file-actions.js index 9dc70c476..e793e8f1f 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -178,7 +178,7 @@ export let FileActions = React.createClass({ const progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0, label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', - labelWithIcon = {label}; + labelWithIcon = {label}; linkBlock =
diff --git a/js/component/link.js b/js/component/link.js index b9ce82e18..56429aefc 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -34,23 +34,19 @@ export let Link = React.createClass({ if (this.props.children) { // Custom content content = this.props.children; } else { - content = ( - - {'icon' in this.props - ? - : null} - {this.props.label} - {'badge' in this.props - ? {this.props.badge} - : null} - - ); + content = [ + 'icon' in this.props ? : null, + {this.props.label}, + 'badge' in this.props ? {this.props.badge} : null, + ]; } return ( - {content} + {this.props.button + ? {content} + : content} ); } diff --git a/scss/_gui.scss b/scss/_gui.scss index 7a464a794..a184f6ace 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -177,27 +177,26 @@ input[type="text"], input[type="search"] cursor: pointer; } +.button__content { + margin: 0 $padding-button; +} + .button-primary { color: white; background-color: $color-primary; box-shadow: $default-box-shadow; - padding: 0 $padding-button; } .button-alt { background-color: $color-bg-alt; box-shadow: $default-box-shadow; - padding: 0 $padding-button; } -.button-cancel -{ - padding: 0 $padding-button; -} .button-text { @include text-link(); + display: inline-block; } .button-text-help { diff --git a/scss/component/_file-actions.scss b/scss/component/_file-actions.scss index e17ed80d3..5117b11f5 100644 --- a/scss/component/_file-actions.scss +++ b/scss/component/_file-actions.scss @@ -9,15 +9,11 @@ $color-download: #444; .file-actions__download-status-bar { - padding-right: $padding-button; - padding-left: $padding-button; position: relative; color: $color-download; } .file-actions__download-status-bar-overlay { - padding-right: $padding-button; - padding-left: $padding-button; background: $color-download; color: white; position: absolute; From 425fbb84a5523a8ebbb17ee798c4ba1b9ece3649 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 17 Jan 2017 04:04:29 -0500 Subject: [PATCH 48/93] Fix delay in files disappearing from My Files We now maintain a list of files that have been requested for removal in lbry.js and simulate the file being removed so all components can respond immediately. --- js/component/file-actions.js | 8 ++++---- js/lbry.js | 38 ++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/js/component/file-actions.js b/js/component/file-actions.js index e793e8f1f..cdb4044b7 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -61,6 +61,7 @@ export let FileActions = React.createClass({ path: React.PropTypes.string, hidden: React.PropTypes.bool, deleteChecked: React.PropTypes.bool, + onRemove: React.PropTypes.function, }, getInitialState: function() { return { @@ -137,14 +138,13 @@ export let FileActions = React.createClass({ }, handleRemoveConfirmed: function() { if (this.props.streamName) { - lbry.deleteFile(this.props.streamName, this.state.deleteChecked); + lbry.removeFile(this.props.sdHash, this.props.streamName, this.state.deleteChecked); } else { alert('this file cannot be deleted because lbry is a retarded piece of shit'); } this.setState({ modal: null, fileInfo: false, - attemptingRemove: true, attemptingDownload: false }); }, @@ -169,10 +169,10 @@ export let FileActions = React.createClass({ return
; } const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', - showMenu = !this.state.attemptingRemove && this.state.fileInfo !== null; + showMenu = !!this.state.fileInfo; let linkBlock; - if (this.state.attemptingRemove || (this.state.fileInfo === false && !this.state.attemptingDownload)) { + if (this.state.fileInfo === false && !this.state.attemptingDownload) { linkBlock = ; } else if (this.state.attemptingDownload || !this.state.fileInfo.completed) { const diff --git a/js/lbry.js b/js/lbry.js index d264b1a31..38df0564e 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -265,7 +265,10 @@ lbry.stopFile = function(name, callback) { lbry.call('stop_lbry_file', { name: name }, callback); } -lbry.deleteFile = function(name, deleteTargetFile=true, callback) { +lbry.removeFile = function(sdHash, name, deleteTargetFile=true, callback) { // Name param is temporary until the API can delete by unique ID (SD hash, claim ID etc.) + this._removedFiles.push(sdHash); + this._updateSubscribedFileInfo(sdHash); + lbry.call('delete_lbry_file', { name: name, delete_target_file: deleteTargetFile, @@ -461,6 +464,7 @@ lbry.fileInfo = {}; lbry._fileInfoSubscribeIdCounter = 0; lbry._fileInfoSubscribeCallbacks = {}; lbry._fileInfoSubscribeInterval = 5000; +lbry._removedFiles = []; lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not lbry._updateClaimOwnershipCache = function(claimId) { @@ -472,20 +476,30 @@ lbry._updateClaimOwnershipCache = function(claimId) { }; lbry._updateSubscribedFileInfo = function(sdHash) { - lbry.getFileInfoBySdHash(sdHash, (fileInfo) => { - if (fileInfo) { - if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { - lbry._updateClaimOwnershipCache(fileInfo.claim_id); - } - fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; - } - Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).forEach(function(subscribeId) { - lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId](fileInfo); + const callSubscribedCallbacks = (sdHash, fileInfo) => { + Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).forEach((subscribeId) => { + this._fileInfoSubscribeCallbacks[sdHash][subscribeId](fileInfo); }); - }); + } + + if (lbry._removedFiles.includes(sdHash)) { + callSubscribedCallbacks(sdHash, false); + } else { + lbry.getFileInfoBySdHash(sdHash, (fileInfo) => { + if (fileInfo) { + if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { + this._updateClaimOwnershipCache(fileInfo.claim_id); + } + fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; + } + + callSubscribedCallbacks(sdHash, fileInfo); + }); + } + if (Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).length) { setTimeout(() => { - this._updateSubscribedFileInfo(sdHash) + this._updateSubscribedFileInfo(sdHash); }, lbry._fileInfoSubscribeInterval); } } From 0b9980f371058c8d2fdf07cd62564d9aaf13fe2e Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 17 Jan 2017 05:06:39 -0500 Subject: [PATCH 49/93] Rename all vars called "___sInfo" to "___Infos" --- js/lbry.js | 10 +++++----- js/page/file-list.js | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/js/lbry.js b/js/lbry.js index 38df0564e..c0c4798d7 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -286,8 +286,8 @@ lbry.revealFile = function(path, callback) { lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) { // Calls callback with file info when it appears in the list of files returned by lbry.getFilesInfo(). // If timeoutCallback is provided, it will be called if the file fails to appear. - lbry.getFilesInfo(function(filesInfo) { - for (var fileInfo of filesInfo) { + lbry.getFilesInfo(function(fileInfos) { + for (var fileInfo of fileInfos) { if (fileInfo.lbry_uri == name) { callback(fileInfo); return; @@ -468,8 +468,8 @@ lbry._removedFiles = []; lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not lbry._updateClaimOwnershipCache = function(claimId) { - lbry.getMyClaims((claimsInfo) => { - lbry._claimIdOwnershipCache[claimId] = !!claimsInfo.reduce(function(match, claimInfo) { + lbry.getMyClaims((claimInfos) => { + lbry._claimIdOwnershipCache[claimId] = !!claimInfos.reduce(function(match, claimInfo) { return match || claimInfo.claim_id == claimId; }); }); @@ -520,4 +520,4 @@ lbry.fileInfoUnsubscribe = function(name, subscribeId) { delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; } -export default lbry; \ No newline at end of file +export default lbry; diff --git a/js/page/file-list.js b/js/page/file-list.js index 9b4a7674f..3cf51ee37 100644 --- a/js/page/file-list.js +++ b/js/page/file-list.js @@ -19,11 +19,11 @@ export let FileListDownloaded = React.createClass({ document.title = "Downloaded Files"; let publishedFilesSdHashes = []; - lbry.getMyClaims((claimsInfo) => { + lbry.getMyClaims((claimInfos) => { if (!this._isMounted) { return; } - for (let claimInfo of claimsInfo) { + for (let claimInfo of claimInfos) { let metadata = JSON.parse(claimInfo.value); publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash); } @@ -74,23 +74,23 @@ export let FileListPublished = React.createClass({ this._isMounted = true; document.title = "Published Files"; - lbry.getMyClaims((claimsInfo) => { + lbry.getMyClaims((claimInfos) => { /** * Build newFileInfos as a sparse array and drop elements in at the same position they - * occur in claimsInfo, so the order is preserved even if the API calls inside this loop + * occur in claimInfos, so the order is preserved even if the API calls inside this loop * return out of order. */ - let newFileInfos = Array(claimsInfo.length), + let newFileInfos = Array(claimInfos.length), claimInfoProcessedCount = 0; - for (let [i, claimInfo] of claimsInfo.entries()) { + for (let [i, claimInfo] of claimInfos.entries()) { let metadata = JSON.parse(claimInfo.value); lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => { claimInfoProcessedCount++; if (fileInfo !== false) { newFileInfos[i] = fileInfo; } - if (claimInfoProcessedCount >= claimsInfo.length) { + if (claimInfoProcessedCount >= claimInfos.length) { /** * newfileInfos may have gaps from claims that don't have associated files in * lbrynet, so filter out any missing elements From 88d4c49d9a360be805f91d5e8e91c0b6ad91f1b9 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 17 Jan 2017 05:11:21 -0500 Subject: [PATCH 50/93] Use Object.entries() when calling file info callbacks --- js/lbry.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/lbry.js b/js/lbry.js index c0c4798d7..628cd2bb4 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -477,9 +477,9 @@ lbry._updateClaimOwnershipCache = function(claimId) { lbry._updateSubscribedFileInfo = function(sdHash) { const callSubscribedCallbacks = (sdHash, fileInfo) => { - Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).forEach((subscribeId) => { - this._fileInfoSubscribeCallbacks[sdHash][subscribeId](fileInfo); - }); + for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[sdHash])) { + callback(fileInfo); + } } if (lbry._removedFiles.includes(sdHash)) { From 8fdfd4b60e83f1fe3de7e91ac7d655d41dca78ef Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 17 Jan 2017 05:19:09 -0500 Subject: [PATCH 51/93] Show prices on Published page --- js/page/file-list.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/js/page/file-list.js b/js/page/file-list.js index 3cf51ee37..95304535c 100644 --- a/js/page/file-list.js +++ b/js/page/file-list.js @@ -55,7 +55,7 @@ export let FileListDownloaded = React.createClass({ } else { return (
- +
); } @@ -149,7 +149,13 @@ export let FileList = React.createClass({ }, }, propTypes: { - fileInfos: React.PropTypes.array.isRequired + fileInfos: React.PropTypes.array.isRequired, + hidePrices: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + hidePrices: false, + }; }, getInitialState: function() { return { @@ -174,7 +180,8 @@ export let FileList = React.createClass({ } seenUris[lbry_uri] = true; - content.push(); + content.push(); } return ( From 839c42bc3abadbe8d4a666b172a8ce111c96d951 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 17 Jan 2017 05:31:58 -0500 Subject: [PATCH 52/93] Use new format for open and reveal RPC methods --- js/component/file-actions.js | 4 ++-- js/lbry.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/js/component/file-actions.js b/js/component/file-actions.js index cdb4044b7..9e9ccc12e 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -118,7 +118,7 @@ export let FileActions = React.createClass({ }, onOpenClick: function() { if (this.state.fileInfo && this.state.fileInfo.completed) { - lbry.openFile(this.state.fileInfo.download_path); + lbry.openFile(this.props.sdHash); } }, handleDeleteCheckboxClicked: function(event) { @@ -128,7 +128,7 @@ export let FileActions = React.createClass({ }, handleRevealClicked: function() { if (this.state.fileInfo && this.state.fileInfo.download_path) { - lbry.revealFile(this.state.fileInfo.download_path); + lbry.revealFile(this.props.sdHash); } }, handleRemoveClicked: function() { diff --git a/js/lbry.js b/js/lbry.js index 628cd2bb4..12ba61707 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -275,12 +275,12 @@ lbry.removeFile = function(sdHash, name, deleteTargetFile=true, callback) { // N }, callback); } -lbry.openFile = function(path, callback) { - lbry.call('open_file', {path: path}, callback); +lbry.openFile = function(sdHash, callback) { + lbry.call('open', {sd_hash: sdHash}, callback); } -lbry.revealFile = function(path, callback) { - lbry.call('reveal', {path: path}, callback); +lbry.revealFile = function(sdHash, callback) { + lbry.call('reveal', {sd_hash: sdHash}, callback); } lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) { From e6563f26b8dafb7d6247fae6a87df059f4a37464 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Tue, 17 Jan 2017 05:50:38 -0500 Subject: [PATCH 53/93] Fix extra margin around Open and Download links --- js/component/link.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/component/link.js b/js/component/link.js index 56429aefc..4a80ee830 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -25,9 +25,10 @@ export let Link = React.createClass({ return null; } + /* The way the class name is generated here is a mess -- refactor */ + const className = (this.props.className || '') + (this.props.button ? ' button-block button-' + this.props.button : '') + - (!this.props.className && !this.props.button ? 'button-text' : '') + (this.props.disabled ? ' disabled' : ''); let content; @@ -44,7 +45,7 @@ export let Link = React.createClass({ return ( - {this.props.button + {('button' in this.props) && this.props.button != 'text' ? {content} : content} From 5f2312295c210273c9ab599f228b0f1bf0c52597 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 17 Jan 2017 21:36:32 -0600 Subject: [PATCH 54/93] fix typo in proptypes --- js/component/file-actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/component/file-actions.js b/js/component/file-actions.js index 9e9ccc12e..df203a467 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -61,7 +61,7 @@ export let FileActions = React.createClass({ path: React.PropTypes.string, hidden: React.PropTypes.bool, deleteChecked: React.PropTypes.bool, - onRemove: React.PropTypes.function, + onRemove: React.PropTypes.func, }, getInitialState: function() { return { @@ -218,4 +218,4 @@ export let FileActions = React.createClass({
); } -}); \ No newline at end of file +}); From 416641982378c260c5e7858aba2aa27f783fd22b Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 17 Jan 2017 21:36:48 -0600 Subject: [PATCH 55/93] fix missing Icon import --- js/component/form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/component/form.js b/js/component/form.js index 27a7c977a..ae4135a29 100644 --- a/js/component/form.js +++ b/js/component/form.js @@ -1,4 +1,5 @@ import React from 'react'; +import {Icon} from './common.js'; var requiredFieldWarningStyle = { color: '#cc0000', From 22c4f41066eb208e642f0575c1a1cd919974356f Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 17 Jan 2017 21:37:24 -0600 Subject: [PATCH 56/93] fix errorcallback in publish --- js/lbry.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/js/lbry.js b/js/lbry.js index 12ba61707..65fb4e821 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -308,12 +308,7 @@ lbry.publish = function(params, fileListedCallback, publishedCallback, errorCall // lbry.getFilesInfo() during the publish process. // Use ES6 named arguments instead of directly passing param dict? - lbry.call('publish', params, publishedCallback, (errorInfo) => { - errorCallback({ - name: fault.fault, - message: fault.faultString, - }); - }); + lbry.call('publish', params, publishedCallback, errorCallback); if (fileListedCallback) { lbry.getFileInfoWhenListed(params.name, function(fileInfo) { fileListedCallback(fileInfo); From f1b2d0d62d4fc89e15a779ce0d831723196b16f4 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 17 Jan 2017 21:57:16 -0600 Subject: [PATCH 57/93] ensure that content_type exists --- js/component/file-actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/component/file-actions.js b/js/component/file-actions.js index df203a467..ec4a14e06 100644 --- a/js/component/file-actions.js +++ b/js/component/file-actions.js @@ -191,7 +191,7 @@ export let FileActions = React.createClass({ return (
- {this.props.metadata.content_type.startsWith('video/') ? : null} + {(this.props.metadata.content_type && this.props.metadata.content_type.startsWith('video/')) ? : null} {this.state.fileInfo !== null || this.state.fileInfo.isMine ?
{linkBlock}
: null} From 2aa10261d957a73144b86c2ecc70b71b8f7869e7 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 18 Jan 2017 09:29:47 -0600 Subject: [PATCH 58/93] Improve startup checking Before this fix, if the daemon wasn't accepting connections the UI would get an error and then be stuck on the loading screen. We now check if the daemon is accepting connections first, and then check for the status. --- js/component/splash.js | 42 +++++++++++++++++++++++++++--------------- js/lbry.js | 23 ++++++++++------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/js/component/splash.js b/js/component/splash.js index bcb045468..29cb65234 100644 --- a/js/component/splash.js +++ b/js/component/splash.js @@ -14,24 +14,36 @@ var SplashScreen = React.createClass({ } }, updateStatus: function(was_lagging=false) { - lbry.getDaemonStatus((status) => { - if (status.code == 'started') { - this.props.onLoadDone(); - return; - } - - this.setState({ - details: status.message + (status.is_lagging ? '' : '...'), - isLagging: status.is_lagging, - }); - - setTimeout(() => { - this.updateStatus(status.is_lagging); - }, 500); + lbry.getDaemonStatus(this._updateStatusCallback); + }, + _updateStatusCallback: function(status) { + if (status.code == 'started') { + // Wait until we are able to resolve a name before declaring + // that we are done. + // TODO: This is a hack, and the logic should live in the daemon + // to give us a better sense of when we are actually started + this.setState({ + details: 'Waiting for name resolution', + isLagging: false }); + + lbry.resolveName('one', () => { + this.props.onLoadDone(); + }); + return; + } + this.setState({ + details: status.message + (status.is_lagging ? '' : '...'), + isLagging: status.is_lagging, + }); + setTimeout(() => { + this.updateStatus(status.is_lagging); + }, 500); }, componentDidMount: function() { - this.updateStatus(); + lbry.connect((connected) => { + this.updateStatus(); + }); }, render: function() { return ; diff --git a/js/lbry.js b/js/lbry.js index 65fb4e821..9063aca2e 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -90,17 +90,18 @@ lbry.call = function (method, params, callback, errorCallback, connectFailedCall //core lbry.connect = function(callback) { - // Check every half second to see if the daemon's running. - // Returns true to callback once connected, or false if it takes too long and we give up. - function checkDaemonRunning(tryNum=0) { - lbry.daemonRunningStatus(function (runningStatus) { + // Check every half second to see if the daemon is accepting connections + // Once this returns True, can call getDaemonStatus to see where + // we are in the startup process + function checkDaemonStarted(tryNum=0) { + lbry.isDaemonAcceptingConnections(function (runningStatus) { if (runningStatus) { lbry.isConnected = true; callback(true); } else { if (tryNum <= 600) { // Move # of tries into constant or config option setTimeout(function () { - checkDaemonRunning(tryNum + 1); + checkDaemonStarted(tryNum + 1); }, 500); } else { callback(false); @@ -108,16 +109,12 @@ lbry.connect = function(callback) } }); } - checkDaemonRunning(); + checkDaemonStarted(); } -lbry.daemonRunningStatus = function (callback) { - // Returns true/false whether the daemon is running (i.e. fully conncected to the network), - // or null if the AJAX connection to the daemon fails. - - lbry.call('is_running', {}, callback, null, function () { - callback(null); - }); +lbry.isDaemonAcceptingConnections = function (callback) { + // Returns true/false whether the daemon is at a point it will start returning status + lbry.call('status', {}, () => callback(true), null, () => callback(false)) }; lbry.getDaemonStatus = function (callback) { From 9d0aa0d8da70797bfe303b7cd0f1c3fbad3503a9 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Thu, 19 Jan 2017 04:58:11 -0500 Subject: [PATCH 59/93] Add setting to hide unavailable content --- js/component/file-tile.js | 23 +++++++++++++++++++---- js/page/discover.js | 4 ++-- js/page/settings.js | 19 +++++++++++++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/js/component/file-tile.js b/js/component/file-tile.js index 42f79d16c..1c4f1873e 100644 --- a/js/component/file-tile.js +++ b/js/component/file-tile.js @@ -64,7 +64,8 @@ export let FileTileStream = React.createClass({ getInitialState: function() { return { showNsfwHelp: false, - isHidden: false + isHidden: false, + available: null, } }, getDefaultProps: function() { @@ -73,6 +74,15 @@ export let FileTileStream = React.createClass({ hidePrice: false } }, + componentWillMount: function() { + if (!('available' in this.props)) { + lbry.getPeersForBlobHash(this.props.sdHash, (peers) => { + this.setState({ + available: peers.length > 0, + }); + }); + } + }, componentDidMount: function() { this._isMounted = true; if (this.props.hideOnRemove) { @@ -84,6 +94,9 @@ export let FileTileStream = React.createClass({ lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); } }, + isAvailable: function() { + return 'available' in this.props ? this.props.available : this.state.available; + }, onFileInfoUpdate: function(fileInfo) { if (!fileInfo && this._isMounted && this.props.hideOnRemove) { this.setState({ @@ -106,7 +119,7 @@ export let FileTileStream = React.createClass({ } }, render: function() { - if (this.state.isHidden) { + if (this.state.isHidden || (!lbry.getClientSetting('showUnavailable') && !this.isAvailable())) { return null; } @@ -157,7 +170,8 @@ export let FileTile = React.createClass({ _isMounted: false, propTypes: { - name: React.PropTypes.string.isRequired + name: React.PropTypes.string.isRequired, + available: React.PropTypes.bool, }, getInitialState: function() { @@ -187,6 +201,7 @@ export let FileTile = React.createClass({ return null; } - return ; + return ; } }); \ No newline at end of file diff --git a/js/page/discover.js b/js/page/discover.js index 3e0bf65e2..3d9d620c5 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -43,11 +43,11 @@ var SearchResults = React.createClass({ render: function() { var rows = [], seenNames = {}; //fix this when the search API returns claim IDs - this.props.results.forEach(function({name, value}) { + this.props.results.forEach(function({name, peer_count}) { if (!seenNames[name]) { seenNames[name] = name; rows.push( - + 0} /> ); } }); diff --git a/js/page/settings.js b/js/page/settings.js index 81403492b..b192f15c9 100644 --- a/js/page/settings.js +++ b/js/page/settings.js @@ -51,7 +51,8 @@ var SettingsPage = React.createClass({ getInitialState: function() { return { settings: null, - showNsfw: lbry.getClientSetting('showNsfw') + showNsfw: lbry.getClientSetting('showNsfw'), + showUnavailable: lbry.getClientSetting('showUnavailable'), } }, componentDidMount: function() { @@ -69,6 +70,9 @@ var SettingsPage = React.createClass({ onShowNsfwChange: function(event) { lbry.setClientSetting('showNsfw', event.target.checked); }, + onShowUnavailableChange: function(event) { + lbry.setClientSetting('showUnavailable', event.target.checked); + }, render: function() { if (!this.state.daemonSettings) { return null; @@ -114,7 +118,7 @@ var SettingsPage = React.createClass({

Content

NSFW content may include nudity, intense sexuality, profanity, or other adult content. @@ -122,6 +126,17 @@ var SettingsPage = React.createClass({
+
+

Search

+
+
+ Would you like search results to include items that are not currently available for download? +
+ +
+

Share Diagnostic Data