file list refactor

This commit is contained in:
Jeremy Kauffman 2017-01-13 17:18:37 -05:00 committed by Alex Liebowitz
parent 3b7d093201
commit 7f135275df
7 changed files with 225 additions and 254 deletions

View file

@ -4,7 +4,6 @@ import SettingsPage from './page/settings.js';
import HelpPage from './page/help.js'; import HelpPage from './page/help.js';
import WatchPage from './page/watch.js'; import WatchPage from './page/watch.js';
import ReportPage from './page/report.js'; import ReportPage from './page/report.js';
import {MyFilesPage} from './page/my_files.js';
import StartPage from './page/start.js'; import StartPage from './page/start.js';
import ClaimCodePage from './page/claim_code.js'; import ClaimCodePage from './page/claim_code.js';
import ReferralPage from './page/referral.js'; import ReferralPage from './page/referral.js';
@ -14,6 +13,7 @@ import PublishPage from './page/publish.js';
import DiscoverPage from './page/discover.js'; import DiscoverPage from './page/discover.js';
import SplashScreen from './component/splash.js'; import SplashScreen from './component/splash.js';
import DeveloperPage from './page/developer.js'; import DeveloperPage from './page/developer.js';
import {FileListDownloaded, FileListPublished} from './page/file-list.js';
import Drawer from './component/drawer.js'; import Drawer from './component/drawer.js';
import Header from './component/header.js'; import Header from './component/header.js';
import Modal from './component/modal.js'; import Modal from './component/modal.js';
@ -164,9 +164,9 @@ var App = React.createClass({
case 'report': case 'report':
return <ReportPage />; return <ReportPage />;
case 'downloaded': case 'downloaded':
return <MyFilesPage show="downloaded" />; return <FileListDownloaded />;
case 'published': case 'published':
return <MyFilesPage show="published" />; return <FileListPublished />;
case 'start': case 'start':
return <StartPage />; return <StartPage />;
case 'claim': case 'claim':

View file

@ -56,11 +56,10 @@ export let FileActions = React.createClass({
propTypes: { propTypes: {
streamName: React.PropTypes.string, streamName: React.PropTypes.string,
sdHash: React.PropTypes.string, sdHash: React.PropTypes.string.isRequired,
metadata: React.PropTypes.object, metadata: React.PropTypes.object,
path: React.PropTypes.string, path: React.PropTypes.string,
hidden: React.PropTypes.bool, hidden: React.PropTypes.bool,
onRemoveConfirmed: React.PropTypes.func,
deleteChecked: React.PropTypes.bool, deleteChecked: React.PropTypes.bool,
}, },
getInitialState: function() { getInitialState: function() {
@ -137,9 +136,10 @@ export let FileActions = React.createClass({
}); });
}, },
handleRemoveConfirmed: function() { handleRemoveConfirmed: function() {
lbry.deleteFile(this.props.sdHash || this.props.streamName, this.state.deleteChecked); if (this.props.streamName) {
if (this.props.onRemoveConfirmed) { lbry.deleteFile(this.props.streamName, this.state.deleteChecked);
this.props.onRemoveConfirmed(); } else {
alert('this file cannot be deleted because lbry is a retarded piece of shit');
} }
this.setState({ this.setState({
modal: null, modal: null,
@ -155,20 +155,12 @@ export let FileActions = React.createClass({
}, },
componentDidMount: function() { componentDidMount: function() {
this._isMounted = true; this._isMounted = true;
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate);
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() { componentWillUnmount: function() {
this._isMounted = false; this._isMounted = false;
if (this._fileInfoSubscribeId) { if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.streamName, this._fileInfoSubscribeId); lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId);
} }
}, },
render: function() { render: function() {

View file

@ -55,8 +55,7 @@ export let FileTileStream = React.createClass({
metadata: React.PropTypes.object, metadata: React.PropTypes.object,
sdHash: React.PropTypes.string, sdHash: React.PropTypes.string,
hidePrice: React.PropTypes.bool, hidePrice: React.PropTypes.bool,
obscureNsfw: React.PropTypes.bool, obscureNsfw: React.PropTypes.bool
hideOnRemove: React.PropTypes.bool
}, },
getInitialState: function() { getInitialState: function() {
return { return {
@ -66,7 +65,6 @@ export let FileTileStream = React.createClass({
}, },
getDefaultProps: function() { getDefaultProps: function() {
return { return {
hideOnRemove: false,
obscureNsfw: !lbry.getClientSetting('showNsfw'), obscureNsfw: !lbry.getClientSetting('showNsfw'),
hidePrice: false hidePrice: false
} }
@ -85,19 +83,11 @@ export let FileTileStream = React.createClass({
}); });
} }
}, },
onRemove: function() {
this.setState({
isRemoved: true,
});
},
render: function() { render: function() {
if (this.props.metadata === null || (this.props.hideOnRemove && this.state.isRemoved)) {
return null;
}
const metadata = this.props.metadata || {}, const metadata = this.props.metadata || {},
obscureNsfw = this.props.obscureNsfw && metadata.nsfw, obscureNsfw = this.props.obscureNsfw && metadata.nsfw,
title = metadata.title ? metadata.title : ('lbry://' + this.props.name); title = metadata.title ? metadata.title : ('lbry://' + this.props.name);
return ( return (
<section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}> <section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className="row-fluid card-content file-tile__row"> <div className="row-fluid card-content file-tile__row">
@ -116,7 +106,7 @@ export let FileTileStream = React.createClass({
</TruncatedText> </TruncatedText>
</a> </a>
</h3> </h3>
<FileActions streamName={this.props.name} metadata={metadata} /> <FileActions streamName={this.props.name} sdHash={this.props.sdHash} metadata={metadata} />
<p className="file-tile__description"> <p className="file-tile__description">
<TruncatedText lines={3}> <TruncatedText lines={3}>
{metadata.description} {metadata.description}
@ -141,7 +131,7 @@ export let FileTile = React.createClass({
_isMounted: false, _isMounted: false,
propTypes: { propTypes: {
name: React.PropTypes.string name: React.PropTypes.string.isRequired
}, },
getInitialState: function() { getInitialState: function() {
@ -167,7 +157,7 @@ export let FileTile = React.createClass({
this._isMounted = false; this._isMounted = false;
}, },
render: function() { render: function() {
if (this.state.metadata === null || this.state.sdHash === null) { if (!this.state.metadata || !this.state.sdHash) {
return null; return null;
} }

View file

@ -472,43 +472,37 @@ lbry._updateClaimOwnershipCache = function(claimId) {
}); });
}; };
lbry._updateSubscribedFileInfoByName = function(name) { lbry._updateSubscribedFileInfo = function(sdHash) {
lbry.getFileInfoByName(name, (fileInfo) => { lbry.getFileInfoBySdHash(sdHash, (fileInfo) => {
if (fileInfo) { if (fileInfo) {
if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) {
lbry._updateClaimOwnershipCache(fileInfo.claim_id); lbry._updateClaimOwnershipCache(fileInfo.claim_id);
} }
fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id];
} }
Object.keys(this._fileInfoSubscribeCallbacks[name]).forEach(function(subscribeId) { Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).forEach(function(subscribeId) {
lbry._fileInfoSubscribeCallbacks[name][subscribeId](fileInfo); lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId](fileInfo);
}); });
}); });
if (Object.keys(this._fileInfoSubscribeCallbacks[name]).length) { if (Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).length) {
setTimeout(() => { setTimeout(() => {
this._updateSubscribedFileInfoByName(name) this._updateSubscribedFileInfo(sdHash)
}, lbry._fileInfoSubscribeInterval); }, lbry._fileInfoSubscribeInterval);
} }
} }
lbry.fileInfoSubscribeByName = function(name, callback) { lbry.fileInfoSubscribe = function(sdHash, callback) {
if (!lbry._fileInfoSubscribeCallbacks[name]) if (!lbry._fileInfoSubscribeCallbacks[sdHash])
{ {
lbry._fileInfoSubscribeCallbacks[name] = {}; lbry._fileInfoSubscribeCallbacks[sdHash] = {};
} }
const subscribeId = ++lbry._fileInfoSubscribeIdCounter; const subscribeId = ++lbry._fileInfoSubscribeIdCounter;
lbry._fileInfoSubscribeCallbacks[name][subscribeId] = callback; lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId] = callback;
lbry._updateSubscribedFileInfoByName(name); lbry._updateSubscribedFileInfo(sdHash);
return subscribeId; 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) { lbry.fileInfoUnsubscribe = function(name, subscribeId) {
delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; delete lbry._fileInfoSubscribeCallbacks[name][subscribeId];
} }

194
js/page/file-list.js Normal file
View file

@ -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 (
<main className="page">
<BusyMessage message="Loading" />
</main>
);
} else if (!this.state.fileInfos.length) {
return (
<main className="page">
<span>You haven't downloaded anything from LBRY yet. Go <Link href="/" label="search for your first download" />!</span>
</main>
);
} else {
return (
<main className="page">
<FileList fileInfos={this.state.fileInfos} />
</main>
);
}
}
});
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 (
<main className="page">
<BusyMessage message="Loading" />
</main>
);
}
else if (!this.state.fileInfos.length) {
return (
<main className="page">
<span>You haven't published anything to LBRY yet.</span> Try <Link href="/?publish" label="publishing" />!
</main>
);
}
else {
return (
<main className="page">
<FileList fileInfos={this.state.fileInfos} />
</main>
);
}
}
});
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(<FileTileStream key={lbry_uri} name={lbry_uri} hideOnRemove={true} sdHash={sd_hash} hidePrice={true} metadata={metadata} />);
}
return (
<section>
<span className='sort-section'>
Sort by { ' ' }
<FormField type="select" onChange={this.handleSortChanged}>
<option value="date">Date</option>
<option value="title">Title</option>
<option value="filename">File name</option>
</FormField>
</span>
{content}
</section>
);
}
});

View file

@ -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 (
<main className="page">
<BusyMessage message="Loading" />
</main>
);
} else if (!this.state.filesInfo.length) {
return (
<main className="page">
{this.props.show == 'downloaded'
? <span>You haven't downloaded anything from LBRY yet. Go <Link href="/" label="search for your first download" />!</span>
: <span>You haven't published anything to LBRY yet.</span>}
</main>
);
} 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(<FileTileStream name={lbry_uri} sdHash={sd_hash} hideOnRemove={true} hidePrice={true} metadata={metadata} />);
}
}
return (
<main className="page">
<span className='sort-section'>
Sort by { ' ' }
<FormField type="select" onChange={this.handleSortChanged}>
<option value="date">Date</option>
<option value="title">Title</option>
<option value="filename">File name</option>
</FormField>
</span>
{content}
</main>
);
}
});

View file

@ -347,12 +347,6 @@ input[type="text"], input[type="search"]
margin: 0px 6px; margin: 0px 6px;
} }
.error-modal__error-list {
border: 1px solid #eee;
padding: 8px;
list-style: none;
}
.error-modal-overlay { .error-modal-overlay {
background: rgba(#000, .88); background: rgba(#000, .88);
} }
@ -371,12 +365,15 @@ input[type="text"], input[type="search"]
word-break: break-all; word-break: break-all;
} }
.error-modal { .error-modal {
max-width: none; max-width: none;
width: 400px; width: 400px;
} }
.error-modal__error-list { /*shitty hack/temp fix for long errors making modals unusable*/ .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-height: 400px;
max-width: 400px;
overflow-y: hidden; overflow-y: hidden;
} }