Merge pull request #141 from lbryio/hide-unavailable-files

Add setting to hide unavailable content
This commit is contained in:
alexliebowitz 2017-01-22 01:56:13 -05:00 committed by GitHub
commit f3c951187d
15 changed files with 190 additions and 108 deletions

View file

@ -50,31 +50,6 @@ export let BusyMessage = React.createClass({
} }
}); });
var toolTipStyle = {
position: 'absolute',
zIndex: '1',
top: '100%',
left: '-120px',
width: '260px',
padding: '15px',
border: '1px solid #aaa',
backgroundColor: '#fff',
fontSize: '14px',
};
export let ToolTip = React.createClass({
propTypes: {
open: React.PropTypes.bool.isRequired,
onMouseOut: React.PropTypes.func
},
render: function() {
return (
<div className={this.props.open ? '' : 'hidden'} style={toolTipStyle} onMouseOut={this.props.onMouseOut}>
{this.props.children}
</div>
);
}
});
var creditAmountStyle = { var creditAmountStyle = {
color: '#216C2A', color: '#216C2A',
fontWeight: 'bold', fontWeight: 'bold',

View file

@ -4,6 +4,7 @@ import {Link} from '../component/link.js';
import {Icon} from '../component/common.js'; import {Icon} from '../component/common.js';
import Modal from './modal.js'; import Modal from './modal.js';
import FormField from './form.js'; import FormField from './form.js';
import {ToolTip} from '../component/tooltip.js';
import {DropDownMenu, DropDownMenuItem} from './menu.js'; import {DropDownMenu, DropDownMenuItem} from './menu.js';
let WatchLink = React.createClass({ let WatchLink = React.createClass({
@ -50,18 +51,14 @@ let WatchLink = React.createClass({
} }
}); });
export let FileActions = React.createClass({ let FileActionsRow = React.createClass({
_isMounted: false, _isMounted: false,
_fileInfoSubscribeId: null, _fileInfoSubscribeId: null,
propTypes: { propTypes: {
streamName: React.PropTypes.string, streamName: React.PropTypes.string,
sdHash: React.PropTypes.string.isRequired, sdHash: React.PropTypes.string.isRequired,
metadata: React.PropTypes.object, metadata: React.PropTypes.object
path: React.PropTypes.string,
hidden: React.PropTypes.bool,
deleteChecked: React.PropTypes.bool,
onRemove: React.PropTypes.func,
}, },
getInitialState: function() { getInitialState: function() {
return { return {
@ -70,7 +67,7 @@ export let FileActions = React.createClass({
menuOpen: false, menuOpen: false,
deleteChecked: false, deleteChecked: false,
attemptingDownload: false, attemptingDownload: false,
attemptingRemove: false, attemptingRemove: false
} }
}, },
onFileInfoUpdate: function(fileInfo) { onFileInfoUpdate: function(fileInfo) {
@ -166,10 +163,11 @@ export let FileActions = React.createClass({
render: function() { render: function() {
if (this.state.fileInfo === null) if (this.state.fileInfo === null)
{ {
return <section className="file-actions--stub"></section>; return null;
} }
const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
showMenu = !!this.state.fileInfo; showMenu = !!this.state.fileInfo;
let linkBlock; let linkBlock;
if (this.state.fileInfo === false && !this.state.attemptingDownload) { if (this.state.fileInfo === false && !this.state.attemptingDownload) {
@ -180,20 +178,21 @@ export let FileActions = React.createClass({
label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
labelWithIcon = <span className="button__content"><Icon icon="icon-download" />{label}</span>; labelWithIcon = <span className="button__content"><Icon icon="icon-download" />{label}</span>;
linkBlock = linkBlock = (
<div className="faux-button-block file-actions__download-status-bar"> <div className="faux-button-block file-actions__download-status-bar">
<div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div> <div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div>
{labelWithIcon} {labelWithIcon}
</div>; </div>
);
} else { } else {
linkBlock = <Link button="text" label="Open" icon="icon-folder-open" onClick={this.onOpenClick} />; linkBlock = <Link button="text" label="Open" icon="icon-folder-open" onClick={this.onOpenClick} />;
} }
return ( return (
<section className="file-actions"> <div>
{(this.props.metadata.content_type && this.props.metadata.content_type.startsWith('video/')) ? <WatchLink streamName={this.props.streamName} /> : null} {(this.props.metadata.content_type && this.props.metadata.content_type.startsWith('video/')) ? <WatchLink streamName={this.props.streamName} /> : null}
{this.state.fileInfo !== null || this.state.fileInfo.isMine ? {this.state.fileInfo !== null || this.state.fileInfo.isMine ?
<div className="button-container">{linkBlock}</div> <div className="button-container">{linkBlock}</div>
: null} : null}
{ showMenu ? { showMenu ?
<DropDownMenu> <DropDownMenu>
@ -215,7 +214,62 @@ export let FileActions = React.createClass({
<label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label> <label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
</Modal> </Modal>
</section> </div>
); );
} }
}); });
export let FileActions = React.createClass({
_isMounted: false,
_fileInfoSubscribeId: null,
propTypes: {
streamName: React.PropTypes.string,
sdHash: React.PropTypes.string.isRequired,
metadata: React.PropTypes.object
},
getInitialState: function() {
return {
available: true,
forceShowActions: false,
}
},
onShowFileActionsRowClicked: function() {
this.setState({
forceShowActions: true,
});
},
componentDidMount: function() {
this._isMounted = true;
lbry.getPeersForBlobHash(this.props.sdHash, (peers) => {
if (!this._isMounted) {
return;
}
this.setState({
available: peers.length > 0,
});
});
},
componentWillUnmount: function() {
this._isMounted = false;
},
render: function() {
return (<section className="file-actions">
{
this.state.available || this.state.forceShowActions ?
<FileActionsRow sdHash={this.props.sdHash} metadata={this.props.metadata} streamName={this.props.streamName} /> :
(<div>
<div className="button-container empty">This file is not currently available.</div>
<div className="button-container">
<ToolTip label="Why?"
body="The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment" />
</div>
<div className="button-container">
<Link label="Try Anyway" className="button-text" onClick={this.onShowFileActionsRowClicked} />
</div>
</div>)
}
</section>);
}
});

View file

@ -64,7 +64,8 @@ export let FileTileStream = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
showNsfwHelp: false, showNsfwHelp: false,
isHidden: false isHidden: false,
available: null,
} }
}, },
getDefaultProps: function() { getDefaultProps: function() {
@ -112,11 +113,10 @@ export let FileTileStream = React.createClass({
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"}>
<div className="span3"> <div className="span3">
<a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.name)} /></a> <a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.name)} /></a>
</div> </div>
@ -157,7 +157,8 @@ export let FileTile = React.createClass({
_isMounted: false, _isMounted: false,
propTypes: { propTypes: {
name: React.PropTypes.string.isRequired name: React.PropTypes.string.isRequired,
available: React.PropTypes.bool,
}, },
getInitialState: function() { getInitialState: function() {
@ -187,6 +188,6 @@ export let FileTile = React.createClass({
return null; return null;
} }
return <FileTileStream name={this.props.name} sdHash={this.state.sdHash} metadata={this.state.metadata} />; return <FileTileStream sdHash={this.state.sdHash} metadata={this.state.metadata} {... this.props} />;
} }
}); });

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {Icon, ToolTip} from './common.js'; import {Icon} from './common.js';
export let Link = React.createClass({ export let Link = React.createClass({
propTypes: { propTypes: {
@ -51,54 +51,4 @@ export let Link = React.createClass({
</a> </a>
); );
} }
}); });
var linkContainerStyle = {
position: 'relative',
};
export let ToolTipLink = React.createClass({
getInitialState: function() {
return {
showTooltip: false,
};
},
handleClick: function() {
if (this.props.tooltip) {
this.setState({
showTooltip: !this.state.showTooltip,
});
}
if (this.props.onClick) {
this.props.onClick();
}
},
handleTooltipMouseOut: function() {
this.setState({
showTooltip: false,
});
},
render: function() {
var href = this.props.href ? this.props.href : 'javascript:;',
icon = this.props.icon ? <Icon icon={this.props.icon} /> : '',
className = this.props.className +
(this.props.button ? ' button-block button-' + this.props.button : '') +
(this.props.hidden ? ' hidden' : '') +
(this.props.disabled ? ' disabled' : '');
return (
<span style={linkContainerStyle}>
<a className={className ? className : 'button-text'} href={href} style={this.props.style ? this.props.style : {}}
title={this.props.title} onClick={this.handleClick}>
{this.props.icon ? icon : '' }
{this.props.label}
</a>
{(!this.props.tooltip ? null :
<ToolTip open={this.state.showTooltip} onMouseOut={this.handleTooltipMouseOut}>
{this.props.tooltip}
</ToolTip>
)}
</span>
);
}
});

36
js/component/tooltip.js Normal file
View file

@ -0,0 +1,36 @@
import React from 'react';
export let ToolTip = React.createClass({
propTypes: {
body: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired
},
getInitialState: function() {
return {
showTooltip: false,
};
},
handleClick: function() {
this.setState({
showTooltip: !this.state.showTooltip,
});
},
handleTooltipMouseOut: function() {
this.setState({
showTooltip: false,
});
},
render: function() {
return (
<span className={'tooltip ' + (this.props.className || '')}>
<a className="tooltip__link" onClick={this.handleClick}>
{this.props.label}
</a>
<div className={'tooltip__body ' + (this.state.showTooltip ? '' : ' hidden')}
onMouseOut={this.handleTooltipMouseOut}>
{this.props.body}
</div>
</span>
);
}
});

View file

@ -10,6 +10,7 @@ var lbry = {
}, },
defaultClientSettings: { defaultClientSettings: {
showNsfw: false, showNsfw: false,
showUnavailable: true,
debug: false, debug: false,
useCustomLighthouseServers: false, useCustomLighthouseServers: false,
customLighthouseServers: [], customLighthouseServers: [],

View file

@ -2,7 +2,8 @@ import React from 'react';
import lbry from '../lbry.js'; import lbry from '../lbry.js';
import lighthouse from '../lighthouse.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 {Link} from '../component/link.js';
import {ToolTip} from '../component/tooltip.js';
import {BusyMessage} from '../component/common.js'; import {BusyMessage} from '../component/common.js';
var fetchResultsStyle = { var fetchResultsStyle = {
@ -47,7 +48,7 @@ var SearchResults = React.createClass({
if (!seenNames[name]) { if (!seenNames[name]) {
seenNames[name] = name; seenNames[name] = name;
rows.push( rows.push(
<FileTile key={name} name={name} /> <FileTile key={name} name={name} sdHash={value.sources.lbry_sd_hash} />
); );
} }
}); });
@ -65,6 +66,9 @@ var featuredContentLegendStyle = {
var FeaturedContent = React.createClass({ var FeaturedContent = React.createClass({
render: function() { render: function() {
const toolTipText = ('Community Content is a public space where anyone can share content with the ' +
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
'"five" to put your content here!');
return ( return (
<div className="row-fluid"> <div className="row-fluid">
<div className="span6"> <div className="span6">
@ -77,8 +81,10 @@ var FeaturedContent = React.createClass({
</div> </div>
<div className="span6"> <div className="span6">
<h3>Community Content <ToolTipLink style={featuredContentLegendStyle} label="What's this?" <h3>
tooltip='Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!' /></h3> Community Content
<ToolTip label="What's this?" body={toolTipText} className="tooltip--header"/>
</h3>
<FileTile name="one" /> <FileTile name="one" />
<FileTile name="two" /> <FileTile name="two" />
<FileTile name="three" /> <FileTile name="three" />

View file

@ -51,7 +51,8 @@ var SettingsPage = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
settings: null, settings: null,
showNsfw: lbry.getClientSetting('showNsfw') showNsfw: lbry.getClientSetting('showNsfw'),
showUnavailable: lbry.getClientSetting('showUnavailable'),
} }
}, },
componentDidMount: function() { componentDidMount: function() {
@ -69,6 +70,9 @@ var SettingsPage = React.createClass({
onShowNsfwChange: function(event) { onShowNsfwChange: function(event) {
lbry.setClientSetting('showNsfw', event.target.checked); lbry.setClientSetting('showNsfw', event.target.checked);
}, },
onShowUnavailableChange: function(event) {
lbry.setClientSetting('showUnavailable', event.target.checked);
},
render: function() { render: function() {
if (!this.state.daemonSettings) { if (!this.state.daemonSettings) {
return null; return null;
@ -114,7 +118,7 @@ var SettingsPage = React.createClass({
<h3>Content</h3> <h3>Content</h3>
<div className="form-row"> <div className="form-row">
<label style={settingsCheckBoxOptionStyles}> <label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShowNsfwChange} defaultChecked={this.state.showNsfw} /> Show NSFW Content <input type="checkbox" onChange={this.onShowNsfwChange} defaultChecked={this.state.showNsfw} /> Show NSFW content
</label> </label>
<div className="help"> <div className="help">
NSFW content may include nudity, intense sexuality, profanity, or other adult content. NSFW content may include nudity, intense sexuality, profanity, or other adult content.
@ -122,6 +126,17 @@ var SettingsPage = React.createClass({
</div> </div>
</div> </div>
</section> </section>
<section className="card">
<h3>Search</h3>
<div className="form-row">
<div className="help">
Would you like search results to include items that are not currently available for download?
</div>
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShowUnavailableChange} defaultChecked={this.state.showUnavailable} /> Show unavailable content in search results
</label>
</div>
</section>
<section className="card"> <section className="card">
<h3>Share Diagnostic Data</h3> <h3>Share Diagnostic Data</h3>
<label style={settingsCheckBoxOptionStyles}> <label style={settingsCheckBoxOptionStyles}>

View file

@ -8,7 +8,7 @@ html
body body
{ {
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
line-height: 1.3333; line-height: $font-line-height;
} }
$drawer-width: 240px; $drawer-width: 240px;

View file

@ -8,6 +8,7 @@ $color-primary: #155B4A;
$color-light-alt: hsl(hue($color-primary), 15, 85); $color-light-alt: hsl(hue($color-primary), 15, 85);
$color-text-dark: #000; $color-text-dark: #000;
$color-help: rgba(0,0,0,.6); $color-help: rgba(0,0,0,.6);
$color-notice: #921010;
$color-canvas: #f5f5f5; $color-canvas: #f5f5f5;
$color-bg: #ffffff; $color-bg: #ffffff;
$color-bg-alt: #D9D9D9; $color-bg-alt: #D9D9D9;
@ -15,6 +16,7 @@ $color-money: #216C2A;
$color-meta-light: #505050; $color-meta-light: #505050;
$font-size: 16px; $font-size: 16px;
$font-line-height: 1.3333;
$mobile-width-threshold: 801px; $mobile-width-threshold: 801px;
$max-content-width: 1000px; $max-content-width: 1000px;

View file

@ -1,7 +1,7 @@
@import "global"; @import "global";
@mixin text-link($color: $color-primary, $hover-opacity: 0.70) { @mixin text-link($color: $color-primary, $hover-opacity: 0.70) {
color: $color;
.icon .icon
{ {
&:first-child { &:first-child {
@ -29,6 +29,7 @@
} }
color: $color; color: $color;
cursor: pointer;
} }
.icon-fixed-width { .icon-fixed-width {
@ -200,7 +201,7 @@ input[type="text"], input[type="search"]
} }
.button-text-help .button-text-help
{ {
@include text-link(#5b8c80); @include text-link(#aaa);
font-size: 0.8em; font-size: 0.8em;
} }

View file

@ -8,4 +8,5 @@
@import "component/_file-actions.scss"; @import "component/_file-actions.scss";
@import "component/_file-tile.scss"; @import "component/_file-tile.scss";
@import "component/_menu.scss"; @import "component/_menu.scss";
@import "component/_tooltip.scss";
@import "page/_developer.scss"; @import "page/_developer.scss";

View file

@ -2,9 +2,10 @@
$color-download: #444; $color-download: #444;
.file-actions--stub .file-actions
{ {
height: $height-button; line-height: $height-button;
min-height: $height-button;
} }
.file-actions__download-status-bar .file-actions__download-status-bar

View file

@ -4,6 +4,10 @@
height: $spacing-vertical * 7; height: $spacing-vertical * 7;
} }
.file-tile__row--unavailable {
opacity: 0.5;
}
.file-tile__thumbnail { .file-tile__thumbnail {
max-width: 100%; max-width: 100%;
max-height: $spacing-vertical * 7; max-height: $spacing-vertical * 7;

View file

@ -0,0 +1,35 @@
@import "../global";
.tooltip {
position: relative;
}
.tooltip__link {
@include text-link();
}
.tooltip__body {
$tooltip-body-width: 300px;
position: absolute;
z-index: 1;
left: 50%;
margin-left: $tooltip-body-width * -1 / 2;
box-sizing: border-box;
padding: $spacing-vertical / 2;
width: $tooltip-body-width;
border: 1px solid #aaa;
color: $color-text-dark;
background-color: $color-bg;
font-size: $font-size * 7/8;
line-height: $font-line-height;
box-shadow: $default-box-shadow;
}
.tooltip--header .tooltip__link {
@include text-link(#aaa);
font-size: $font-size * 3/4;
margin-left: $padding-button;
vertical-align: middle;
}