diff --git a/js/app.js b/js/app.js index c87017c28..68c03a648 100644 --- a/js/app.js +++ b/js/app.js @@ -4,7 +4,6 @@ import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; import ReportPage from './page/report.js'; -import MyFilesPage from './page/my_files.js'; import StartPage from './page/start.js'; import ClaimCodePage from './page/claim_code.js'; import ReferralPage from './page/referral.js'; @@ -14,6 +13,7 @@ import PublishPage from './page/publish.js'; import DiscoverPage from './page/discover.js'; import SplashScreen from './component/splash.js'; import DeveloperPage from './page/developer.js'; +import {FileListDownloaded, FileListPublished} from './page/file-list.js'; import Drawer from './component/drawer.js'; import Header from './component/header.js'; import Modal from './component/modal.js'; @@ -164,9 +164,9 @@ var App = React.createClass({ case 'report': return ; case 'downloaded': - return ; + return ; case 'published': - return ; + return ; case 'start': return ; case 'claim': @@ -190,7 +190,8 @@ var App = React.createClass({ }, render: function() { var mainContent = this.getMainContent(), - headerLinks = this.getHeaderLinks(); + headerLinks = this.getHeaderLinks(), + searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : ''; return ( this.state.viewingPage == 'watch' ? @@ -198,7 +199,7 @@ var App = React.createClass({
-
+
{mainContent}
+ const {fixed, className, ...other} = this.props; + const spanClassName = ('icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') + + this.props.icon + ' ' + (this.props.className || '')); + return } }); @@ -123,7 +124,7 @@ export let Thumbnail = React.createClass({ _isMounted: false, propTypes: { - src: React.PropTypes.string.isRequired, + src: React.PropTypes.string, }, handleError: function() { if (this.state.imageUrl != this._defaultImageUri) { @@ -151,6 +152,6 @@ export let Thumbnail = React.createClass({ this._isMounted = false; }, render: function() { - return + return }, }); diff --git a/js/component/file-actions.js b/js/component/file-actions.js new file mode 100644 index 000000000..9e9ccc12e --- /dev/null +++ b/js/component/file-actions.js @@ -0,0 +1,221 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import {Link} from '../component/link.js'; +import {Icon} from '../component/common.js'; +import Modal from './modal.js'; +import FormField from './form.js'; +import {DropDownMenu, DropDownMenuItem} from './menu.js'; + +let WatchLink = React.createClass({ + propTypes: { + streamName: React.PropTypes.string, + }, + handleClick: function() { + this.setState({ + loading: true, + }) + lbry.getCostInfoForName(this.props.streamName, ({cost}) => { + lbry.getBalance((balance) => { + if (cost > balance) { + this.setState({ + modal: 'notEnoughCredits', + loading: false, + }); + } else { + window.location = '?watch=' + this.props.streamName; + } + }); + }); + }, + getInitialState: function() { + return { + modal: null, + loading: false, + }; + }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, + render: function() { + return ( +
+ + + You don't have enough LBRY credits to pay for this stream. + +
+ ); + } +}); + +export let FileActions = React.createClass({ + _isMounted: false, + _fileInfoSubscribeId: null, + + propTypes: { + streamName: React.PropTypes.string, + sdHash: React.PropTypes.string.isRequired, + metadata: React.PropTypes.object, + path: React.PropTypes.string, + hidden: React.PropTypes.bool, + deleteChecked: React.PropTypes.bool, + onRemove: React.PropTypes.function, + }, + 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.props.sdHash); + } + }, + handleDeleteCheckboxClicked: function(event) { + this.setState({ + deleteChecked: event.target.checked, + }); + }, + handleRevealClicked: function() { + if (this.state.fileInfo && this.state.fileInfo.download_path) { + lbry.revealFile(this.props.sdHash); + } + }, + handleRemoveClicked: function() { + this.setState({ + modal: 'confirmRemove', + }); + }, + handleRemoveConfirmed: function() { + if (this.props.streamName) { + lbry.removeFile(this.props.sdHash, this.props.streamName, this.state.deleteChecked); + } else { + alert('this file cannot be deleted because lbry is a retarded piece of shit'); + } + this.setState({ + modal: null, + fileInfo: false, + attemptingDownload: false + }); + }, + openMenu: function() { + this.setState({ + menuOpen: !this.state.menuOpen, + }); + }, + componentDidMount: function() { + this._isMounted = true; + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); + }, + componentWillUnmount: function() { + this._isMounted = false; + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); + } + }, + render: function() { + if (this.state.fileInfo === null) + { + return
; + } + const openInFolderMessage = window.navigator.platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder', + showMenu = !!this.state.fileInfo; + + let linkBlock; + if (this.state.fileInfo === false && !this.state.attemptingDownload) { + linkBlock = ; + } else if (this.state.attemptingDownload || !this.state.fileInfo.completed) { + const + progress = this.state.fileInfo ? this.state.fileInfo.written_bytes / this.state.fileInfo.total_bytes * 100 : 0, + label = this.state.fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...', + labelWithIcon = {label}; + + linkBlock = +
+
{labelWithIcon}
+ {labelWithIcon} +
; + } else { + linkBlock = ; + } + + return ( +
+ {this.props.metadata.content_type.startsWith('video/') ? : null} + {this.state.fileInfo !== null || this.state.fileInfo.isMine ? +
{linkBlock}
+ : null} + { showMenu ? + + + + : '' } + + You don't have enough LBRY credits to pay for this stream. + + + LBRY was unable to download the stream lbry://{this.props.streamName}. + + +

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

+ + +
+
+ ); + } +}); \ No newline at end of file diff --git a/js/component/file-tile.js b/js/component/file-tile.js new file mode 100644 index 000000000..42f79d16c --- /dev/null +++ b/js/component/file-tile.js @@ -0,0 +1,192 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import {Link} from '../component/link.js'; +import {FileActions} from '../component/file-actions.js'; +import {Thumbnail, TruncatedText, CreditAmount} from '../component/common.js'; + +let FilePrice = React.createClass({ + _isMounted: false, + + propTypes: { + name: React.PropTypes.string + }, + + getInitialState: function() { + return { + cost: null, + costIncludesData: null, + } + }, + + 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 ( + + + + ); + } +}); + +/*should be merged into FileTile once FileTile is refactored to take a single id*/ +export let FileTileStream = React.createClass({ + _fileInfoSubscribeId: null, + _isMounted: null, + + propTypes: { + metadata: React.PropTypes.object, + sdHash: React.PropTypes.string, + hideOnRemove: React.PropTypes.bool, + hidePrice: React.PropTypes.bool, + obscureNsfw: React.PropTypes.bool + }, + getInitialState: function() { + return { + showNsfwHelp: false, + isHidden: false + } + }, + getDefaultProps: function() { + return { + obscureNsfw: !lbry.getClientSetting('showNsfw'), + hidePrice: false + } + }, + componentDidMount: function() { + this._isMounted = true; + if (this.props.hideOnRemove) { + lbry.fileInfoSubscribe(this.props.sdHash, this.onFileInfoUpdate); + } + }, + componentWillUnmount: function() { + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.sdHash, this._fileInfoSubscribeId); + } + }, + onFileInfoUpdate: function(fileInfo) { + if (!fileInfo && this._isMounted && this.props.hideOnRemove) { + this.setState({ + isHidden: true + }); + } + }, + handleMouseOver: function() { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { + this.setState({ + showNsfwHelp: true, + }); + } + }, + handleMouseOut: function() { + if (this.state.showNsfwHelp) { + this.setState({ + showNsfwHelp: false, + }); + } + }, + render: function() { + if (this.state.isHidden) { + return null; + } + + const metadata = this.props.metadata || {}, + obscureNsfw = this.props.obscureNsfw && metadata.nsfw, + title = metadata.title ? metadata.title : ('lbry://' + this.props.name); + + return ( +
+
+
+ +
+
+ { !this.props.hidePrice + ? + : null} + +

+ + + {title} + + +

+ +

+ + {metadata.description} + +

+
+
+ {this.state.showNsfwHelp + ?
+

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

+
+ : null} +
+ ); + } +}); + +export let FileTile = React.createClass({ + _isMounted: false, + + propTypes: { + name: React.PropTypes.string.isRequired + }, + + getInitialState: function() { + return { + sdHash: null, + metadata: null + } + }, + + componentDidMount: function() { + this._isMounted = true; + + lbry.resolveName(this.props.name, (metadata) => { + if (this._isMounted) { + this.setState({ + sdHash: metadata.sources.lbry_sd_hash, + metadata: metadata, + }); + } + }); + }, + componentWillUnmount: function() { + this._isMounted = false; + }, + render: function() { + if (!this.state.metadata || !this.state.sdHash) { + return null; + } + + return ; + } +}); \ No newline at end of file diff --git a/js/component/header.js b/js/component/header.js index 83fd3748b..fb64e9ece 100644 --- a/js/component/header.js +++ b/js/component/header.js @@ -52,7 +52,7 @@ var Header = React.createClass({

{ this.state.title }

-
@@ -70,7 +70,7 @@ var SubHeader = React.createClass({ render: function() { var links = [], viewingUrl = '?' + this.props.viewingPage; - + for (let link of Object.keys(this.props.links)) { links.push( diff --git a/js/component/link.js b/js/component/link.js index 99ce1e627..4a80ee830 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -1,29 +1,53 @@ import React from 'react'; -import lbry from '../lbry.js'; -import Modal from './modal.js'; import {Icon, ToolTip} from './common.js'; - export let Link = React.createClass({ - handleClick: function() { + propTypes: { + label: React.PropTypes.string, + icon: React.PropTypes.string, + button: React.PropTypes.string, + badge: React.PropTypes.string, + hidden: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + hidden: false, + disabled: false, + }; + }, + handleClick: function(e) { if (this.props.onClick) { - this.props.onClick(); + this.props.onClick(e); } }, render: function() { - var href = this.props.href ? this.props.href : 'javascript:;', - icon = this.props.icon ? : '', - className = (this.props.className ? this.props.className : '') + - (this.props.button ? ' button-block button-' + this.props.button : '') + - (this.props.hidden ? ' hidden' : '') + - (this.props.disabled ? ' disabled' : ''); + if (this.props.hidden) { + return null; + } + + /* The way the class name is generated here is a mess -- refactor */ + + const className = (this.props.className || '') + + (this.props.button ? ' button-block button-' + this.props.button : '') + + (this.props.disabled ? ' disabled' : ''); + + let content; + if (this.props.children) { // Custom content + content = this.props.children; + } else { + content = [ + 'icon' in this.props ? : null, + {this.props.label}, + 'badge' in this.props ? {this.props.badge} : null, + ]; + } return ( - - {this.props.icon ? icon : '' } - {this.props.label} - {this.props.badge ? {this.props.badge} : '' } + + {('button' in this.props) && this.props.button != 'text' + ? {content} + : content} ); } @@ -78,144 +102,3 @@ export let ToolTipLink = React.createClass({ ); } }); - -export let DownloadLink = React.createClass({ - propTypes: { - type: React.PropTypes.string, - streamName: React.PropTypes.string, - label: React.PropTypes.string, - downloadingLabel: React.PropTypes.string, - button: React.PropTypes.string, - style: React.PropTypes.object, - hidden: React.PropTypes.bool, - }, - getDefaultProps: function() { - return { - icon: 'icon-download', - label: 'Download', - downloadingLabel: 'Downloading...', - } - }, - getInitialState: function() { - return { - downloading: false, - filePath: null, - modal: null, - } - }, - closeModal: function() { - this.setState({ - modal: null, - }) - }, - handleClick: function() { - this.setState({ - downloading: true - }); - - lbry.getCostInfoForName(this.props.streamName, ({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - downloading: false - }); - } else { - lbry.getStream(this.props.streamName, (streamInfo) => { - if (streamInfo === null || typeof streamInfo !== 'object') { - this.setState({ - modal: 'timedOut', - downloading: false, - }); - } else { - this.setState({ - modal: 'downloadStarted', - filePath: streamInfo.path, - }); - } - }); - } - }); - }); - }, - render: function() { - var label = (!this.state.downloading ? this.props.label : this.props.downloadingLabel); - return ( - - - -

Downloading to:

-
{this.state.filePath}
-
- - You don't have enough LBRY credits to pay for this stream. - - - LBRY was unable to download the stream lbry://{this.props.streamName}. - -
- ); - } -}); - -export let WatchLink = React.createClass({ - propTypes: { - type: React.PropTypes.string, - streamName: React.PropTypes.string, - label: React.PropTypes.string, - button: React.PropTypes.string, - style: React.PropTypes.object, - hidden: React.PropTypes.bool, - }, - handleClick: function() { - this.setState({ - loading: true, - }) - lbry.getCostInfoForName(this.props.streamName, ({cost}) => { - lbry.getBalance((balance) => { - if (cost > balance) { - this.setState({ - modal: 'notEnoughCredits', - loading: false, - }); - } else { - window.location = '?watch=' + this.props.streamName; - } - }); - }); - }, - getInitialState: function() { - return { - modal: null, - loading: false, - }; - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - getDefaultProps: function() { - return { - icon: 'icon-play', - label: 'Watch', - } - }, - render: function() { - return ( - - - - You don't have enough LBRY credits to pay for this stream. - - - ); - } -}); diff --git a/js/component/menu.js b/js/component/menu.js index a3670646d..c3c10047f 100644 --- a/js/component/menu.js +++ b/js/component/menu.js @@ -1,53 +1,8 @@ import React from 'react'; -import ReactDOM from 'react-dom'; import {Icon} from './common.js'; +import {Link} from '../component/link.js'; -// Generic menu styles -export let menuStyle = { - whiteSpace: 'nowrap' -}; - -export let Menu = React.createClass({ - handleWindowClick: function(e) { - if (this.props.toggleButton && ReactDOM.findDOMNode(this.props.toggleButton).contains(e.target)) { - // Toggle button was clicked - this.setState({ - open: !this.state.open - }); - } else if (this.state.open && !this.refs.div.contains(e.target)) { - // Menu is open and user clicked outside of it - this.setState({ - open: false - }); - } - }, - propTypes: { - openButton: React.PropTypes.element, - }, - getInitialState: function() { - return { - open: false, - }; - }, - componentDidMount: function() { - window.addEventListener('click', this.handleWindowClick, false); - }, - componentWillUnmount: function() { - window.removeEventListener('click', this.handleWindowClick, false); - }, - render: function() { - return ( -
- {this.props.children} -
- ); - } -}); - -export let menuItemStyle = { - display: 'block', -}; -export let MenuItem = React.createClass({ +export let DropDownMenuItem = React.createClass({ propTypes: { href: React.PropTypes.string, label: React.PropTypes.string, @@ -63,7 +18,7 @@ export let MenuItem = React.createClass({ var icon = (this.props.icon ? : null); return ( - {this.props.iconPosition == 'left' ? icon : null} {this.props.label} @@ -72,3 +27,54 @@ 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(e) { + this.setState({ + menuOpen: !this.state.menuOpen, + }); + if (!this.state.menuOpen && !this._isWindowClickBound) { + this._isWindowClickBound = true; + window.addEventListener('click', this.handleWindowClick, false); + e.stopPropagation(); + } + return false; + }, + handleWindowClick: function(e) { + if (this.state.menuOpen && + (!this._menuDiv || !this._menuDiv.contains(e.target))) { + this.setState({ + menuOpen: false + }); + } + }, + render: function() { + if (!this.state.menuOpen && this._isWindowClickBound) { + this._isWindowClickBound = false; + window.removeEventListener('click', this.handleWindowClick, false); + } + return ( +
+ this._menuButton = span} icon="icon-ellipsis-v" onClick={this.onMenuIconClick} /> + {this.state.menuOpen + ?
this._menuDiv = div} className="menu"> + {this.props.children} +
+ : null} +
+ ); + } +}); \ No newline at end of file diff --git a/js/lbry.js b/js/lbry.js index 9563d4195..12ba61707 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -132,7 +132,7 @@ lbry.getNewAddress = function(callback) { lbry.call('get_new_address', {}, callback); } -lbry.checkAddressIsMine = function(address, callback) { +lbry.checkAddressIsMine = function(address, callback) { lbry.call('address_is_mine', {address: address}, callback); } @@ -265,22 +265,29 @@ lbry.stopFile = function(name, callback) { lbry.call('stop_lbry_file', { name: name }, callback); } -lbry.deleteFile = function(name, deleteTargetFile=true, callback) { +lbry.removeFile = function(sdHash, name, deleteTargetFile=true, callback) { // Name param is temporary until the API can delete by unique ID (SD hash, claim ID etc.) + this._removedFiles.push(sdHash); + this._updateSubscribedFileInfo(sdHash); + lbry.call('delete_lbry_file', { name: name, delete_target_file: deleteTargetFile, }, callback); } -lbry.revealFile = function(path, callback) { - lbry.call('reveal', { path: path }, callback); +lbry.openFile = function(sdHash, callback) { + lbry.call('open', {sd_hash: sdHash}, callback); +} + +lbry.revealFile = function(sdHash, callback) { + lbry.call('reveal', {sd_hash: sdHash}, callback); } lbry.getFileInfoWhenListed = function(name, callback, timeoutCallback, tryNum=0) { // Calls callback with file info when it appears in the list of files returned by lbry.getFilesInfo(). // If timeoutCallback is provided, it will be called if the file fails to appear. - lbry.getFilesInfo(function(filesInfo) { - for (var fileInfo of filesInfo) { + lbry.getFilesInfo(function(fileInfos) { + for (var fileInfo of fileInfos) { if (fileInfo.lbry_uri == name) { callback(fileInfo); return; @@ -453,5 +460,64 @@ lbry.stop = function(callback) { lbry.call('stop', {}, callback); }; +lbry.fileInfo = {}; +lbry._fileInfoSubscribeIdCounter = 0; +lbry._fileInfoSubscribeCallbacks = {}; +lbry._fileInfoSubscribeInterval = 5000; +lbry._removedFiles = []; +lbry._claimIdOwnershipCache = {}; // should be claimId!!! But not + +lbry._updateClaimOwnershipCache = function(claimId) { + lbry.getMyClaims((claimInfos) => { + lbry._claimIdOwnershipCache[claimId] = !!claimInfos.reduce(function(match, claimInfo) { + return match || claimInfo.claim_id == claimId; + }); + }); +}; + +lbry._updateSubscribedFileInfo = function(sdHash) { + const callSubscribedCallbacks = (sdHash, fileInfo) => { + for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[sdHash])) { + callback(fileInfo); + } + } + + if (lbry._removedFiles.includes(sdHash)) { + callSubscribedCallbacks(sdHash, false); + } else { + lbry.getFileInfoBySdHash(sdHash, (fileInfo) => { + if (fileInfo) { + if (this._claimIdOwnershipCache[fileInfo.claim_id] === undefined) { + this._updateClaimOwnershipCache(fileInfo.claim_id); + } + fileInfo.isMine = !!this._claimIdOwnershipCache[fileInfo.claim_id]; + } + + callSubscribedCallbacks(sdHash, fileInfo); + }); + } + + if (Object.keys(this._fileInfoSubscribeCallbacks[sdHash]).length) { + setTimeout(() => { + this._updateSubscribedFileInfo(sdHash); + }, lbry._fileInfoSubscribeInterval); + } +} + +lbry.fileInfoSubscribe = function(sdHash, callback) { + if (!lbry._fileInfoSubscribeCallbacks[sdHash]) + { + lbry._fileInfoSubscribeCallbacks[sdHash] = {}; + } + + const subscribeId = ++lbry._fileInfoSubscribeIdCounter; + lbry._fileInfoSubscribeCallbacks[sdHash][subscribeId] = callback; + lbry._updateSubscribedFileInfo(sdHash); + return subscribeId; +} + +lbry.fileInfoUnsubscribe = function(name, subscribeId) { + delete lbry._fileInfoSubscribeCallbacks[name][subscribeId]; +} export default lbry; diff --git a/js/page/discover.js b/js/page/discover.js index af9792236..3e0bf65e2 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -1,8 +1,9 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; -import {Link, ToolTipLink, DownloadLink, WatchLink} from '../component/link.js'; -import {Thumbnail, CreditAmount, TruncatedText, BusyMessage} from '../component/common.js'; +import {FileTile} from '../component/file-tile.js'; +import {Link, ToolTipLink} from '../component/link.js'; +import {BusyMessage} from '../component/common.js'; var fetchResultsStyle = { color: '#888', @@ -40,14 +41,15 @@ var SearchNoResults = React.createClass({ var SearchResults = React.createClass({ render: function() { - var rows = []; - this.props.results.forEach(function(result) { - console.log(result); - var mediaType = lbry.getMediaType(result.value.content_type); - rows.push( - - ); + var rows = [], + seenNames = {}; //fix this when the search API returns claim IDs + this.props.results.forEach(function({name, value}) { + if (!seenNames[name]) { + seenNames[name] = name; + rows.push( + + ); + } }); return (
{rows}
@@ -55,180 +57,6 @@ var SearchResults = React.createClass({ } }); -var - searchRowStyle = { - height: (24 * 7) + 'px', - overflowY: 'hidden' - }, - searchRowCompactStyle = { - height: '180px', - }, - searchRowImgStyle = { - maxWidth: '100%', - maxHeight: (24 * 7) + 'px', - display: 'block', - marginLeft: 'auto', - marginRight: 'auto' - }, - searchRowTitleStyle = { - fontWeight: 'bold' - }, - searchRowTitleCompactStyle = { - fontSize: '1.25em', - lineHeight: '1.15', - }, - searchRowCostStyle = { - float: 'right', - }, - searchRowDescriptionStyle = { - color : '#444', - marginTop: '12px', - fontSize: '0.9em' - }; - - -var SearchResultRow = React.createClass({ - getInitialState: function() { - return { - downloading: false, - isHovered: false, - cost: null, - costIncludesData: null, - } - }, - handleMouseOver: function() { - this.setState({ - isHovered: true, - }); - }, - handleMouseOut: function() { - this.setState({ - isHovered: false, - }); - }, - componentWillMount: function() { - if ('cost' in this.props) { - this.setState({ - cost: this.props.cost, - costIncludesData: this.props.costIncludesData, - }); - } else { - lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => { - this.setState({ - cost: cost, - costIncludesData: includesData, - }); - }); - } - }, - render: function() { - var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw; - if (!this.props.compact) { - var style = searchRowStyle; - var titleStyle = searchRowTitleStyle; - } else { - var style = Object.assign({}, searchRowStyle, searchRowCompactStyle); - var titleStyle = Object.assign({}, searchRowTitleStyle, searchRowTitleCompactStyle); - } - - return ( -
-
-
- -
-
- {this.state.cost !== null - ? - - - : null} - -

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

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

- - {this.props.description} - -

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

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

-
- } -
- ); - } -}); - -var featuredContentItemContainerStyle = { - position: 'relative', -}; - -var FeaturedContentItem = React.createClass({ - resolveSearch: false, - - propTypes: { - name: React.PropTypes.string, - }, - - getInitialState: function() { - return { - metadata: null, - title: null, - cost: null, - overlayShowing: false, - }; - }, - - componentWillUnmount: function() { - this.resolveSearch = false; - }, - - componentDidMount: function() { - this._isMounted = true; - - lbry.resolveName(this.props.name, (metadata) => { - if (!this._isMounted) { - return; - } - - this.setState({ - metadata: metadata, - title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name), - }); - }); - }, - - render: function() { - if (this.state.metadata === null) { - // Still waiting for metadata, skip render - return null; - } - - return (
- -
); - } -}); - var featuredContentLegendStyle = { fontSize: '12px', color: '#aaa', @@ -241,21 +69,21 @@ var FeaturedContent = React.createClass({

Featured Content

- - - - - + + + + +

Community Content

- - - - - + + + + +
); diff --git a/js/page/file-list.js b/js/page/file-list.js new file mode 100644 index 000000000..95304535c --- /dev/null +++ b/js/page/file-list.js @@ -0,0 +1,201 @@ +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((claimInfos) => { + + if (!this._isMounted) { return; } + + for (let claimInfo of claimInfos) { + let metadata = JSON.parse(claimInfo.value); + publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash); + } + + lbry.getFilesInfo((fileInfos) => { + if (!this._isMounted) { return; } + + this.setState({ + fileInfos: fileInfos.filter(({sd_hash}) => { + return publishedFilesSdHashes.indexOf(sd_hash) == -1; + }) + }); + }); + }); + }, + render: function() { + if (this.state.fileInfos === null) { + return ( +
+ +
+ ); + } else if (!this.state.fileInfos.length) { + return ( +
+ You haven't downloaded anything from LBRY yet. Go ! +
+ ); + } else { + return ( +
+ +
+ ); + } + } +}); + +export let FileListPublished = React.createClass({ + _isMounted: false, + + getInitialState: function () { + return { + fileInfos: null, + }; + }, + componentDidMount: function () { + this._isMounted = true; + document.title = "Published Files"; + + lbry.getMyClaims((claimInfos) => { + /** + * Build newFileInfos as a sparse array and drop elements in at the same position they + * occur in claimInfos, so the order is preserved even if the API calls inside this loop + * return out of order. + */ + let newFileInfos = Array(claimInfos.length), + claimInfoProcessedCount = 0; + + for (let [i, claimInfo] of claimInfos.entries()) { + let metadata = JSON.parse(claimInfo.value); + lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => { + claimInfoProcessedCount++; + if (fileInfo !== false) { + newFileInfos[i] = fileInfo; + } + if (claimInfoProcessedCount >= claimInfos.length) { + /** + * newfileInfos may have gaps from claims that don't have associated files in + * lbrynet, so filter out any missing elements + */ + this.setState({ + fileInfos: newFileInfos.filter(function () { + return true + }), + }); + } + }); + } + }); + }, + render: function () { + if (this.state.fileInfos === null) { + return ( +
+ +
+ ); + } + else if (!this.state.fileInfos.length) { + return ( +
+ You haven't published anything to LBRY yet. Try ! +
+ ); + } + else { + return ( +
+ +
+ ); + } + } +}); + +export let FileList = React.createClass({ + _sortFunctions: { + date: function(fileInfos) { + return fileInfos.reverse(); + }, + title: function(fileInfos) { + return fileInfos.sort(function(a, b) { + return ((a.metadata ? a.metadata.title.toLowerCase() : a.name) > + (b.metadata ? b.metadata.title.toLowerCase() : b.name)); + }); + }, + filename: function(fileInfos) { + return fileInfos.sort(function(a, b) { + return (a.file_name.toLowerCase() > + b.file_name.toLowerCase()); + }); + }, + }, + propTypes: { + fileInfos: React.PropTypes.array.isRequired, + hidePrices: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + hidePrices: false, + }; + }, + getInitialState: function() { + return { + sortBy: 'date', + }; + }, + handleSortChanged: function(event) { + this.setState({ + sortBy: event.target.value, + }); + }, + render: function() { + var content = [], + seenUris = {}; + + const fileInfosSorted = this._sortFunctions[this.state.sortBy](this.props.fileInfos); + for (let fileInfo of fileInfosSorted) { + let {lbry_uri, sd_hash, metadata} = fileInfo; + + if (!metadata || seenUris[lbry_uri]) { + continue; + } + + seenUris[lbry_uri] = true; + content.push(); + } + + return ( +
+ + Sort by { ' ' } + + + + + + + {content} +
+ ); + } +}); \ No newline at end of file diff --git a/js/page/my_files.js b/js/page/my_files.js deleted file mode 100644 index 2d6b28e66..000000000 --- a/js/page/my_files.js +++ /dev/null @@ -1,380 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import {Link, WatchLink} from '../component/link.js'; -import {Menu, MenuItem} from '../component/menu.js'; -import FormField from '../component/form.js'; -import Modal from '../component/modal.js'; -import {BusyMessage, Thumbnail} from '../component/common.js'; - -var moreMenuStyle = { - position: 'absolute', - display: 'block', - top: '26px', - right: '13px', -}; -var MyFilesRowMoreMenu = React.createClass({ - propTypes: { - title: React.PropTypes.string.isRequired, - path: React.PropTypes.string.isRequired, - completed: React.PropTypes.bool.isRequired, - lbryUri: React.PropTypes.string.isRequired, - }, - handleRevealClicked: function() { - lbry.revealFile(this.props.path); - }, - handleRemoveClicked: function() { - lbry.deleteFile(this.props.lbryUri, false); - }, - handleDeleteClicked: function() { - this.setState({ - modal: 'confirmDelete', - }); - }, - handleDeleteConfirmed: function() { - lbry.deleteFile(this.props.lbryUri); - this.setState({ - modal: null, - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - getInitialState: function() { - return { - modal: null, - }; - }, - render: function() { - return ( -
- -
- {/* @TODO: Switch to OS specific wording */} - - -
-
- - Are you sure you'd like to delete {this.props.title}? This will {this.props.completed ? ' stop the download and ' : ''} - permanently remove the file from your system. - -
- ); - } -}); - -var moreButtonColumnStyle = { - height: '120px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - moreButtonContainerStyle = { - display: 'block', - position: 'relative', - }, - moreButtonStyle = { - fontSize: '1.3em', - }, - progressBarStyle = { - height: '15px', - width: '230px', - backgroundColor: '#444', - border: '2px solid #eee', - display: 'inline-block', - }, - artStyle = { - maxHeight: '100px', - maxWidth: '100%', - display: 'block', - marginLeft: 'auto', - marginRight: 'auto', - }; -var MyFilesRow = React.createClass({ - onPauseResumeClicked: function() { - if (this.props.stopped) { - lbry.startFile(this.props.lbryUri); - } else { - lbry.stopFile(this.props.lbryUri); - } - }, - render: function() { - //@TODO: Convert progress bar to reusable component - - var progressBarWidth = 230; - - if (this.props.completed) { - var pauseLink = null; - var curProgressBarStyle = {display: 'none'}; - } else { - var pauseLink = { this.onPauseResumeClicked() }} />; - - var curProgressBarStyle = Object.assign({}, progressBarStyle); - curProgressBarStyle.width = Math.floor(this.props.ratioLoaded * progressBarWidth) + 'px'; - curProgressBarStyle.borderRightWidth = progressBarWidth - Math.ceil(this.props.ratioLoaded * progressBarWidth) + 2; - } - - if (this.props.showWatchButton) { - var watchButton = - } else { - var watchButton = null; - } - - return ( -
-
-
- -
-
-

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

- {this.props.pending ? This file is pending confirmation - : ( -
-
- { ' ' } - {this.props.completed - ? (this.props.isMine - ? 'Published' - : 'Download complete') - : (parseInt(this.props.ratioLoaded * 100) + '%')} -
{ pauseLink }
-
{ watchButton }
-
- ) - } -
-
- {this.props.pending ? null : -
- - -
- } -
-
-
- ); - } -}); - -var MyFilesPage = React.createClass({ - _fileTimeout: null, - _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: {}, - sortBy: 'date', - }; - }, - getDefaultProps: function() { - return { - show: null, - }; - }, - componentDidMount: function() { - document.title = "My Files"; - }, - componentWillMount: function() { - if (this.props.show == 'downloaded') { - this.getPublishedFilesSdHashes(() => { - this.updateFilesInfo(); - }); - } else { - this.updateFilesInfo(); - } - }, - getPublishedFilesSdHashes: function(callback) { - // Determines which files were published by the user and saves their SD hashes in - // this.state.publishedFilesSdHashes. Used on the Downloads page to filter out claims published - // by the user. - var publishedFilesSdHashes = []; - lbry.getMyClaims((claimsInfo) => { - for (let claimInfo of claimsInfo) { - let metadata = JSON.parse(claimInfo.value); - publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash); - } - - this.setState({ - publishedFilesSdHashes: publishedFilesSdHashes, - }); - callback(); - }); - }, - componentWillUnmount: function() { - if (this._fileTimeout) - { - clearTimeout(this._fileTimeout); - } - }, - handleSortChanged: function(event) { - this.setState({ - sortBy: event.target.value, - }); - }, - updateFilesInfo: function() { - this._fileInfoCheckNum += 1; - - if (this.props.show == 'published') { - // We're in the Published tab, so populate this.state.filesInfo with data from the user's claims - lbry.getMyClaims((claimsInfo) => { - /** - * Build newFilesInfo as a sparse array and drop elements in at the same position they - * occur in claimsInfo, so the order is preserved even if the API calls inside this loop - * return out of order. - */ - - let newFilesInfo = Array(claimsInfo.length); - let claimInfoProcessedCount = 0; - for (let [i, claimInfo] of claimsInfo.entries()) { - let metadata = JSON.parse(claimInfo.value); - lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => { - claimInfoProcessedCount++; - if (fileInfo !== false) { - newFilesInfo[i] = fileInfo; - } - if (claimInfoProcessedCount >= claimsInfo.length) { - /** - * newFilesInfo may have gaps from claims that don't have associated files in - * lbrynet, so filter out any missing elements - */ - this.setState({ - filesInfo: newFilesInfo.filter(function() { return true }), - }); - - this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000); - } - }); - } - }); - } else { - // We're in the Downloaded tab, so populate this.state.filesInfo with files the user has in - // lbrynet, with published files filtered out. - lbry.getFilesInfo((filesInfo) => { - this.setState({ - filesInfo: filesInfo.filter(({sd_hash}) => { - return this.state.publishedFilesSdHashes.indexOf(sd_hash) == -1; - }), - }); - - let newFilesAvailable; - if (!(this._fileInfoCheckNum % this._fileInfoCheckRate)) { - // Time to update file availability status - - newFilesAvailable = {}; - let filePeersCheckCount = 0; - for (let {sd_hash} of filesInfo) { - lbry.getPeersForBlobHash(sd_hash, (peers) => { - filePeersCheckCount++; - newFilesAvailable[sd_hash] = peers.length >= 0; - if (filePeersCheckCount >= filesInfo.length) { - this.setState({ - filesAvailable: newFilesAvailable, - }); - } - }); - } - } - - this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000); - }) - } - }, - render: function() { - if (this.state.filesInfo === null || (this.props.show == 'downloaded' && this.state.publishedFileSdHashes === null)) { - return ( -
- -
- ); - } else if (!this.state.filesInfo.length) { - return ( -
- {this.props.show == 'downloaded' - ? You haven't downloaded anything from LBRY yet. Go ! - : You haven't published anything to LBRY yet.} -
- ); - } else { - var content = [], - seenUris = {}; - - const filesInfoSorted = this._sortFunctions[this.state.sortBy](this.state.filesInfo); - for (let fileInfo of filesInfoSorted) { - let {completed, written_bytes, total_bytes, lbry_uri, file_name, download_path, - stopped, metadata, sd_hash} = fileInfo; - - if (!metadata || seenUris[lbry_uri]) { - continue; - } - - seenUris[lbry_uri] = true; - - let {title, thumbnail} = metadata; - - if (!fileInfo.pending && typeof metadata == 'object') { - var {title, thumbnail} = metadata; - var pending = false; - } else { - var title = null; - var thumbnail = null; - var pending = true; - } - - var ratioLoaded = written_bytes / total_bytes; - - var mediaType = lbry.getMediaType(metadata.content_type, file_name); - var showWatchButton = (mediaType == 'video'); - - content.push(); - } - } - return ( -
- - Sort by { ' ' } - - - - - - - {content} -
- ); - } -}); - - -export default MyFilesPage; diff --git a/js/page/publish.js b/js/page/publish.js index 70e1b851d..8e3ede775 100644 --- a/js/page/publish.js +++ b/js/page/publish.js @@ -91,8 +91,7 @@ var PublishPage = React.createClass({ if (this.refs.file.getValue() !== '') { publishArgs.file_path = this._tempFilePath; } - - console.log(publishArgs); + lbry.publish(publishArgs, (message) => { this.handlePublishStarted(); }, null, (error) => { diff --git a/js/page/show.js b/js/page/show.js index 7486b0c93..0bd1dde57 100644 --- a/js/page/show.js +++ b/js/page/show.js @@ -2,7 +2,8 @@ import React from 'react'; import lbry from '../lbry.js'; import lighthouse from '../lighthouse.js'; import {CreditAmount, Thumbnail} from '../component/common.js'; -import {Link, DownloadLink, WatchLink} from '../component/link.js'; +import {FileActions} from '../component/file-actions.js'; +import {Link} from '../component/link.js'; var formatItemImgStyle = { maxWidth: '100%', @@ -62,10 +63,7 @@ var FormatItem = React.createClass({ -
- {mediaType == 'video' ? : null} - -
+
diff --git a/package.json b/package.json index 40ef3d434..b36fac6f7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "babel-cli": "^6.11.4", "babel-preset-es2015": "^6.13.2", "babel-preset-react": "^6.11.1", - "clamp": "^1.0.1", + "clamp-js": "^0.7.0", "mediaelement": "^2.23.4", "node-sass": "^3.8.0", "react": "^15.4.0", @@ -34,9 +34,10 @@ "babel-core": "^6.18.2", "babel-loader": "^6.2.8", "babel-plugin-react-require": "^3.0.0", + "babel-polyfill": "^6.20.0", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", - "babel-polyfill": "^6.20.0", + "babel-preset-stage-2": "^6.18.0", "eslint": "^3.10.2", "eslint-config-airbnb": "^13.0.0", "eslint-loader": "^1.6.1", diff --git a/scss/_canvas.scss b/scss/_canvas.scss index fd24a2236..7a6184a95 100644 --- a/scss/_canvas.scss +++ b/scss/_canvas.scss @@ -56,7 +56,7 @@ $drawer-width: 240px; #drawer-handle { padding: $spacing-vertical / 2; - max-height: $header-height - $spacing-vertical; + max-height: $height-header - $spacing-vertical; text-align: center; } @@ -76,10 +76,10 @@ $drawer-width: 240px; background: $color-primary; color: white; &.header-no-subnav { - height: $header-height; + height: $height-header; } &.header-with-subnav { - height: $header-height * 2; + height: $height-header * 2; } position: fixed; top: 0; @@ -87,7 +87,7 @@ $drawer-width: 240px; width: 100%; z-index: 2; box-sizing: border-box; - h1 { font-size: 1.8em; line-height: $header-height - $spacing-vertical; display: inline-block; float: left; } + h1 { font-size: 1.8em; line-height: $height-header - $spacing-vertical; display: inline-block; float: left; } &.header-scrolled { box-shadow: $default-box-shadow; @@ -120,7 +120,7 @@ nav.sub-header display: inline-block; margin: 0 15px; padding: 0 5px; - line-height: $header-height - $spacing-vertical - $sub-header-selected-underline-height; + line-height: $height-header - $spacing-vertical - $sub-header-selected-underline-height; color: #e8e8e8; &:first-child { @@ -147,13 +147,13 @@ nav.sub-header background: $color-canvas; &.no-sub-nav { - min-height: calc(100vh - 60px); //should be -$header-height, but I'm dumb I guess? It wouldn't work - main { margin-top: $header-height; } + min-height: calc(100vh - 60px); //should be -$height-header, but I'm dumb I guess? It wouldn't work + main { margin-top: $height-header; } } &.with-sub-nav { - min-height: calc(100vh - 120px); //should be -$header-height, but I'm dumb I guess? It wouldn't work - main { margin-top: $header-height * 2; } + min-height: calc(100vh - 120px); //should be -$height-header, but I'm dumb I guess? It wouldn't work + main { margin-top: $height-header * 2; } } main { @@ -206,9 +206,6 @@ $header-icon-size: 1.5em; box-shadow: $default-box-shadow; border-radius: 2px; } -.card-compact { - padding: 22px; -} .card-obscured { position: relative; diff --git a/scss/_global.scss b/scss/_global.scss index ad5854a36..b59b5821f 100644 --- a/scss/_global.scss +++ b/scss/_global.scss @@ -2,12 +2,15 @@ $spacing-vertical: 24px; +$padding-button: 12px; + $color-primary: #155B4A; $color-light-alt: hsl(hue($color-primary), 15, 85); $color-text-dark: #000; $color-help: rgba(0,0,0,.6); $color-canvas: #f5f5f5; $color-bg: #ffffff; +$color-bg-alt: #D9D9D9; $color-money: #216C2A; $color-meta-light: #505050; @@ -17,7 +20,8 @@ $mobile-width-threshold: 801px; $max-content-width: 1000px; $max-text-width: 660px; -$header-height: $spacing-vertical * 2.5; +$height-header: $spacing-vertical * 2.5; +$height-button: $spacing-vertical * 1.5; $default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); diff --git a/scss/_gui.scss b/scss/_gui.scss index 965d2eb9e..a184f6ace 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -27,6 +27,8 @@ text-decoration: none; } } + + color: $color; } .icon-fixed-width { @@ -138,18 +140,20 @@ input[type="text"], input[type="search"] } .button-container { + position: relative; + display: inline-block; + + .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; @@ -168,27 +172,31 @@ input[type="text"], input[type="search"] padding-left: 5px; } } +.button-block +{ + cursor: pointer; +} + +.button__content { + margin: 0 $padding-button; +} .button-primary { color: white; background-color: $color-primary; box-shadow: $default-box-shadow; - padding: 0 12px; } .button-alt { - background-color: rgba(0,0,0,.15); + background-color: $color-bg-alt; box-shadow: $default-box-shadow; - padding: 0 12px; -} -.button-cancel -{ - padding: 0 12px; } + .button-text { @include text-link(); + display: inline-block; } .button-text-help { @@ -338,21 +346,10 @@ 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); } -.error-modal { - max-width: none; - width: 400px; -} - .error-modal__content { display: flex; padding: 0px 8px 10px 10px; @@ -367,3 +364,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; +} diff --git a/scss/all.scss b/scss/all.scss index e02fd6d3d..4055cc708 100644 --- a/scss/all.scss +++ b/scss/all.scss @@ -3,6 +3,9 @@ @import "_icons"; @import "_mediaelement"; @import "_canvas"; -@import "_table"; @import "_gui"; +@import "component/_table"; +@import "component/_file-actions.scss"; +@import "component/_file-tile.scss"; +@import "component/_menu.scss"; @import "page/_developer.scss"; \ No newline at end of file diff --git a/scss/component/_file-actions.scss b/scss/component/_file-actions.scss new file mode 100644 index 000000000..5117b11f5 --- /dev/null +++ b/scss/component/_file-actions.scss @@ -0,0 +1,25 @@ +@import "../global"; + +$color-download: #444; + +.file-actions--stub +{ + height: $height-button; +} + +.file-actions__download-status-bar +{ + position: relative; + color: $color-download; +} +.file-actions__download-status-bar-overlay +{ + background: $color-download; + color: white; + position: absolute; + white-space: nowrap; + overflow: hidden; + z-index: 1; + top: 0px; + left: 0px; +} \ No newline at end of file diff --git a/scss/component/_file-tile.scss b/scss/component/_file-tile.scss new file mode 100644 index 000000000..11a071232 --- /dev/null +++ b/scss/component/_file-tile.scss @@ -0,0 +1,27 @@ +@import "../global"; + +.file-tile__row { + height: $spacing-vertical * 7; +} + +.file-tile__thumbnail { + max-width: 100%; + max-height: $spacing-vertical * 7; + display: block; + margin-left: auto; + margin-right: auto; +} + +.file-tile__title { + font-weight: bold; +} + +.file-tile__cost { + float: right; +} + +.file-tile__description { + color: #444; + margin-top: 12px; + font-size: 0.9em; +} \ No newline at end of file diff --git a/scss/component/_menu.scss b/scss/component/_menu.scss new file mode 100644 index 000000000..d46926bba --- /dev/null +++ b/scss/component/_menu.scss @@ -0,0 +1,21 @@ +@import "../global"; + +$border-radius-menu: 2px; + +.menu { + position: absolute; + white-space: nowrap; + background-color: white; + box-shadow: $default-box-shadow; + border-radius: $border-radius-menu; + padding-top: $spacing-vertical / 2; + padding-bottom: $spacing-vertical / 2; +} + +.menu__menu-item { + display: block; + padding: $spacing-vertical / 4 $spacing-vertical / 2; + &:hover { + background: $color-bg-alt; + } +} \ No newline at end of file diff --git a/scss/_table.scss b/scss/component/_table.scss similarity index 97% rename from scss/_table.scss rename to scss/component/_table.scss index 899010d60..9d60cf6e8 100644 --- a/scss/_table.scss +++ b/scss/component/_table.scss @@ -1,3 +1,5 @@ +@import "../global"; + table.table-standard { word-wrap: break-word; max-width: 100%; diff --git a/webpack.config.js b/webpack.config.js index 34b4bdffc..d13ac540c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,12 +25,12 @@ module.exports = { loaders: [ { test: /\.css$/, loader: "style!css" }, { - test: /\.jsx?$/, - // Enable caching for improved performance during development - // It uses default OS directory by default. If you need - // something more custom, pass a path to it. - // I.e., babel?cacheDirectory= - loader: 'babel?cacheDirectory' + test: /\.jsx?$/, + loader: 'babel', + query: { + cacheDirectory: true, + presets:[ 'es2015', 'react', 'stage-3' ] + } } ] }