Merge branch 'master' into 348-multiple-wallet-addresses
This commit is contained in:
commit
6297faac79
33 changed files with 244 additions and 276 deletions
19
README.md
19
README.md
|
@ -1,7 +1,7 @@
|
||||||
# Spee.ch
|
# Spee.ch
|
||||||
Spee.ch is a web app that reads and publishes images and videos to and from the [LBRY](https://lbry.io/) blockchain.
|
Spee.ch is a web app that reads and publishes images and videos to and from the [LBRY](https://lbry.io/) blockchain.
|
||||||
|
|
||||||
## how to run this repository locally
|
## How to run this repository locally
|
||||||
* start mysql
|
* start mysql
|
||||||
* install mysql
|
* install mysql
|
||||||
* create a database called `lbry`
|
* create a database called `lbry`
|
||||||
|
@ -9,8 +9,6 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
|
||||||
* start lbrynet daemon
|
* start lbrynet daemon
|
||||||
* install the [`lbry`](https://github.com/lbryio/lbry) daemon
|
* install the [`lbry`](https://github.com/lbryio/lbry) daemon
|
||||||
* start the `lbry` daemon
|
* start the `lbry` daemon
|
||||||
* start spee.ch-sync
|
|
||||||
* install and run this [`speech-sync`](https://github.com/billbitt/spee.ch-sync) tool
|
|
||||||
* start spee.ch
|
* start spee.ch
|
||||||
* clone this repo
|
* clone this repo
|
||||||
* run `npm install`
|
* run `npm install`
|
||||||
|
@ -20,6 +18,9 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
|
||||||
* build the app by running `npm run build-prod`
|
* build the app by running `npm run build-prod`
|
||||||
* to start the server, run `npm run start`
|
* to start the server, run `npm run start`
|
||||||
* visit [localhost:3000](http://localhost:3000)
|
* visit [localhost:3000](http://localhost:3000)
|
||||||
|
* start spee.ch-sync (optional, recommended)
|
||||||
|
* Note: this tool will decode blocks from the `lbry` blockchain and update the Claim and Certificate tables in mysql with all the claims from the blockchain. This is not necessary if you only want to host and resolve content published through your version of spee.ch, but it is required if you want to retrieve and host other content from the lbry network.
|
||||||
|
* install and run this [`speech-sync`](https://github.com/billbitt/spee.ch-sync) tool
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
* Spee.ch uses `mocha` with `chai` for testing.
|
* Spee.ch uses `mocha` with `chai` for testing.
|
||||||
|
@ -33,16 +34,16 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
|
||||||
* example: `curl https://spee.ch/api/claim/resolve/doitlive/xyz`
|
* example: `curl https://spee.ch/api/claim/resolve/doitlive/xyz`
|
||||||
* /api/claim/list/:name
|
* /api/claim/list/:name
|
||||||
* example: `curl https://spee.ch/api/claim/list/doitlive`
|
* example: `curl https://spee.ch/api/claim/list/doitlive`
|
||||||
* /api/claim/availability/:name (
|
* /api/claim/availability/:name
|
||||||
* returns `true`/`false` for whether a name is available through spee.ch
|
* returns the name if it is available
|
||||||
* example: `curl https://spee.ch/api/claim/availability/doitlive`
|
* example: `curl https://spee.ch/api/claim/availability/doitlive`
|
||||||
* /api/channel/availability/:name (
|
* /api/channel/availability/:name
|
||||||
* returns `true`/`false` for whether a channel is available through spee.ch
|
* returns the name if it is available
|
||||||
* example: `curl https://spee.ch/api/channel/availability/@CoolChannel`
|
* example: `curl https://spee.ch/api/channel/availability/@CoolChannel`
|
||||||
|
|
||||||
#### POST
|
#### POST
|
||||||
* /api/claim/publish
|
* /api/claim/publish
|
||||||
* example: `curl -X POST -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish`
|
* example: `curl -F 'name=MyPictureName' -F 'file=@/path/to/myPicture.jpeg' https://spee.ch/api/claim/publish`
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* `name`
|
* `name`
|
||||||
* `file` (must be type .mp4, .jpeg, .jpg, .gif, or .png)
|
* `file` (must be type .mp4, .jpeg, .jpg, .gif, or .png)
|
||||||
|
@ -54,5 +55,5 @@ Spee.ch is a web app that reads and publishes images and videos to and from the
|
||||||
* `channelName`(optional)
|
* `channelName`(optional)
|
||||||
* `channelPassword` (optional,; required if `channelName` is provided)
|
* `channelPassword` (optional,; required if `channelName` is provided)
|
||||||
|
|
||||||
## bugs
|
## Bugs
|
||||||
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord!
|
If you find a bug or experience a problem, please report your issue here on github and find us in the lbry discord!
|
||||||
|
|
|
@ -20,17 +20,17 @@ module.exports = {
|
||||||
uploadDirectory: null, // enter file path to where uploads/publishes should be stored
|
uploadDirectory: null, // enter file path to where uploads/publishes should be stored
|
||||||
},
|
},
|
||||||
site: {
|
site: {
|
||||||
title: 'Spee.ch',
|
title : 'Spee.ch',
|
||||||
name : 'Spee.ch',
|
name : 'Spee.ch',
|
||||||
host : 'https://spee.ch',
|
host : 'https://spee.ch',
|
||||||
description: 'Open-source, decentralized image and video sharing.'
|
description: 'Open-source, decentralized image and video sharing.',
|
||||||
},
|
},
|
||||||
publish: {
|
publish: {
|
||||||
primaryClaimAddress : null, // choose any address from your lbry wallet
|
primaryClaimAddress : null, // choose any address from your lbry wallet
|
||||||
additionalClaimAddresses: [], // // optional: add previously used claim addresses
|
additionalClaimAddresses: [], // // optional: add previously used claim addresses
|
||||||
thumbnailChannel : '@channelName', // create a channel to use for thumbnail images
|
thumbnailChannel : '@channelName', // create a channel to use for thumbnail images
|
||||||
thumbnailChannelId : 'xyz123...', // the channel_id (claim id) for the channel above
|
thumbnailChannelId : 'xyz123...', // the channel_id (claim id) for the channel above
|
||||||
}
|
},
|
||||||
claim: {
|
claim: {
|
||||||
defaultTitle : 'Spee.ch',
|
defaultTitle : 'Spee.ch',
|
||||||
defaultThumbnail : 'https://spee.ch/assets/img/video_thumb_default.png',
|
defaultThumbnail : 'https://spee.ch/assets/img/video_thumb_default.png',
|
||||||
|
|
|
@ -158,6 +158,13 @@ a, a:visited {
|
||||||
color: #4156C5;
|
color: #4156C5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link--secondary, .link--secondary:visited {
|
||||||
|
font-size: medium;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0.3em;
|
||||||
|
color: #9b9b9b;
|
||||||
|
}
|
||||||
|
|
||||||
.link--nav {
|
.link--nav {
|
||||||
color: black;
|
color: black;
|
||||||
border-bottom: 2px solid white;
|
border-bottom: 2px solid white;
|
||||||
|
@ -461,6 +468,10 @@ button {
|
||||||
color: #9b9b9b;
|
color: #9b9b9b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--wide {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* TABLES */
|
/* TABLES */
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const ActiveStatusBar = () => {
|
const ActiveStatusBar = () => {
|
||||||
return <span className="progress-bar progress-bar--active">| </span>;
|
return <span className='progress-bar progress-bar--active'>| </span>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ActiveStatusBar;
|
export default ActiveStatusBar;
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
class AssetInfo extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
showDetails: false,
|
|
||||||
};
|
|
||||||
this.toggleDetails = this.toggleDetails.bind(this);
|
|
||||||
this.copyToClipboard = this.copyToClipboard.bind(this);
|
|
||||||
}
|
|
||||||
toggleDetails () {
|
|
||||||
if (this.state.showDetails) {
|
|
||||||
return this.setState({showDetails: false});
|
|
||||||
}
|
|
||||||
this.setState({showDetails: true});
|
|
||||||
}
|
|
||||||
copyToClipboard (event) {
|
|
||||||
var elementToCopy = event.target.dataset.elementtocopy;
|
|
||||||
var element = document.getElementById(elementToCopy);
|
|
||||||
element.select();
|
|
||||||
try {
|
|
||||||
document.execCommand('copy');
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({error: 'Oops, unable to copy'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render () {
|
|
||||||
const { asset: { shortId, claimData : { channelName, certificateId, description, name, claimId, fileExt, contentType, thumbnail, host } } } = this.props;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{channelName &&
|
|
||||||
<div className="row row--padded row--wide row--no-top">
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<span className="text">Channel:</span>
|
|
||||||
</div>
|
|
||||||
<div className="column column--8 column--med-10">
|
|
||||||
<span className="text"><Link to={`/${channelName}:${certificateId}`}>{channelName}</Link></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{description &&
|
|
||||||
<div className="row row--padded row--wide row--no-top">
|
|
||||||
<span className="text">{description}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className="row row--padded row--wide row--no-top">
|
|
||||||
<div id="show-short-link">
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<Link className="link--primary" to={`/${shortId}/${name}.${fileExt}`}><span
|
|
||||||
className="text">Link:</span></Link>
|
|
||||||
</div>
|
|
||||||
<div className="column column--8 column--med-10">
|
|
||||||
<div className="row row--short row--wide">
|
|
||||||
<div className="column column--7">
|
|
||||||
<div className="input-error" id="input-error-copy-short-link" hidden="true">error here</div>
|
|
||||||
<input type="text" id="short-link" className="input-disabled input-text--full-width" readOnly
|
|
||||||
spellCheck="false"
|
|
||||||
value={`${host}/${shortId}/${name}.${fileExt}`}
|
|
||||||
onClick={this.select}/>
|
|
||||||
</div>
|
|
||||||
<div className="column column--1"> </div>
|
|
||||||
<div className="column column--2">
|
|
||||||
<button className="button--primary" data-elementtocopy="short-link"
|
|
||||||
onClick={this.copyToClipboard}>copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="show-embed-code">
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<span className="text">Embed:</span>
|
|
||||||
</div>
|
|
||||||
<div className="column column--8 column--med-10">
|
|
||||||
<div className="row row--short row--wide">
|
|
||||||
<div className="column column--7">
|
|
||||||
<div className="input-error" id="input-error-copy-embed-text" hidden="true">error here</div>
|
|
||||||
{(contentType === 'video/mp4') ? (
|
|
||||||
<input type="text" id="embed-text" className="input-disabled input-text--full-width" readOnly
|
|
||||||
onClick={this.select} spellCheck="false"
|
|
||||||
value={`<video width="100%" controls poster="${thumbnail}" src="${host}/${claimId}/${name}.${fileExt}"/></video>`}/>
|
|
||||||
) : (
|
|
||||||
<input type="text" id="embed-text" className="input-disabled input-text--full-width" readOnly
|
|
||||||
onClick={this.select} spellCheck="false"
|
|
||||||
value={`<img src="${host}/${claimId}/${name}.${fileExt}"/>`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="column column--1"> </div>
|
|
||||||
<div className="column column--2">
|
|
||||||
<button className="button--primary" data-elementtocopy="embed-text"
|
|
||||||
onClick={this.copyToClipboard}>copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="show-share-buttons">
|
|
||||||
<div className="row row--padded row--wide row--no-top">
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<span className="text">Share:</span>
|
|
||||||
</div>
|
|
||||||
<div className="column column--7 column--med-10">
|
|
||||||
<div
|
|
||||||
className="row row--short row--wide flex-container--row flex-container--space-between-bottom flex-container--wrap">
|
|
||||||
<a className="link--primary" target="_blank"
|
|
||||||
href={`https://twitter.com/intent/tweet?text=${host}/${shortId}/${name}`}>twitter</a>
|
|
||||||
<a className="link--primary" target="_blank"
|
|
||||||
href={`https://www.facebook.com/sharer/sharer.php?u=${host}/${shortId}/${name}`}>facebook</a>
|
|
||||||
<a className="link--primary" target="_blank"
|
|
||||||
href={`http://tumblr.com/widgets/share/tool?canonicalUrl=${host}/${shortId}/${name}`}>tumblr</a>
|
|
||||||
<a className="link--primary" target="_blank"
|
|
||||||
href={`https://www.reddit.com/submit?url=${host}/${shortId}/${name}&title=${name}`}>reddit</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ this.state.showDetails &&
|
|
||||||
<div>
|
|
||||||
<div className="row--padded row--wide row--no-top">
|
|
||||||
<div>
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<span className="text">Claim Name:</span>
|
|
||||||
</div><div className="column column--8 column--med-10">
|
|
||||||
{name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<span className="text">Claim Id:</span>
|
|
||||||
</div><div className="column column--8 column--med-10">
|
|
||||||
{claimId}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="column column--2 column--med-10">
|
|
||||||
<span className="text">File Type:</span>
|
|
||||||
</div><div className="column column--8 column--med-10">
|
|
||||||
{contentType ? `${contentType}` : 'unknown'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="row--padded row--wide row--no-top">
|
|
||||||
<div className="column column--10">
|
|
||||||
<a target="_blank" href="https://lbry.io/dmca">Report</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div className="row row--wide">
|
|
||||||
<button className="button--secondary" onClick={this.toggleDetails}>{this.state.showDetails ? 'less' : 'more'}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AssetInfo;
|
|
|
@ -2,31 +2,32 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class ExpandingTextarea extends Component {
|
class ExpandingTextarea extends Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
this._handleChange = this._handleChange.bind(this);
|
||||||
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.adjustTextarea({});
|
this.adjustTextarea({});
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
|
||||||
const { onChange, ...rest } = this.props;
|
|
||||||
return (
|
|
||||||
<textarea
|
|
||||||
{ ...rest }
|
|
||||||
ref={x => this.el = x}
|
|
||||||
onChange={ this._handleChange.bind(this) }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleChange (event) {
|
_handleChange (event) {
|
||||||
const { onChange } = this.props;
|
const { onChange } = this.props;
|
||||||
if (onChange) onChange(event);
|
if (onChange) onChange(event);
|
||||||
this.adjustTextarea(event);
|
this.adjustTextarea(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustTextarea ({ target = this.el }) {
|
adjustTextarea ({ target = this.el }) {
|
||||||
target.style.height = 0;
|
target.style.height = 0;
|
||||||
target.style.height = `${target.scrollHeight}px`;
|
target.style.height = `${target.scrollHeight}px`;
|
||||||
}
|
}
|
||||||
|
render () {
|
||||||
|
const { ...rest } = this.props;
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
{...rest}
|
||||||
|
ref={x => this.el = x}
|
||||||
|
onChange={this._handleChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExpandingTextarea.propTypes = {
|
ExpandingTextarea.propTypes = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const InactiveStatusBar = () => {
|
const InactiveStatusBar = () => {
|
||||||
return <span className="progress-bar progress-bar--inactive">| </span>;
|
return <span className='progress-bar progress-bar--inactive'>| </span>;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default InactiveStatusBar;
|
export default InactiveStatusBar;
|
||||||
|
|
|
@ -3,20 +3,20 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
function Logo () {
|
function Logo () {
|
||||||
return (
|
return (
|
||||||
<svg version="1.1" id="Layer_1" x="0px" y="0px" height="24px" viewBox="0 0 80 31" enableBackground="new 0 0 80 31" className="nav-bar-logo">
|
<svg version='1.1' id='Layer_1' x='0px' y='0px' height='24px' viewBox='0 0 80 31' enableBackground='new 0 0 80 31' className='nav-bar-logo'>
|
||||||
<Link to="/">
|
<Link to='/'>
|
||||||
<title>Logo</title>
|
<title>Logo</title>
|
||||||
<desc>Spee.ch logo</desc>
|
<desc>Spee.ch logo</desc>
|
||||||
<g id="About">
|
<g id='About'>
|
||||||
<g id="Publish-Form-V2-_x28_filled_x29_" transform="translate(-42.000000, -23.000000)">
|
<g id='Publish-Form-V2-_x28_filled_x29_' transform='translate(-42.000000, -23.000000)'>
|
||||||
<g id="Group-17" transform="translate(42.000000, 22.000000)">
|
<g id='Group-17' transform='translate(42.000000, 22.000000)'>
|
||||||
<text transform="matrix(1 0 0 1 0 20)" fontSize="25" fontFamily="Roboto">Spee<h</text>
|
<text transform='matrix(1 0 0 1 0 20)' fontSize='25' fontFamily='Roboto'>Spee<h</text>
|
||||||
<g id="Group-16" transform="translate(0.000000, 30.000000)">
|
<g id='Group-16' transform='translate(0.000000, 30.000000)'>
|
||||||
<path id="Line-8" fill="none" stroke="#09F911" strokeWidth="1" strokeLinecap="square" d="M0.5,1.5h15"/>
|
<path id='Line-8' fill='none' stroke='#09F911' strokeWidth='1' strokeLinecap='square' d='M0.5,1.5h15' />
|
||||||
<path id="Line-8-Copy" fill="none" stroke="#029D74" strokeWidth="1" strokeLinecap="square" d="M16.5,1.5h15"/>
|
<path id='Line-8-Copy' fill='none' stroke='#029D74' strokeWidth='1' strokeLinecap='square' d='M16.5,1.5h15' />
|
||||||
<path id="Line-8-Copy-2" fill="none" stroke="#E35BD8" strokeWidth="1" strokeLinecap="square" d="M32.5,1.5h15"/>
|
<path id='Line-8-Copy-2' fill='none' stroke='#E35BD8' strokeWidth='1' strokeLinecap='square' d='M32.5,1.5h15' />
|
||||||
<path id="Line-8-Copy-3" fill="none" stroke="#4156C5" strokeWidth="1" strokeLinecap="square" d="M48.5,1.5h15"/>
|
<path id='Line-8-Copy-3' fill='none' stroke='#4156C5' strokeWidth='1' strokeLinecap='square' d='M48.5,1.5h15' />
|
||||||
<path id="Line-8-Copy-4" fill="none" stroke="#635688" strokeWidth="1" strokeLinecap="square" d="M64.5,1.5h15"/>
|
<path id='Line-8-Copy-4' fill='none' stroke='#635688' strokeWidth='1' strokeLinecap='square' d='M64.5,1.5h15' />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||||
|
|
||||||
function NavBarChannelDropdown ({ channelName, handleSelection, defaultSelection, VIEW, LOGOUT }) {
|
function NavBarChannelDropdown ({ channelName, handleSelection, defaultSelection, VIEW, LOGOUT }) {
|
||||||
return (
|
return (
|
||||||
<select type="text" id="nav-bar-channel-select" className="select select--arrow link--nav" onChange={handleSelection} value={defaultSelection}>
|
<select type='text' id='nav-bar-channel-select' className='select select--arrow link--nav' onChange={handleSelection} value={defaultSelection}>
|
||||||
<option id="nav-bar-channel-select-channel-option">{channelName}</option>
|
<option id='nav-bar-channel-select-channel-option'>{channelName}</option>
|
||||||
<option value={VIEW}>View</option>
|
<option value={VIEW}>View</option>
|
||||||
<option value={LOGOUT}>Logout</option>
|
<option value={LOGOUT}>Logout</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -4,13 +4,13 @@ import PropTypes from 'prop-types';
|
||||||
function UrlMiddle ({publishInChannel, selectedChannel, loggedInChannelName, loggedInChannelShortId}) {
|
function UrlMiddle ({publishInChannel, selectedChannel, loggedInChannelName, loggedInChannelShortId}) {
|
||||||
if (publishInChannel) {
|
if (publishInChannel) {
|
||||||
if (selectedChannel === loggedInChannelName) {
|
if (selectedChannel === loggedInChannelName) {
|
||||||
return <span id="url-channel" className="url-text--secondary">{loggedInChannelName}:{loggedInChannelShortId} /</span>;
|
return <span id='url-channel' className='url-text--secondary'>{loggedInChannelName}:{loggedInChannelShortId} /</span>;
|
||||||
}
|
}
|
||||||
return <span id="url-channel-placeholder" className="url-text--secondary tooltip">@channel<span
|
return <span id='url-channel-placeholder' className='url-text--secondary tooltip'>@channel<span
|
||||||
className="tooltip-text">Select a channel below</span> /</span>;
|
className='tooltip-text'>Select a channel below</span> /</span>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span id="url-no-channel-placeholder" className="url-text--secondary tooltip">xyz<span className="tooltip-text">This will be a random id</span> /</span>
|
<span id='url-no-channel-placeholder' className='url-text--secondary tooltip'>xyz<span className='tooltip-text'>This will be a random id</span> /</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
123
react/containers/AssetInfo/view.jsx
Normal file
123
react/containers/AssetInfo/view.jsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
class AssetInfo extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
this.copyToClipboard = this.copyToClipboard.bind(this);
|
||||||
|
}
|
||||||
|
copyToClipboard (event) {
|
||||||
|
var elementToCopy = event.target.dataset.elementtocopy;
|
||||||
|
var element = document.getElementById(elementToCopy);
|
||||||
|
element.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({error: 'Oops, unable to copy'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
const { asset: { shortId, claimData : { channelName, certificateId, description, name, claimId, fileExt, contentType, thumbnail, host } } } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{channelName &&
|
||||||
|
<div className='row row--padded row--wide row--no-top'>
|
||||||
|
<div className='column column--2 column--med-10'>
|
||||||
|
<span className='text'>Channel:</span>
|
||||||
|
</div>
|
||||||
|
<div className='column column--8 column--med-10'>
|
||||||
|
<span className='text'><Link to={`/${channelName}:${certificateId}`}>{channelName}</Link></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{description &&
|
||||||
|
<div className='row row--padded row--wide row--no-top'>
|
||||||
|
<span className='text'>{description}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div id='show-share-buttons'>
|
||||||
|
<div className='row row--padded row--wide row--no-top'>
|
||||||
|
<div className='column column--2 column--med-10'>
|
||||||
|
<span className='text'>Share:</span>
|
||||||
|
</div>
|
||||||
|
<div className='column column--8 column--med-10'>
|
||||||
|
<div
|
||||||
|
className='row row--short row--wide flex-container--row flex-container--space-between-bottom flex-container--wrap'>
|
||||||
|
<a className='link--primary' target='_blank' href={`https://twitter.com/intent/tweet?text=${host}/${shortId}/${name}`}>twitter</a>
|
||||||
|
<a className='link--primary' target='_blank' href={`https://www.facebook.com/sharer/sharer.php?u=${host}/${shortId}/${name}`}>facebook</a>
|
||||||
|
<a className='link--primary' target='_blank' href={`http://tumblr.com/widgets/share/tool?canonicalUrl=${host}/${shortId}/${name}`}>tumblr</a>
|
||||||
|
<a className='link--primary' target='_blank' href={`https://www.reddit.com/submit?url=${host}/${shortId}/${name}&title=${name}`}>reddit</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='row row--padded row--wide row--no-top'>
|
||||||
|
<div id='show-short-link'>
|
||||||
|
<div className='column column--2 column--med-10'>
|
||||||
|
<span className='text'>Link:</span>
|
||||||
|
</div>
|
||||||
|
<div className='column column--8 column--med-10'>
|
||||||
|
<div className='row row--short row--wide'>
|
||||||
|
<div className='column column--7'>
|
||||||
|
<div className='input-error' id='input-error-copy-short-link' hidden='true'>error here</div>
|
||||||
|
<input type='text' id='short-link' className='input-disabled input-text--full-width' readOnly
|
||||||
|
spellCheck='false'
|
||||||
|
value={`${host}/${shortId}/${name}.${fileExt}`}
|
||||||
|
onClick={this.select} />
|
||||||
|
</div>
|
||||||
|
<div className='column column--1' />
|
||||||
|
<div className='column column--2'>
|
||||||
|
<button className='button--primary button--wide' data-elementtocopy='short-link'
|
||||||
|
onClick={this.copyToClipboard}>copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='show-embed-code'>
|
||||||
|
<div className='column column--2 column--med-10'>
|
||||||
|
<span className='text'>Embed:</span>
|
||||||
|
</div>
|
||||||
|
<div className='column column--8 column--med-10'>
|
||||||
|
<div className='row row--short row--wide'>
|
||||||
|
<div className='column column--7'>
|
||||||
|
<div className='input-error' id='input-error-copy-embed-text' hidden='true'>error here</div>
|
||||||
|
{(contentType === 'video/mp4') ? (
|
||||||
|
<input type='text' id='embed-text' className='input-disabled input-text--full-width' readOnly
|
||||||
|
onClick={this.select} spellCheck='false'
|
||||||
|
value={`<video width="100%" controls poster="${thumbnail}" src="${host}/${claimId}/${name}.${fileExt}"/></video>`} />
|
||||||
|
) : (
|
||||||
|
<input type='text' id='embed-text' className='input-disabled input-text--full-width' readOnly
|
||||||
|
onClick={this.select} spellCheck='false'
|
||||||
|
value={`<img src="${host}/${claimId}/${name}.${fileExt}"/>`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='column column--1' />
|
||||||
|
<div className='column column--2'>
|
||||||
|
<button className='button--primary button--wide' data-elementtocopy='embed-text'
|
||||||
|
onClick={this.copyToClipboard}>copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-container--row flex-container--space-between-bottom'>
|
||||||
|
<Link className='link--primary' to={`/${shortId}/${name}.${fileExt}`}><span
|
||||||
|
className='text'>Direct Link</span></Link>
|
||||||
|
<a className='link--primary' href={`${host}/${claimId}/${name}.${fileExt}`} download={name}>Download</a>
|
||||||
|
<a className='link--primary' target='_blank' href='https://lbry.io/dmca'>Report</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AssetInfo;
|
|
@ -3,9 +3,7 @@ import View from './view';
|
||||||
import { selectAsset } from 'selectors/show';
|
import { selectAsset } from 'selectors/show';
|
||||||
|
|
||||||
const mapStateToProps = ({ show }) => {
|
const mapStateToProps = ({ show }) => {
|
||||||
// select title
|
|
||||||
const { claimData: { title } } = selectAsset(show);
|
const { claimData: { title } } = selectAsset(show);
|
||||||
// return props
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
};
|
};
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
const AssetTitle = ({ title }) => {
|
const AssetTitle = ({ title }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<span className="text--large">{title}</span>
|
<span className='text--large'>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -24,7 +24,7 @@ class ChannelClaimsDisplay extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const { channel: { claimsData: { claims, currentPage, totalPages } } } = this.props;
|
const { channel: { claimsData: { claims, currentPage, totalPages } } } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="row row--tall">
|
<div className='row row--tall'>
|
||||||
{(claims.length > 0) ? (
|
{(claims.length > 0) ? (
|
||||||
<div>
|
<div>
|
||||||
{claims.map((claim, index) => <AssetPreview
|
{claims.map((claim, index) => <AssetPreview
|
||||||
|
|
|
@ -54,15 +54,15 @@ class NavBar extends React.Component {
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className="row row--wide nav-bar">
|
<div className='row row--wide nav-bar'>
|
||||||
<div className="row row--padded row--short flex-container--row flex-container--space-between-center">
|
<div className='row row--padded row--short flex-container--row flex-container--space-between-center'>
|
||||||
<Logo />
|
<Logo />
|
||||||
<div className="nav-bar--center">
|
<div className='nav-bar--center'>
|
||||||
<span className="nav-bar-tagline">Open-source, decentralized image and video sharing.</span>
|
<span className='nav-bar-tagline'>Open-source, decentralized image and video sharing.</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="nav-bar--right">
|
<div className='nav-bar--right'>
|
||||||
<NavLink className="nav-bar-link link--nav" activeClassName="link--nav-active" to="/" exact={true}>Publish</NavLink>
|
<NavLink className='nav-bar-link link--nav' activeClassName='link--nav-active' to='/' exact>Publish</NavLink>
|
||||||
<NavLink className="nav-bar-link link--nav" activeClassName="link--nav-active" to="/about">About</NavLink>
|
<NavLink className='nav-bar-link link--nav' activeClassName='link--nav-active' to='/about'>About</NavLink>
|
||||||
{ this.props.channelName ? (
|
{ this.props.channelName ? (
|
||||||
<NavBarChannelDropdown
|
<NavBarChannelDropdown
|
||||||
channelName={this.props.channelName}
|
channelName={this.props.channelName}
|
||||||
|
@ -72,7 +72,7 @@ class NavBar extends React.Component {
|
||||||
LOGOUT={LOGOUT}
|
LOGOUT={LOGOUT}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<NavLink id="nav-bar-login-link" className="nav-bar-link link--nav" activeClassName="link--nav-active" to="/login">Channel</NavLink>
|
<NavLink id='nav-bar-login-link' className='nav-bar-link link--nav' activeClassName='link--nav-active' to='/login'>Channel</NavLink>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -24,48 +24,48 @@ class PublishMetadataInputs extends React.Component {
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div id="publish-details" className="row row--padded row--no-top row--wide">
|
<div id='publish-details' className='row row--padded row--no-top row--wide'>
|
||||||
{this.props.showMetadataInputs && (
|
{this.props.showMetadataInputs && (
|
||||||
<div>
|
<div>
|
||||||
<div className="row row--no-top">
|
<div className='row row--no-top'>
|
||||||
<div className="column column--3 column--med-10 align-content-top">
|
<div className='column column--3 column--med-10 align-content-top'>
|
||||||
<label htmlFor="publish-license" className="label">Description:</label>
|
<label htmlFor='publish-license' className='label'>Description:</label>
|
||||||
</div><div className="column column--7 column--sml-10">
|
</div><div className='column column--7 column--sml-10'>
|
||||||
<ExpandingTextArea
|
<ExpandingTextArea
|
||||||
id="publish-description"
|
id='publish-description'
|
||||||
className="textarea textarea--primary textarea--full-width"
|
className='textarea textarea--primary textarea--full-width'
|
||||||
rows={1}
|
rows={1}
|
||||||
maxLength={2000}
|
maxLength={2000}
|
||||||
style={{ maxHeight: 200 }}
|
style={{ maxHeight: 200 }}
|
||||||
name="description"
|
name='description'
|
||||||
placeholder="Optional description"
|
placeholder='Optional description'
|
||||||
value={this.props.description}
|
value={this.props.description}
|
||||||
onChange={this.handleInput} />
|
onChange={this.handleInput} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row row--no-top">
|
<div className='row row--no-top'>
|
||||||
<div className="column column--3 column--med-10">
|
<div className='column column--3 column--med-10'>
|
||||||
<label htmlFor="publish-license" className="label">License:</label>
|
<label htmlFor='publish-license' className='label'>License:</label>
|
||||||
</div><div className="column column--7 column--sml-10">
|
</div><div className='column column--7 column--sml-10'>
|
||||||
<select type="text" name="license" id="publish-license" className="select select--primary" onChange={this.handleSelect}>
|
<select type='text' name='license' id='publish-license' className='select select--primary' onChange={this.handleSelect}>
|
||||||
<option value=" ">Unspecified</option>
|
<option value=' '>Unspecified</option>
|
||||||
<option value="Public Domain">Public Domain</option>
|
<option value='Public Domain'>Public Domain</option>
|
||||||
<option value="Creative Commons">Creative Commons</option>
|
<option value='Creative Commons'>Creative Commons</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row row--no-top">
|
<div className='row row--no-top'>
|
||||||
<div className="column column--3">
|
<div className='column column--3'>
|
||||||
<label htmlFor="publish-nsfw" className="label">Mature:</label>
|
<label htmlFor='publish-nsfw' className='label'>Mature:</label>
|
||||||
</div><div className="column column--7">
|
</div><div className='column column--7'>
|
||||||
<input className="input-checkbox" type="checkbox" id="publish-nsfw" name="nsfw" value={this.props.nsfw} onChange={this.handleInput} />
|
<input className='input-checkbox' type='checkbox' id='publish-nsfw' name='nsfw' value={this.props.nsfw} onChange={this.handleInput} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button className="button--secondary" onClick={this.toggleShowInputs}>{this.props.showMetadataInputs ? 'less' : 'more'}</button>
|
<button className='button--secondary' onClick={this.toggleShowInputs}>{this.props.showMetadataInputs ? 'less' : 'more'}</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class PublishTitleInput extends React.Component {
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<input type="text" id="publish-title" className="input-text text--large input-text--full-width" name="title" placeholder="Give your post a title..." onChange={this.handleInput} value={this.props.title}/>
|
<input type='text' id='publish-title' className='input-text text--large input-text--full-width' name='title' placeholder='Give your post a title...' onChange={this.handleInput} value={this.props.title} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,15 @@ import React from 'react';
|
||||||
import SEO from 'components/SEO';
|
import SEO from 'components/SEO';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
import ErrorPage from 'components/ErrorPage';
|
import ErrorPage from 'components/ErrorPage';
|
||||||
import AssetTitle from 'components/AssetTitle';
|
import AssetTitle from 'containers/AssetTitle';
|
||||||
import AssetDisplay from 'components/AssetDisplay';
|
import AssetDisplay from 'containers/AssetDisplay';
|
||||||
import AssetInfo from 'components/AssetInfo';
|
import AssetInfo from 'containers/AssetInfo';
|
||||||
|
|
||||||
class ShowAssetDetails extends React.Component {
|
class ShowAssetDetails extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const { asset } = this.props;
|
const { asset } = this.props;
|
||||||
if (asset) {
|
if (asset) {
|
||||||
const { name } = asset.claimData;
|
const { claimData: { name } } = asset;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SEO pageTitle={`${name} - details`} asset={asset} />
|
<SEO pageTitle={`${name} - details`} asset={asset} />
|
||||||
|
@ -29,7 +29,6 @@ class ShowAssetDetails extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SEO from 'components/SEO';
|
import SEO from 'components/SEO';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import AssetDisplay from 'components/AssetDisplay';
|
import AssetDisplay from 'containers/AssetDisplay';
|
||||||
|
|
||||||
class ShowLite extends React.Component {
|
class ShowLite extends React.Component {
|
||||||
render () {
|
render () {
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ErrorPage from 'components/ErrorPage';
|
import ErrorPage from 'components/ErrorPage';
|
||||||
import ShowAssetLite from 'components/ShowAssetLite';
|
import ShowAssetLite from 'containers/ShowAssetLite';
|
||||||
import ShowAssetDetails from 'components/ShowAssetDetails';
|
import ShowAssetDetails from 'containers/ShowAssetDetails';
|
||||||
import ShowChannel from 'components/ShowChannel';
|
import ShowChannel from 'containers/ShowChannel';
|
||||||
|
|
||||||
import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from 'constants/show_request_types';
|
import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from 'constants/show_request_types';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue