Seed Support #56
10 changed files with 115 additions and 85 deletions
|
@ -19,6 +19,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
* New welcome flow for new users
|
* New welcome flow for new users
|
||||||
* Redesigned UI for Discover
|
* Redesigned UI for Discover
|
||||||
* Handle more of price calculations at the daemon layer to improve page load time
|
* Handle more of price calculations at the daemon layer to improve page load time
|
||||||
|
* Add special support for building channel claims in lbryuri module
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Update process now easier and more reliable
|
* Update process now easier and more reliable
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import uri from '../uri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Icon} from './common.js';
|
import {Icon} from './common.js';
|
||||||
|
|
||||||
const UriIndicator = React.createClass({
|
const UriIndicator = React.createClass({
|
||||||
|
@ -11,7 +11,7 @@ const UriIndicator = React.createClass({
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
const uriObj = uri.parseLbryUri(this.props.uri);
|
const uriObj = lbryuri.parse(this.props.uri);
|
||||||
|
|
||||||
if (!this.props.hasSignature || !uriObj.isChannel) {
|
if (!this.props.hasSignature || !uriObj.isChannel) {
|
||||||
return <span className="empty">Anonymous</span>;
|
return <span className="empty">Anonymous</span>;
|
||||||
|
@ -19,7 +19,8 @@ const UriIndicator = React.createClass({
|
||||||
|
|
||||||
const channelUriObj = Object.assign({}, uriObj);
|
const channelUriObj = Object.assign({}, uriObj);
|
||||||
delete channelUriObj.path;
|
delete channelUriObj.path;
|
||||||
const channelUri = uri.buildLbryUri(channelUriObj, false);
|
delete channelUriObj.contentName;
|
||||||
|
const channelUri = lbryuri.build(channelUriObj, false);
|
||||||
|
|
||||||
let icon, modifier;
|
let icon, modifier;
|
||||||
if (this.props.signatureIsValid) {
|
if (this.props.signatureIsValid) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import uri from '../uri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import {Icon, FilePrice} from '../component/common.js';
|
import {Icon, FilePrice} from '../component/common.js';
|
||||||
import {Modal} from './modal.js';
|
import {Modal} from './modal.js';
|
||||||
|
@ -156,8 +156,8 @@ let FileActionsRow = React.createClass({
|
||||||
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={this.onOpenClick} />;
|
linkBlock = <Link label="Open" button="text" icon="icon-folder-open" onClick={this.onOpenClick} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lbryUri = uri.normalizeLbryUri(this.props.uri);
|
const uri = lbryuri.normalize(this.props.uri);
|
||||||
const title = this.props.metadata ? this.props.metadata.title : lbryUri;
|
const title = this.props.metadata ? this.props.metadata.title : uri;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.state.fileInfo !== null || this.state.fileInfo.isMine
|
{this.state.fileInfo !== null || this.state.fileInfo.isMine
|
||||||
|
@ -170,7 +170,7 @@ let FileActionsRow = React.createClass({
|
||||||
</DropDownMenu> : '' }
|
</DropDownMenu> : '' }
|
||||||
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
<Modal type="confirm" isOpen={this.state.modal == 'affirmPurchase'}
|
||||||
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
|
contentLabel="Confirm Purchase" onConfirmed={this.onAffirmPurchase} onAborted={this.closeModal}>
|
||||||
Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={lbryUri} metadata={this.props.metadata} label={false} look="plain" /></strong> credits?
|
Are you sure you'd like to buy <strong>{title}</strong> for <strong><FilePrice uri={uri} metadata={this.props.metadata} label={false} look="plain" /></strong> credits?
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
<Modal isOpen={this.state.modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
||||||
onConfirmed={this.closeModal}>
|
onConfirmed={this.closeModal}>
|
||||||
|
@ -178,7 +178,7 @@ let FileActionsRow = React.createClass({
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
|
<Modal isOpen={this.state.modal == 'timedOut'} contentLabel="Download failed"
|
||||||
onConfirmed={this.closeModal}>
|
onConfirmed={this.closeModal}>
|
||||||
LBRY was unable to download the stream <strong>{lbryUri}</strong>.
|
LBRY was unable to download the stream <strong>{uri}</strong>.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
|
<Modal isOpen={this.state.modal == 'confirmRemove'} contentLabel="Not enough credits"
|
||||||
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
|
type="confirm" confirmButtonLabel="Remove" onConfirmed={this.handleRemoveConfirmed}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import uri from '../uri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import {FileActions} from '../component/file-actions.js';
|
import {FileActions} from '../component/file-actions.js';
|
||||||
import {Thumbnail, TruncatedText, FilePrice} from '../component/common.js';
|
import {Thumbnail, TruncatedText, FilePrice} from '../component/common.js';
|
||||||
|
@ -72,25 +72,25 @@ export let FileTileStream = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lbryUri = uri.normalizeLbryUri(this.props.uri);
|
const uri = lbryuri.normalize(this.props.uri);
|
||||||
const metadata = this.props.metadata;
|
const metadata = this.props.metadata;
|
||||||
const isConfirmed = !!metadata;
|
const isConfirmed = !!metadata;
|
||||||
const title = isConfirmed ? metadata.title : lbryUri;
|
const title = isConfirmed ? metadata.title : uri;
|
||||||
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
||||||
return (
|
return (
|
||||||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||||
<div className={"row-fluid card__inner file-tile__row"}>
|
<div className={"row-fluid card__inner file-tile__row"}>
|
||||||
<div className="span3 file-tile__thumbnail-container">
|
<div className="span3 file-tile__thumbnail-container">
|
||||||
<a href={'?show=' + lbryUri}><Thumbnail className="file-tile__thumbnail" {... metadata && metadata.thumbnail ? {src: metadata.thumbnail} : {}} alt={'Photo for ' + (title || this.props.uri)} /></a>
|
<a href={'?show=' + uri}><Thumbnail className="file-tile__thumbnail" {... metadata && metadata.thumbnail ? {src: metadata.thumbnail} : {}} alt={'Photo for ' + this.props.uri} /></a>
|
||||||
</div>
|
</div>
|
||||||
<div className="span9">
|
<div className="span9">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
{ !this.props.hidePrice
|
{ !this.props.hidePrice
|
||||||
? <FilePrice uri={this.props.uri} />
|
? <FilePrice uri={this.props.uri} />
|
||||||
: null}
|
: null}
|
||||||
<div className="meta"><a href={'?show=' + this.props.uri}>{lbryUri}</a></div>
|
<div className="meta"><a href={'?show=' + this.props.uri}>{uri}</a></div>
|
||||||
<h3>
|
<h3>
|
||||||
<a href={'?show=' + this.props.uri}>
|
<a href={'?show=' + uri}>
|
||||||
<TruncatedText lines={1}>
|
<TruncatedText lines={1}>
|
||||||
{title}
|
{title}
|
||||||
</TruncatedText>
|
</TruncatedText>
|
||||||
|
@ -184,12 +184,12 @@ export let FileCardStream = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lbryUri = uri.normalizeLbryUri(this.props.uri);
|
const uri = lbryuri.normalize(this.props.uri);
|
||||||
const metadata = this.props.metadata;
|
const metadata = this.props.metadata;
|
||||||
const isConfirmed = !!metadata;
|
const isConfirmed = !!metadata;
|
||||||
const title = isConfirmed ? metadata.title : lbryUri;
|
const title = isConfirmed ? metadata.title : uri;
|
||||||
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
||||||
const primaryUrl = '?show=' + lbryUri;
|
const primaryUrl = '?show=' + uri;
|
||||||
return (
|
return (
|
||||||
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||||
<div className="card__inner">
|
<div className="card__inner">
|
||||||
|
@ -198,7 +198,7 @@ export let FileCardStream = React.createClass({
|
||||||
<h5><TruncatedText lines={1}>{title}</TruncatedText></h5>
|
<h5><TruncatedText lines={1}>{title}</TruncatedText></h5>
|
||||||
<div className="card__subtitle">
|
<div className="card__subtitle">
|
||||||
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span> : null}
|
{ !this.props.hidePrice ? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span> : null}
|
||||||
<UriIndicator uri={lbryUri} metadata={metadata} contentType={this.props.contentType}
|
<UriIndicator uri={uri} metadata={metadata} contentType={this.props.contentType}
|
||||||
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
|
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import lbryio from './lbryio.js';
|
import lbryio from './lbryio.js';
|
||||||
import lighthouse from './lighthouse.js';
|
import lighthouse from './lighthouse.js';
|
||||||
import jsonrpc from './jsonrpc.js';
|
import jsonrpc from './jsonrpc.js';
|
||||||
import uri from './uri.js';
|
import lbryuri from './lbryuri.js';
|
||||||
import {getLocal, getSession, setSession, setLocal} from './utils.js';
|
import {getLocal, getSession, setSession, setLocal} from './utils.js';
|
||||||
|
|
||||||
const {remote} = require('electron');
|
const {remote} = require('electron');
|
||||||
|
@ -12,19 +12,19 @@ const menu = remote.require('./menu/main-menu');
|
||||||
* needed to make a dummy claim or file info object.
|
* needed to make a dummy claim or file info object.
|
||||||
*/
|
*/
|
||||||
function savePendingPublish({name, channel_name}) {
|
function savePendingPublish({name, channel_name}) {
|
||||||
let lbryUri;
|
let uri;
|
||||||
if (channel_name) {
|
if (channel_name) {
|
||||||
lbryUri = uri.buildLbryUri({name: channel_name, path: name}, false);
|
uri = lbryuri.build({name: channel_name, path: name}, false);
|
||||||
} else {
|
} else {
|
||||||
lbryUri = uri.buildLbryUri({name: name}, false);
|
uri = lbryuri.build({name: name}, false);
|
||||||
}
|
}
|
||||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||||
const newPendingPublish = {
|
const newPendingPublish = {
|
||||||
name, channel_name,
|
name, channel_name,
|
||||||
claim_id: 'pending_claim_' + lbryUri,
|
claim_id: 'pending_claim_' + uri,
|
||||||
txid: 'pending_' + lbryUri,
|
txid: 'pending_' + uri,
|
||||||
nout: 0,
|
nout: 0,
|
||||||
outpoint: 'pending_' + lbryUri + ':0',
|
outpoint: 'pending_' + uri + ':0',
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
};
|
};
|
||||||
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
||||||
|
@ -215,35 +215,35 @@ lbry.getPeersForBlobHash = function(blobHash, callback) {
|
||||||
* from Lighthouse is included.
|
* from Lighthouse is included.
|
||||||
*/
|
*/
|
||||||
lbry.costPromiseCache = {}
|
lbry.costPromiseCache = {}
|
||||||
lbry.getCostInfo = function(lbryUri) {
|
lbry.getCostInfo = function(uri) {
|
||||||
if (lbry.costPromiseCache[lbryUri] === undefined) {
|
if (lbry.costPromiseCache[uri] === undefined) {
|
||||||
lbry.costPromiseCache[lbryUri] = new Promise((resolve, reject) => {
|
lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
|
||||||
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
||||||
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
|
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
|
||||||
|
|
||||||
function cacheAndResolve(cost, includesData) {
|
function cacheAndResolve(cost, includesData) {
|
||||||
costInfoCache[lbryUri] = {cost, includesData};
|
costInfoCache[uri] = {cost, includesData};
|
||||||
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
||||||
resolve({cost, includesData});
|
resolve({cost, includesData});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lbryUri) {
|
if (!uri) {
|
||||||
return reject(new Error(`URI required.`));
|
return reject(new Error(`URI required.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (costInfoCache[lbryUri] && costInfoCache[lbryUri].cost) {
|
if (costInfoCache[uri] && costInfoCache[uri].cost) {
|
||||||
return resolve(costInfoCache[lbryUri])
|
return resolve(costInfoCache[uri])
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCost(lbryUri, size) {
|
function getCost(uri, size) {
|
||||||
lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => {
|
lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
|
||||||
cacheAndResolve(cost, size !== null);
|
cacheAndResolve(cost, size !== null);
|
||||||
}, reject);
|
}, reject);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCostGenerous(lbryUri) {
|
function getCostGenerous(uri) {
|
||||||
// If generous is on, the calculation is simple enough that we might as well do it here in the front end
|
// If generous is on, the calculation is simple enough that we might as well do it here in the front end
|
||||||
lbry.resolve({uri: lbryUri}).then((resolutionInfo) => {
|
lbry.resolve({uri: uri}).then((resolutionInfo) => {
|
||||||
const fee = resolutionInfo.claim.value.stream.metadata.fee;
|
const fee = resolutionInfo.claim.value.stream.metadata.fee;
|
||||||
if (fee === undefined) {
|
if (fee === undefined) {
|
||||||
cacheAndResolve(0, true);
|
cacheAndResolve(0, true);
|
||||||
|
@ -257,12 +257,12 @@ lbry.getCostInfo = function(lbryUri) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const uriObj = uri.parseLbryUri(lbryUri);
|
const uriObj = lbryuri.parse(uri);
|
||||||
const name = uriObj.path || uriObj.name;
|
const name = uriObj.path || uriObj.name;
|
||||||
|
|
||||||
lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
|
lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
|
||||||
if (is_generous_host) {
|
if (is_generous_host) {
|
||||||
return getCostGenerous(lbryUri);
|
return getCostGenerous(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
lighthouse.get_size_for_name(name).then((size) => {
|
lighthouse.get_size_for_name(name).then((size) => {
|
||||||
|
@ -278,7 +278,7 @@ lbry.getCostInfo = function(lbryUri) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return lbry.costPromiseCache[lbryUri];
|
return lbry.costPromiseCache[uri];
|
||||||
}
|
}
|
||||||
|
|
||||||
lbry.getMyClaims = function(callback) {
|
lbry.getMyClaims = function(callback) {
|
||||||
|
|
|
@ -1,22 +1,31 @@
|
||||||
const CHANNEL_NAME_MIN_LEN = 4;
|
const CHANNEL_NAME_MIN_LEN = 4;
|
||||||
const CLAIM_ID_MAX_LEN = 40;
|
const CLAIM_ID_MAX_LEN = 40;
|
||||||
|
|
||||||
const uri = {};
|
const lbryuri = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a LBRY name into its component parts. Throws errors with user-friendly
|
* Parses a LBRY name into its component parts. Throws errors with user-friendly
|
||||||
* messages for invalid names.
|
* messages for invalid names.
|
||||||
*
|
*
|
||||||
|
* N.B. that "name" indicates the value in the name position of the URI. For
|
||||||
|
* claims for channel content, this will actually be the channel name, and
|
||||||
|
* the content name is in the path (e.g. lbry://@channel/content)
|
||||||
|
*
|
||||||
|
* In most situations, you'll want to use the contentName and channelName keys
|
||||||
|
* and ignore the name key.
|
||||||
|
*
|
||||||
* Returns a dictionary with keys:
|
* Returns a dictionary with keys:
|
||||||
* - name (string)
|
* - name (string): The value in the "name" position in the URI. Note that this
|
||||||
* - properName (string; strips off @ for channels)
|
* could be either content name or channel name; see above.
|
||||||
* - isChannel (boolean)
|
* - path (string, if persent)
|
||||||
* - claimSequence (int, if present)
|
* - claimSequence (int, if present)
|
||||||
* - bidPosition (int, if present)
|
* - bidPosition (int, if present)
|
||||||
* - claimId (string, if present)
|
* - claimId (string, if present)
|
||||||
* - path (string, if persent)
|
* - isChannel (boolean)
|
||||||
|
* - contentName (string): For anon claims, the name; for channel claims, the path
|
||||||
|
* - channelName (string, if present): Channel name without @
|
||||||
*/
|
*/
|
||||||
uri.parseLbryUri = function(lbryUri, requireProto=false) {
|
lbryuri.parse = function(uri, requireProto=false) {
|
||||||
// Break into components. Empty sub-matches are converted to null
|
// Break into components. Empty sub-matches are converted to null
|
||||||
const componentsRegex = new RegExp(
|
const componentsRegex = new RegExp(
|
||||||
'^((?:lbry:\/\/)?)' + // protocol
|
'^((?:lbry:\/\/)?)' + // protocol
|
||||||
|
@ -24,7 +33,9 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
|
||||||
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
|
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
|
||||||
'(/?)(.*)' // path separator, path
|
'(/?)(.*)' // path separator, path
|
||||||
);
|
);
|
||||||
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(lbryUri).slice(1).map(match => match || null);
|
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(uri).slice(1).map(match => match || null);
|
||||||
|
|
||||||
|
let contentName;
|
||||||
|
|
||||||
// Validate protocol
|
// Validate protocol
|
||||||
if (requireProto && !proto) {
|
if (requireProto && !proto) {
|
||||||
|
@ -36,20 +47,22 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
|
||||||
throw new Error('URI does not include name.');
|
throw new Error('URI does not include name.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChannel = name[0] == '@';
|
const isChannel = name.startsWith('@');
|
||||||
const properName = isChannel ? name.substr(1) : name;
|
const channelName = isChannel ? name.slice(1) : name;
|
||||||
|
|
||||||
if (isChannel) {
|
if (isChannel) {
|
||||||
if (!properName) {
|
if (!channelName) {
|
||||||
throw new Error('No channel name after @.');
|
throw new Error('No channel name after @.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properName.length < CHANNEL_NAME_MIN_LEN) {
|
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
|
||||||
throw new Error(`Channel names must be at least ${CHANNEL_NAME_MIN_LEN} characters.`);
|
throw new Error(`Channel names must be at least ${CHANNEL_NAME_MIN_LEN} characters.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentName = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameBadChars = properName.match(/[^A-Za-z0-9-]/g);
|
const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g);
|
||||||
if (nameBadChars) {
|
if (nameBadChars) {
|
||||||
throw new Error(`Invalid character${nameBadChars.length == 1 ? '' : 's'} in name: ${nameBadChars.join(', ')}.`);
|
throw new Error(`Invalid character${nameBadChars.length == 1 ? '' : 's'} in name: ${nameBadChars.join(', ')}.`);
|
||||||
}
|
}
|
||||||
|
@ -82,7 +95,7 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
|
||||||
throw new Error('Bid position must be a number.');
|
throw new Error('Bid position must be a number.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate path
|
// Validate and process path
|
||||||
if (path) {
|
if (path) {
|
||||||
if (!isChannel) {
|
if (!isChannel) {
|
||||||
throw new Error('Only channel URIs may have a path.');
|
throw new Error('Only channel URIs may have a path.');
|
||||||
|
@ -92,12 +105,16 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
|
||||||
if (pathBadChars) {
|
if (pathBadChars) {
|
||||||
throw new Error(`Invalid character${count == 1 ? '' : 's'} in path: ${nameBadChars.join(', ')}`);
|
throw new Error(`Invalid character${count == 1 ? '' : 's'} in path: ${nameBadChars.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentName = path;
|
||||||
} else if (pathSep) {
|
} else if (pathSep) {
|
||||||
throw new Error('No path provided after /');
|
throw new Error('No path provided after /');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name, properName, isChannel,
|
name, path, isChannel,
|
||||||
|
... contentName ? {contentName} : {},
|
||||||
|
... channelName ? {channelName} : {},
|
||||||
... claimSequence ? {claimSequence: parseInt(claimSequence)} : {},
|
... claimSequence ? {claimSequence: parseInt(claimSequence)} : {},
|
||||||
... bidPosition ? {bidPosition: parseInt(bidPosition)} : {},
|
... bidPosition ? {bidPosition: parseInt(bidPosition)} : {},
|
||||||
... claimId ? {claimId} : {},
|
... claimId ? {claimId} : {},
|
||||||
|
@ -105,20 +122,45 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
uri.buildLbryUri = function(uriObj, includeProto=true) {
|
/**
|
||||||
const {name, claimId, claimSequence, bidPosition, path} = uriObj;
|
* Takes an object in the same format returned by lbryuri.parse() and builds a URI.
|
||||||
|
*
|
||||||
|
* The channelName key will accept names with or without the @ prefix.
|
||||||
|
*/
|
||||||
|
lbryuri.build = function(uriObj, includeProto=true, allowExtraProps=false) {
|
||||||
|
let {name, claimId, claimSequence, bidPosition, path, contentName, channelName} = uriObj;
|
||||||
|
|
||||||
|
if (channelName) {
|
||||||
|
const channelNameFormatted = channelName.startsWith('@') ? channelName : '@' + channelName;
|
||||||
|
if (!name) {
|
||||||
|
name = channelNameFormatted;
|
||||||
|
} else if (name !== channelNameFormatted) {
|
||||||
|
throw new Error('Received a channel content URI, but name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentName) {
|
||||||
|
if (!path) {
|
||||||
|
path = contentName;
|
||||||
|
} else if (path !== contentName) {
|
||||||
|
throw new Error('path and contentName do not match. Only one is required; most likely you wanted contentName.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (includeProto ? 'lbry://' : '') + name +
|
return (includeProto ? 'lbry://' : '') + name +
|
||||||
(claimId ? `#${claimId}` : '') +
|
(claimId ? `#${claimId}` : '') +
|
||||||
(claimSequence ? `:${claimSequence}` : '') +
|
(claimSequence ? `:${claimSequence}` : '') +
|
||||||
(bidPosition ? `\$${bidPosition}` : '') +
|
(bidPosition ? `\$${bidPosition}` : '') +
|
||||||
(path ? `/${path}` : '');
|
(path ? `/${path}` : '');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just
|
/* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just
|
||||||
* consists of making sure it has a lbry:// prefix) */
|
* consists of adding the lbry:// prefix if needed) */
|
||||||
uri.normalizeLbryUri = function(lbryUri) {
|
lbryuri.normalize= function(uri) {
|
||||||
return uri.buildLbryUri(uri.parseLbryUri(lbryUri));
|
const {name, path, bidPosition, claimSequence, claimId} = lbryuri.parse(uri);
|
||||||
|
return lbryuri.build({name, path, claimSequence, bidPosition, claimId});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default uri;
|
window.lbryuri = lbryuri;
|
||||||
|
export default lbryuri;
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import lbryio from '../lbryio.js';
|
import lbryio from '../lbryio.js';
|
||||||
import uri from '../uri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import lighthouse from '../lighthouse.js';
|
import lighthouse from '../lighthouse.js';
|
||||||
import {FileTile, FileTileStream} from '../component/file-tile.js';
|
import {FileTile, FileTileStream} from '../component/file-tile.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
|
@ -47,22 +47,14 @@ var SearchResults = React.createClass({
|
||||||
var rows = [],
|
var rows = [],
|
||||||
seenNames = {}; //fix this when the search API returns claim IDs
|
seenNames = {}; //fix this when the search API returns claim IDs
|
||||||
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) {
|
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) {
|
||||||
let lbryUri;
|
const uri = lbryuri.build({
|
||||||
if (channel_name) {
|
channelName: channel_name,
|
||||||
lbryUri = uri.buildLbryUri({
|
contentName: name,
|
||||||
name: channel_name,
|
claimId: channel_id || claim_id,
|
||||||
path: name,
|
|
||||||
claimId: channel_id,
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
lbryUri = uri.buildLbryUri({
|
|
||||||
name: name,
|
|
||||||
claimId: claim_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
<FileTileStream key={name} uri={lbryUri} outpoint={txid + ':' + nout} metadata={claim.stream.metadata} contentType={claim.stream.source.contentType} />
|
<FileTileStream key={name} uri={uri} outpoint={txid + ':' + nout} metadata={claim.stream.metadata} contentType={claim.stream.source.contentType} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import uri from '../uri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import {FormField} from '../component/form.js';
|
import {FormField} from '../component/form.js';
|
||||||
import {FileTileStream} from '../component/file-tile.js';
|
import {FileTileStream} from '../component/file-tile.js';
|
||||||
|
@ -196,14 +196,9 @@ export let FileList = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let fileUri;
|
const uri = lbryuri.build({contentName: name, channelName: channel_name});
|
||||||
if (!channel_name) {
|
|
||||||
fileUri = uri.buildLbryUri({name});
|
|
||||||
} else {
|
|
||||||
fileUri = uri.buildLbryUri({name: channel_name, path: name});
|
|
||||||
}
|
|
||||||
seenUris[name] = true;
|
seenUris[name] = true;
|
||||||
content.push(<FileTileStream key={outpoint} outpoint={outpoint} uri={fileUri} hideOnRemove={true}
|
content.push(<FileTileStream key={outpoint} outpoint={outpoint} uri={uri} hideOnRemove={true}
|
||||||
hidePrice={this.props.hidePrices} metadata={streamMetadata} contentType={mime_type}
|
hidePrice={this.props.hidePrices} metadata={streamMetadata} contentType={mime_type}
|
||||||
hasSignature={has_signature} signatureIsValid={signature_is_valid} />);
|
hasSignature={has_signature} signatureIsValid={signature_is_valid} />);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import uri from '../uri.js';
|
|
||||||
import {FormField, FormRow} from '../component/form.js';
|
import {FormField, FormRow} from '../component/form.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import rewards from '../rewards.js';
|
import rewards from '../rewards.js';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import lighthouse from '../lighthouse.js';
|
import lighthouse from '../lighthouse.js';
|
||||||
import uri from '../uri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Video} from '../page/watch.js'
|
import {Video} from '../page/watch.js'
|
||||||
import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js';
|
import {TruncatedText, Thumbnail, FilePrice, BusyMessage} from '../component/common.js';
|
||||||
import {FileActions} from '../component/file-actions.js';
|
import {FileActions} from '../component/file-actions.js';
|
||||||
|
@ -59,7 +59,7 @@ let ShowPage = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._uri = uri.normalizeLbryUri(this.props.uri);
|
this._uri = lbryuri.normalize(this.props.uri);
|
||||||
document.title = this._uri;
|
document.title = this._uri;
|
||||||
|
|
||||||
lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => {
|
lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => {
|
||||||
|
|
Loading…
Reference in a new issue