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 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 <ReportPage />;
case 'downloaded':
return <MyFilesPage show="downloaded" />;
return <FileListDownloaded />;
case 'published':
return <MyFilesPage show="published" />;
return <FileListPublished />;
case 'start':
return <StartPage />;
case 'claim':

View file

@ -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() {

View file

@ -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 (
<section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className="row-fluid card-content file-tile__row">
@ -116,7 +106,7 @@ export let FileTileStream = React.createClass({
</TruncatedText>
</a>
</h3>
<FileActions streamName={this.props.name} metadata={metadata} />
<FileActions streamName={this.props.name} sdHash={this.props.sdHash} metadata={metadata} />
<p className="file-tile__description">
<TruncatedText lines={3}>
{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;
}

View file

@ -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];
}

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;
}
.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;
}