big refactor of file actions/buttons/tiles
This commit is contained in:
parent
95675cd802
commit
b7f23aa0dd
17 changed files with 563 additions and 605 deletions
|
@ -152,6 +152,6 @@ export let Thumbnail = React.createClass({
|
|||
this._isMounted = false;
|
||||
},
|
||||
render: function() {
|
||||
return <img ref="img" onError={this.handleError} {... this.props} src={this.state.imageUri} />
|
||||
return <img ref="img" onError={this.handleError} {... this.props} src={this.state.imageUri} />
|
||||
},
|
||||
});
|
||||
|
|
229
js/component/file-actions.js
Normal file
229
js/component/file-actions.js
Normal file
|
@ -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 (
|
||||
<div className="button-container">
|
||||
<Link button="primary" disabled={this.state.loading} label="Watch" icon="icon-play" onClick={this.handleClick} />
|
||||
<Modal contentLabel="Not enough credits" isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
|
||||
You don't have enough LBRY credits to pay for this stream.
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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 <section className="file-actions--stub"></section>;
|
||||
}
|
||||
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 = <Link button="text" label="Download" icon="icon-download" onClick={this.onDownloadClick} />;
|
||||
} 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 = <span><Icon icon="icon-download" />{label}</span>;
|
||||
|
||||
linkBlock =
|
||||
<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>
|
||||
{labelWithIcon}
|
||||
</div>;
|
||||
} else {
|
||||
linkBlock = <Link button="text" label="Open" icon="icon-folder-open" onClick={this.onOpenClick} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="file-actions">
|
||||
{this.props.metadata.content_type.startsWith('video/') ? <WatchLink streamName={this.props.streamName} /> : null}
|
||||
{this.state.fileInfo !== null || this.state.fileInfo.isMine ?
|
||||
<div className="button-container">{linkBlock}</div>
|
||||
: null}
|
||||
{ showMenu ?
|
||||
<DropDownMenu>
|
||||
<DropDownMenuItem key={0} onClick={this.handleRevealClicked} label={openInFolderMessage} />
|
||||
<DropDownMenuItem key={1} onClick={this.handleRemoveClicked} label="Remove..." />
|
||||
</DropDownMenu> : '' }
|
||||
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
||||
onConfirmed={this.closeModal}>
|
||||
You don't have enough LBRY credits to pay for this stream.
|
||||
</Modal>
|
||||
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
|
||||
onConfirmed={this.closeModal}>
|
||||
LBRY was unable to download the stream <strong>lbry://{this.props.streamName}</strong>.
|
||||
</Modal>
|
||||
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
|
||||
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
|
||||
onAborted={this.closeModal}>
|
||||
<p>Are you sure you'd like to remove <cite>{this.props.metadata.title}</cite> from LBRY?</p>
|
||||
|
||||
<label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -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 (
|
||||
<span className="file-tile__cost">
|
||||
<CreditAmount amount={this.state.cost} isEstimate={!this.state.costIncludesData}/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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 (
|
||||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') + (this.props.compact ? 'file-tile--compact' : '')} 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="span3">
|
||||
<a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={this.props.metadata.thumbnail} alt={'Photo for ' + (this.props.metadata.title || this.props.name)} /></a>
|
||||
<a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={this.state.metadata.thumbnail} alt={'Photo for ' + (this.state.metadata.title || this.props.name)} /></a>
|
||||
</div>
|
||||
<div className="span9">
|
||||
{this.state.cost !== null && !this.state.local
|
||||
? <span className="file-tile__cost">
|
||||
<CreditAmount amount={this.state.cost} isEstimate={!this.state.costIncludesData}/>
|
||||
</span>
|
||||
{ this.props.showPrice
|
||||
? <FilePrice name={this.props.name} />
|
||||
: null}
|
||||
<div className="meta"><a href={'/?show=' + this.props.name}>lbry://{this.props.name}</a></div>
|
||||
<h3 className={'file-tile__title ' + (this.props.compact ? 'file-tile__title--compact' : '')}>
|
||||
<h3 className="file-tile__title">
|
||||
<a href={'/?show=' + this.props.name}>
|
||||
<TruncatedText lines={3}>
|
||||
{this.props.metadata.title}
|
||||
<TruncatedText lines={2}>
|
||||
{this.state.metadata.title}
|
||||
</TruncatedText>
|
||||
</a>
|
||||
</h3>
|
||||
<div>
|
||||
{this.props.metadata.content_type.startsWith('video/') ? <WatchLink streamName={this.props.name} button="primary" /> : null}
|
||||
{!this.props.isMine
|
||||
? <DownloadLink streamName={this.props.name} metadata={this.props.metadata} button="text"
|
||||
onRemoveConfirmed={this.handleRemoveConfirmed} {... downloadLinkExtraProps}/>
|
||||
: null}
|
||||
</div>
|
||||
<FileActions streamName={this.props.name} metadata={this.state.metadata} />
|
||||
<p className="file-tile__description">
|
||||
<TruncatedText lines={3}>
|
||||
{this.props.metadata.description}
|
||||
{this.state.metadata.description}
|
||||
</TruncatedText>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{obscureNsfw && this.state.isHovered
|
||||
{this.state.showNsfwHelp
|
||||
? <div className='card-overlay'>
|
||||
<p>
|
||||
This content is Not Safe For Work.
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
<Link {...other}>
|
||||
<span className="link-label">{this.props.label}</span>
|
||||
<Icon icon="icon-caret-down" fixed={true} onClick={this.handleCaretClicked} />
|
||||
</Link>
|
||||
{this.state.menuOpen
|
||||
? <Menu onClickOut={this.closeMenu}>
|
||||
{this.props.children}
|
||||
</Menu>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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 = [
|
||||
<MenuItem key={0} onClick={this.handleRevealClicked} label={openInFolderMessage} />,
|
||||
<MenuItem key={1} onClick={this.handleRemoveClicked} label="Remove..." />,
|
||||
];
|
||||
|
||||
let linkBlock;
|
||||
if (this.state.attemptingRemove || this.props.state == 'not-started') {
|
||||
linkBlock = <Link button="text" label="Download" icon="icon-download" onClick={this.handleClick} />;
|
||||
} else if (this.state.attemptingDownload) {
|
||||
linkBlock = <Link button="text" className="button-download button-download--bg"
|
||||
label="Connecting..." icon="icon-download" />
|
||||
} else if (this.props.state == 'downloading') {
|
||||
const label = `${parseInt(this.props.progress * 100)}% complete`;
|
||||
linkBlock = (
|
||||
<span>
|
||||
<DropDown button="download" className="button-download--bg" label={label} icon="icon-download"
|
||||
onClick={this.handleClick}>
|
||||
{dropDownItems}
|
||||
</DropDown>
|
||||
<DropDown button="download" className="button-download--fg" label={label} icon="icon-download"
|
||||
onClick={this.handleClick} style={{width: `${this.props.progress * 100}%`}}>
|
||||
{dropDownItems}
|
||||
</DropDown>
|
||||
</span>
|
||||
);
|
||||
} else if (this.props.state == 'done') {
|
||||
linkBlock = (
|
||||
<DropDown button="alt" label="Open" onClick={this.handleClick} onCaretClick={this.openMenu}>
|
||||
{dropDownItems}
|
||||
</DropDown>
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unknown download state ${this.props.state} passed to DownloadLink`);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="button-container">
|
||||
{linkBlock}
|
||||
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
||||
onConfirmed={this.closeModal}>
|
||||
You don't have enough LBRY credits to pay for this stream.
|
||||
</Modal>
|
||||
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
|
||||
onConfirmed={this.closeModal}>
|
||||
LBRY was unable to download the stream <strong>lbry://{this.props.streamName}</strong>.
|
||||
</Modal>
|
||||
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
|
||||
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
|
||||
onAborted={this.closeModal}>
|
||||
<p>Are you sure you'd like to remove <cite>{this.props.metadata.title}</cite> from LBRY?</p>
|
||||
|
||||
<label><FormField type="checkbox" checked={this.state.deleteChecked} onClick={this.handleDeleteCheckboxClicked} /> Delete this file from my computer</label>
|
||||
</Modal>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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 (
|
||||
<div className="button-container">
|
||||
<Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
|
||||
disabled={this.state.loading} label={this.props.label} icon={this.props.icon}
|
||||
onClick={this.handleClick} />
|
||||
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
||||
onConfirmed={this.closeModal}>
|
||||
You don't have enough LBRY credits to pay for this stream.
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
<div ref={(div) => this._div = div} className={'menu ' + (this.props.className || '')}
|
||||
{... other}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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 ? <Icon icon={this.props.icon} fixed /> : null);
|
||||
|
||||
return (
|
||||
<a className="button-text menu__menu-item" onClick={this.props.onClick}
|
||||
<a className="menu__menu-item" onClick={this.props.onClick}
|
||||
href={this.props.href || 'javascript:'} label={this.props.label}>
|
||||
{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 (
|
||||
<div className="button-container">
|
||||
<Link ref={(span) => this._menuButton = span} icon="icon-ellipsis-v" onClick={this.onMenuIconClick} />
|
||||
{this.state.menuOpen
|
||||
? <div ref={(div) => this._menuDiv = div} className="menu">
|
||||
{this.props.children}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
53
js/lbry.js
53
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;
|
||||
|
|
|
@ -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 (<div style={featuredContentItemContainerStyle}>
|
||||
<FileTile name={this.props.name} metadata={this.state.metadata} compact />
|
||||
</div>);
|
||||
}
|
||||
});
|
||||
|
||||
var featuredContentLegendStyle = {
|
||||
fontSize: '12px',
|
||||
color: '#aaa',
|
||||
|
@ -116,21 +65,21 @@ var FeaturedContent = React.createClass({
|
|||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<h3>Featured Content</h3>
|
||||
<FeaturedContentItem name="bellflower" />
|
||||
<FeaturedContentItem name="itsadisaster" />
|
||||
<FeaturedContentItem name="dopeman" />
|
||||
<FeaturedContentItem name="smlawncare" />
|
||||
<FeaturedContentItem name="cinemasix" />
|
||||
<FileTile name="bellflower" />
|
||||
<FileTile name="itsadisaster" />
|
||||
<FileTile name="dopeman" />
|
||||
<FileTile name="smlawncare" />
|
||||
<FileTile name="cinemasix" />
|
||||
|
||||
</div>
|
||||
<div className="span6">
|
||||
<h3>Community Content <ToolTipLink style={featuredContentLegendStyle} label="What's this?"
|
||||
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>
|
||||
<FeaturedContentItem name="one" />
|
||||
<FeaturedContentItem name="two" />
|
||||
<FeaturedContentItem name="three" />
|
||||
<FeaturedContentItem name="four" />
|
||||
<FeaturedContentItem name="five" />
|
||||
<FileTile name="one" />
|
||||
<FileTile name="two" />
|
||||
<FileTile name="three" />
|
||||
<FileTile name="four" />
|
||||
<FileTile name="five" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -178,7 +178,7 @@ var MyFilesPage = React.createClass({
|
|||
|
||||
seenUris[lbry_uri] = true;
|
||||
|
||||
content.push(<FileTile name={lbry_uri} sdHash={sd_hash} isMine={this.props.show == 'published'} local={true} hideOnRemove={true}
|
||||
content.push(<FileTile name={lbry_uri} sdHash={sd_hash} isMine={this.props.show == 'published'} showPrice={false} hideOnRemove={true}
|
||||
metadata={metadata} completed={completed} stopped={stopped} pending={pending} path={download_path}
|
||||
{... this.state.filesAvailable !== null ? {available: this.state.filesAvailable[sd_hash]} : {}} />);
|
||||
}
|
||||
|
@ -199,5 +199,4 @@ var MyFilesPage = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
export default MyFilesPage;
|
||||
export default MyFilesPage;
|
|
@ -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({
|
|||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<section>
|
||||
{mediaType == 'video' ? <WatchLink streamName={this.props.name} button="primary" /> : null}
|
||||
<DownloadLink streamName={this.props.name} button="alt" />
|
||||
</section>
|
||||
<FileActions />
|
||||
<section>
|
||||
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
|
||||
</section>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
108
scss/_gui.scss
108
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;
|
||||
}
|
||||
|
|
|
@ -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";
|
29
scss/component/_file-actions.scss
Normal file
29
scss/component/_file-actions.scss
Normal file
|
@ -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;
|
||||
}
|
27
scss/component/_file-tile.scss
Normal file
27
scss/component/_file-tile.scss
Normal file
|
@ -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;
|
||||
}
|
21
scss/component/_menu.scss
Normal file
21
scss/component/_menu.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
@import "../global";
|
||||
|
||||
table.table-standard {
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
Loading…
Reference in a new issue