paginates over search count rather than channel claim count #3271

Merged
jessopb merged 4 commits from fix-noMatureSearchPagination into master 2019-12-04 00:47:48 +01:00
166 changed files with 10880 additions and 2352 deletions
Showing only changes of commit ba84eb07a5 - Show all commits

View file

@ -2,10 +2,8 @@ module.exports = api => {
api.cache(false); api.cache(false);
return { return {
presets: ['@babel/env', '@babel/react', '@babel/flow'], presets: [['@babel/env', { loose: true, modules: false }], '@babel/react', '@babel/flow'],
plugins: [ plugins: [
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
'import-glob', 'import-glob',
'@babel/plugin-transform-runtime', '@babel/plugin-transform-runtime',
['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],

View file

@ -1,5 +1,7 @@
const config = require('../../config'); const config = require('../../config');
const redirectHosts = ['open.lbry.com']; const PAGES = require('../../ui/constants/pages');
const { formatInAppUrl } = require('../../ui/util/url');
const { parseURI } = require('lbry-redux');
async function redirectMiddleware(ctx, next) { async function redirectMiddleware(ctx, next) {
const requestHost = ctx.host; const requestHost = ctx.host;
@ -16,8 +18,16 @@ async function redirectMiddleware(ctx, next) {
return; return;
} }
if (redirectHosts.includes(requestHost)) { if (requestHost === 'open.lbry.com' || requestHost === 'open.lbry.io') {
const redirectUrl = config.URL + path; const openQuery = '?src=open';
let redirectUrl = config.URL + formatInAppUrl(url, openQuery);
if (redirectUrl.includes('?')) {
redirectUrl = redirectUrl.replace('?', `${openQuery}&`);
} else {
redirectUrl += openQuery;
}
ctx.redirect(redirectUrl); ctx.redirect(redirectUrl);
return; return;
} }

View file

@ -38,6 +38,7 @@
"@babel/core": "^7.0.0", "@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-decorators": "^7.3.0", "@babel/plugin-proposal-decorators": "^7.3.0",
"@babel/plugin-proposal-object-rest-spread": "^7.6.2",
"@babel/plugin-transform-flow-strip-types": "^7.2.3", "@babel/plugin-transform-flow-strip-types": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.4.3", "@babel/plugin-transform-runtime": "^7.4.3",
"@babel/polyfill": "^7.2.5", "@babel/polyfill": "^7.2.5",

View file

@ -28,9 +28,9 @@ const webConfig = {
{ {
loader: 'babel-loader', loader: 'babel-loader',
test: /\.jsx?$/, test: /\.jsx?$/,
exclude: /node_modules/,
options: { options: {
rootMode: 'upward', presets: ['@babel/env', '@babel/react', '@babel/flow'],
plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'],
}, },
}, },
{ {

View file

@ -67,7 +67,7 @@
"@babel/register": "^7.0.0", "@babel/register": "^7.0.0",
"@exponent/electron-cookies": "^2.0.0", "@exponent/electron-cookies": "^2.0.0",
"@hot-loader/react-dom": "^16.8", "@hot-loader/react-dom": "^16.8",
"@lbry/components": "^2.8.0", "@lbry/components": "^3.0.2",
"@reach/menu-button": "^0.1.18", "@reach/menu-button": "^0.1.18",
"@reach/rect": "^0.2.1", "@reach/rect": "^0.2.1",
"@reach/tabs": "^0.1.5", "@reach/tabs": "^0.1.5",
@ -79,6 +79,7 @@
"babel-plugin-add-module-exports": "^1.0.0", "babel-plugin-add-module-exports": "^1.0.0",
"babel-plugin-import-glob": "^2.0.0", "babel-plugin-import-glob": "^2.0.0",
"babel-plugin-transform-imports": "^1.5.1", "babel-plugin-transform-imports": "^1.5.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"bluebird": "^3.5.1", "bluebird": "^3.5.1",
"chalk": "^2.4.2", "chalk": "^2.4.2",
"classnames": "^2.2.5", "classnames": "^2.2.5",
@ -167,6 +168,7 @@
"redux-persist-transform-compress": "^4.2.0", "redux-persist-transform-compress": "^4.2.0",
"redux-persist-transform-filter": "0.0.16", "redux-persist-transform-filter": "0.0.16",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"reakit": "^1.0.0-beta.13",
"remark": "^9.0.0", "remark": "^9.0.0",
"remark-attr": "^0.8.3", "remark-attr": "^0.8.3",
"remark-emoji": "^2.0.1", "remark-emoji": "^2.0.1",
@ -184,7 +186,7 @@
"tree-kill": "^1.1.0", "tree-kill": "^1.1.0",
"unist-util-visit": "^1.4.1", "unist-util-visit": "^1.4.1",
"video.js": "^7.2.2", "video.js": "^7.2.2",
"villain": "btzr-io/Villain", "villain-react": "^1.0.6",
"wavesurfer.js": "^2.2.1", "wavesurfer.js": "^2.2.1",
"webpack": "^4.28.4", "webpack": "^4.28.4",
"webpack-bundle-analyzer": "^3.1.0", "webpack-bundle-analyzer": "^3.1.0",

View file

@ -820,7 +820,6 @@
"Advanced Editor": "Advanced Editor", "Advanced Editor": "Advanced Editor",
"If you bid more than %amount% LBC, when someone navigates to %uri%, it will load your published content. However, you can get a longer version of this URL for any bid.": "If you bid more than %amount% LBC, when someone navigates to %uri%, it will load your published content. However, you can get a longer version of this URL for any bid.", "If you bid more than %amount% LBC, when someone navigates to %uri%, it will load your published content. However, you can get a longer version of this URL for any bid.": "If you bid more than %amount% LBC, when someone navigates to %uri%, it will load your published content. However, you can get a longer version of this URL for any bid.",
"%nameOrTitle% has been published to lbry://%name%. Click here to view it.": "%nameOrTitle% has been published to lbry://%name%. Click here to view it.", "%nameOrTitle% has been published to lbry://%name%. Click here to view it.": "%nameOrTitle% has been published to lbry://%name%. Click here to view it.",
"Discussion": "Discussion",
"If you don't choose a file, the file from your existing claim %name% will be used": "If you don't choose a file, the file from your existing claim %name% will be used", "If you don't choose a file, the file from your existing claim %name% will be used": "If you don't choose a file, the file from your existing claim %name% will be used",
"To enable this feature, check 'Save Password' the next time you start the app.": "To enable this feature, check 'Save Password' the next time you start the app.", "To enable this feature, check 'Save Password' the next time you start the app.": "To enable this feature, check 'Save Password' the next time you start the app.",
"for adding a subscription!": "for adding a subscription!", "for adding a subscription!": "for adding a subscription!",
@ -890,4 +889,18 @@
"allow you to receive notifications related to new content.": "allow you to receive notifications related to new content.", "allow you to receive notifications related to new content.": "allow you to receive notifications related to new content.",
"files": "files", "files": "files",
"Invalid claim ID %claimId%.": "Invalid claim ID %claimId%." "Invalid claim ID %claimId%.": "Invalid claim ID %claimId%."
} "Suggested": "Suggested",
"%amountBehind% blocks behind": "%amountBehind% blocks behind",
"Startup Preferences": "Startup Preferences",
"This will clear the application cache, and might fix issues you are having. Your wallet will not be affected. ": "This will clear the application cache, and might fix issues you are having. Your wallet will not be affected. ",
"Start minimized": "Start minimized",
"Improve view speed and help the LBRY network by allowing the app to cuddle up in your system tray.": "Improve view speed and help the LBRY network by allowing the app to cuddle up in your system tray.",
"Content Type": "Content Type",
"Submit Feedback": "Submit Feedback",
"Checking your publishes...": "Checking your publishes...",
"Checking your publishes": "Checking your publishes",
"Checking for channels": "Checking for channels",
"Error Starting Up": "Error Starting Up",
"Reach out to hello@lbry.com for help, or check out %help_link%.": "Reach out to hello@lbry.com for help, or check out %help_link%.",
"You're not following any tags. Smash that %customize% button!": "You're not following any tags. Smash that %customize% button!",
"customize": "customize"

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -113,7 +113,7 @@ function App(props: Props) {
useEffect(() => { useEffect(() => {
// $FlowFixMe // $FlowFixMe
document.documentElement.setAttribute('data-mode', theme); document.documentElement.setAttribute('theme', theme);
}, [theme]); }, [theme]);
useEffect(() => { useEffect(() => {

View file

@ -26,7 +26,6 @@ export default function BlockButton(props: Props) {
return permanentUrl && !claimIsMine ? ( return permanentUrl && !claimIsMine ? (
<Button <Button
ref={blockRef} ref={blockRef}
iconColor="red"
icon={ICONS.BLOCK} icon={ICONS.BLOCK}
button={'alt'} button={'alt'}
label={blockedOverride || blockLabel} label={blockedOverride || blockLabel}

View file

@ -4,7 +4,7 @@ import React, { forwardRef, useRef } from 'react';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import classnames from 'classnames'; import classnames from 'classnames';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
import { OutboundLink } from 'react-ga'; import { OutboundLink } from 'react-ga';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import useCombinedRefs from 'effects/use-combined-refs'; import useCombinedRefs from 'effects/use-combined-refs';
@ -25,7 +25,6 @@ type Props = {
button: ?string, // primary, secondary, alt, link button: ?string, // primary, secondary, alt, link
iconSize?: number, iconSize?: number,
iconColor?: string, iconColor?: string,
constrict: ?boolean, // to shorten the button and ellipsis, only use for links
activeClass?: string, activeClass?: string,
innerRef: ?any, innerRef: ?any,
// Events // Events
@ -60,7 +59,6 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
button, button,
iconSize, iconSize,
iconColor, iconColor,
constrict,
activeClass, activeClass,
emailVerified, emailVerified,
requiresAuth, requiresAuth,
@ -81,7 +79,6 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
'button--close': button === 'close', 'button--close': button === 'close',
'button--disabled': disabled, 'button--disabled': disabled,
'button--link': button === 'link', 'button--link': button === 'link',
'button--constrict': constrict,
} }
: 'button--no-style', : 'button--no-style',
className className
@ -111,7 +108,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
let path = navigate; let path = navigate;
if (path) { if (path) {
if (path.startsWith('lbry://')) { if (path.startsWith('lbry://')) {
path = formatLbryUriForWeb(path); path = formatLbryUrlForWeb(path);
} else if (!path.startsWith('/')) { } else if (!path.startsWith('/')) {
// Force a leading slash so new paths aren't appended on to the current path // Force a leading slash so new paths aren't appended on to the current path
path = `/${path}`; path = `/${path}`;

View file

@ -13,9 +13,11 @@ $transition-duration: 300ms;
z-index: $base-zindex; z-index: $base-zindex;
vertical-align: top; vertical-align: top;
opacity: 0; opacity: 0;
border-radius: var(--border-radius);
} }
.ff-canvas { .ff-canvas {
border-radius: var(--border-radius);
display: inline-block; display: inline-block;
position: absolute; position: absolute;
top: 0; top: 0;

View file

@ -27,23 +27,23 @@ function ChannelContent(props: Props) {
{showAbout && ( {showAbout && (
<Fragment> <Fragment>
{description && ( {description && (
<div className="media__info-text media__info-text--small"> <div className="media__info-text media__info-text--constrained">
<MarkdownPreview content={description} promptLinks /> <MarkdownPreview content={description} />
</div> </div>
)} )}
{email && ( {email && (
<Fragment> <Fragment>
<div className="media__info-title">{__('Contact')}</div> <label>{__('Contact')}</label>
<div className="media__info-text"> <div className="media__info-text">
<MarkdownPreview content={formatEmail(email)} promptLinks /> <MarkdownPreview content={formatEmail(email)} />
</div> </div>
</Fragment> </Fragment>
)} )}
{website && ( {website && (
<Fragment> <Fragment>
<div className="media__info-title">{__('Site')}</div> <label>{__('Site')}</label>
<div className="media__info-text"> <div className="media__info-text">
<MarkdownPreview content={website} promptLinks /> <MarkdownPreview content={website} />
</div> </div>
</Fragment> </Fragment>
)} )}

View file

@ -41,7 +41,7 @@ function ChannelContent(props: Props) {
{!fetching && !hasContent && !channelIsBlocked && !channelIsBlackListed && ( {!fetching && !hasContent && !channelIsBlocked && !channelIsBlackListed && (
<div className="card--section"> <div className="card--section">
<h2 className="help">{__("This channel hasn't uploaded anything.")}</h2> <h2 className="section__subtitle">{__("This channel hasn't uploaded anything.")}</h2>
</div> </div>
)} )}

View file

@ -3,9 +3,9 @@ import React, { useState } from 'react';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import SelectAsset from 'component/selectAsset'; import SelectAsset from 'component/selectAsset';
import TagsSelect from 'component/tagsSelect';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import { MINIMUM_PUBLISH_BID } from 'constants/claim'; import { MINIMUM_PUBLISH_BID } from 'constants/claim';
import TagsSearch from 'component/tagsSearch';
type Props = { type Props = {
claim: ChannelClaim, claim: ChannelClaim,
@ -173,13 +173,16 @@ function ChannelForm(props: Props) {
disabled={false} disabled={false}
onChange={text => setParams({ ...params, description: text })} onChange={text => setParams({ ...params, description: text })}
/> />
<TagsSelect
title={__('Add Tags')} <TagsSearch
suggestMature suggestMature
disableAutoFocus disabledAutoFocus
help={__('The better your tags are, the easier it will be for people to discover your channel.')} tagsPassedIn={params.tags || []}
empty={__('No tags added')} label={__('Tags Selected')}
placeholder={__('Add a tag')} onRemove={clickedTag => {
const newTags = params.tags.slice().filter(tag => tag.name !== clickedTag.name);
setParams({ ...params, tags: newTags });
}}
onSelect={newTags => { onSelect={newTags => {
newTags.forEach(newTag => { newTags.forEach(newTag => {
if (!params.tags.map(savedTag => savedTag.name).includes(newTag.name)) { if (!params.tags.map(savedTag => savedTag.name).includes(newTag.name)) {
@ -190,11 +193,6 @@ function ChannelForm(props: Props) {
} }
}); });
}} }}
onRemove={clickedTag => {
const newTags = params.tags.slice().filter(tag => tag.name !== clickedTag.name);
setParams({ ...params, tags: newTags });
}}
tagsChosen={params.tags || []}
/> />
<div className={'card__actions'}> <div className={'card__actions'}>
<Button button="primary" label={__('Submit')} onClick={handleSubmit} /> <Button button="primary" label={__('Submit')} onClick={handleSubmit} />

View file

@ -18,7 +18,7 @@ function ChannelThumbnail(props: Props) {
// Generate a random color class based on the first letter of the channel name // Generate a random color class based on the first letter of the channel name
const { channelName } = parseURI(uri); const { channelName } = parseURI(uri);
const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57 const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57
const colorClassName = `channel-thumbnail__default--${initializer % 4}`; const colorClassName = `channel-thumbnail__default--${Math.abs(initializer % 4)}`;
const showThumb = !obscure && !!thumbnail; const showThumb = !obscure && !!thumbnail;
return ( return (
<div <div

View file

@ -121,7 +121,13 @@ export default function ClaimList(props: Props) {
{urisLength > 0 && ( {urisLength > 0 && (
<ul className="ul--no-style"> <ul className="ul--no-style">
{sortedUris.map((uri, index) => ( {sortedUris.map((uri, index) => (
<ClaimPreview key={uri} uri={uri} type={type} showUserBlocked={showHiddenByUser} /> <ClaimPreview
key={uri}
uri={uri}
type={type}
properties={type !== 'small' ? undefined : false}
showUserBlocked={showHiddenByUser}
/>
))} ))}
</ul> </ul>
)} )}

View file

@ -1,5 +1,6 @@
// @flow // @flow
import type { Node } from 'react'; import type { Node } from 'react';
import * as PAGES from 'constants/pages';
import React, { Fragment, useEffect, useState } from 'react'; import React, { Fragment, useEffect, useState } from 'react';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux'; import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
@ -173,7 +174,26 @@ function ClaimListDiscover(props: Props) {
</div> </div>
); );
const emptyState = !loading && (personalSort === SEARCH_SORT_CHANNELS && !hasContent ? noChannels : noResults); const noTags = (
<div>
<p>
<I18nMessage
tokens={{
customize: <Button
button="link"
navigate={`/$/${PAGES.FOLLOWING}`}
label={__('customize')}
/>,
}}
>
You're not following any tags. Add tags above or smash that %customize% button!
</I18nMessage>
</p>
</div>
);
const noFollowing = (personalSort === SEARCH_SORT_YOU && noTags) || (personalSort === SEARCH_SORT_CHANNELS && noChannels);
const emptyState = !loading && !hasContent ? noFollowing : noResults;
function getSearch() { function getSearch() {
let search = `?`; let search = `?`;
@ -234,9 +254,9 @@ function ClaimListDiscover(props: Props) {
</FormField> </FormField>
{!hideCustomization && ( {!hideCustomization && (
<Fragment> <Fragment>
<span>{__('For')}</span> <span className="claim-list__conjuction">{__('for')}</span>
{!personalView && tags && tags.length ? ( {!personalView && tags && tags.length ? (
tags.map(tag => <Tag key={tag} name={tag} disabled />) tags.map(tag => <Tag key={tag} name={tag} disabled type="large" />)
) : ( ) : (
<FormField <FormField
type="select" type="select"

View file

@ -5,7 +5,7 @@ import classnames from 'classnames';
import { parseURI, convertToShareLink } from 'lbry-redux'; import { parseURI, convertToShareLink } from 'lbry-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { openCopyLinkMenu } from 'util/context-menu'; import { openCopyLinkMenu } from 'util/context-menu';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
import { isEmpty } from 'util/object'; import { isEmpty } from 'util/object';
import CardMedia from 'component/cardMedia'; import CardMedia from 'component/cardMedia';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
@ -49,6 +49,7 @@ type Props = {
actions: boolean | Node | string | number, actions: boolean | Node | string | number,
properties: boolean | Node | string | number, properties: boolean | Node | string | number,
onClick?: any => any, onClick?: any => any,
hideBlock?: boolean,
}; };
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -77,6 +78,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
actions, actions,
properties, properties,
onClick, onClick,
hideBlock,
} = props; } = props;
const shouldFetch = const shouldFetch =
claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending); claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending);
@ -147,7 +149,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
if (onClick) { if (onClick) {
onClick(e); onClick(e);
} else if ((isChannel || title) && !pending) { } else if ((isChannel || title) && !pending) {
history.push(formatLbryUriForWeb(claim && claim.canonical_url ? claim.canonical_url : uri)); history.push(formatLbryUrlForWeb(claim && claim.canonical_url ? claim.canonical_url : uri));
} }
} }
@ -184,6 +186,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
'claim-preview--large': type === 'large', 'claim-preview--large': type === 'large',
'claim-preview--inline': type === 'inline', 'claim-preview--inline': type === 'inline',
'claim-preview--tooltip': type === 'tooltip', 'claim-preview--tooltip': type === 'tooltip',
'claim-preview--channel': isChannel,
'claim-preview--visited': !isChannel && !claimIsMine && hasVisitedUri, 'claim-preview--visited': !isChannel && !claimIsMine && hasVisitedUri,
'claim-preview--pending': pending, 'claim-preview--pending': pending,
})} })}
@ -203,7 +206,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
{isChannel && !channelIsBlocked && !claimIsMine && ( {isChannel && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} /> <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)} )}
{isChannel && !isSubscribed && !claimIsMine && ( {!hideBlock && isChannel && !isSubscribed && !claimIsMine && (
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} /> <BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)} )}
{!isChannel && claim && <FileProperties uri={uri} />} {!isChannel && claim && <FileProperties uri={uri} />}

View file

@ -1,5 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import classnames from 'classnames';
import { clipboard } from 'electron'; import { clipboard } from 'electron';
import Button from 'component/button'; import Button from 'component/button';
@ -7,14 +8,15 @@ type Props = {
shortUrl: ?string, shortUrl: ?string,
uri: string, uri: string,
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
inline?: boolean,
}; };
function ClaimUri(props: Props) { function ClaimUri(props: Props) {
const { shortUrl, uri, doToast } = props; const { shortUrl, uri, doToast, inline = false } = props;
return ( return (
<Button <Button
className="media__uri" className={classnames('media__uri', { 'media__uri--inline': inline })}
button="alt" button="alt"
label={shortUrl || uri} label={shortUrl || uri}
onClick={() => { onClick={() => {

View file

@ -18,11 +18,16 @@ function Comment(props: Props) {
return ( return (
<li className="comment"> <li className="comment">
<div className="comment__meta"> <div className="comment__meta">
<Button {!author ? (
className="button--uri-indicator truncated-text comment__author" <span className="comment__author">{__('Anonymous')}</span>
navigate={authorUri} ) : (
label={author || __('Anonymous')} <Button
/> className="button--uri-indicator truncated-text comment__author"
navigate={authorUri}
label={author}
/>
)}
<time className="comment__time" dateTime={timePosted}> <time className="comment__time" dateTime={timePosted}>
{relativeDate(timePosted)} {relativeDate(timePosted)}
</time> </time>
@ -30,7 +35,7 @@ function Comment(props: Props) {
<div> <div>
<Expandable> <Expandable>
<div className={'comment__message'}> <div className={'comment__message'}>
<MarkdownPreview content={message} promptLinks /> <MarkdownPreview content={message} />
</div> </div>
</Expandable> </Expandable>
</div> </div>

View file

@ -10,19 +10,21 @@ type Props = {
body?: string | Node, body?: string | Node,
actions?: string | Node, actions?: string | Node,
icon?: string, icon?: string,
className?: string,
actionIconPadding?: boolean,
}; };
export default function Card(props: Props) { export default function Card(props: Props) {
const { title, subtitle, body, actions, icon } = props; const { title, subtitle, body, actions, icon, className, actionIconPadding = true } = props;
return ( return (
<section className={classnames('card')}> <section className={classnames(className, 'card')}>
{title && ( {title && (
<div className="card__header"> <div className="card__header">
<div className="section__flex"> <div className="section__flex">
{icon && <Icon sectionIcon icon={icon} />} {icon && <Icon sectionIcon icon={icon} />}
<div> <div>
<h2 className="section__title">{title}</h2> <h2 className="section__title">{title}</h2>
<div className="section__subtitle">{subtitle}</div> {subtitle && <div className="section__subtitle">{subtitle}</div>}
</div> </div>
</div> </div>
</div> </div>
@ -30,7 +32,11 @@ export default function Card(props: Props) {
{body && <div className={classnames('card__body', { 'card__body--with-icon': icon })}>{body}</div>} {body && <div className={classnames('card__body', { 'card__body--with-icon': icon })}>{body}</div>}
{actions && ( {actions && (
<div className={classnames('card__main-actions', { 'card__main-actions--with-icon': icon })}>{actions}</div> <div
className={classnames('card__main-actions', { 'card__main-actions--with-icon': icon && actionIconPadding })}
>
{actions}
</div>
)} )}
</section> </section>
); );

View file

@ -66,7 +66,7 @@ class FileSelector extends React.PureComponent<Props> {
readOnly="readonly" readOnly="readonly"
value={placeHolder || __('Choose a file')} value={placeHolder || __('Choose a file')}
inputButton={ inputButton={
<Button button="primary" disabled={disabled} onClick={this.fileInputButton} label={buttonLabel} /> <Button button="secondary" disabled={disabled} onClick={this.fileInputButton} label={buttonLabel} />
} }
/> />
<input <input

View file

@ -80,36 +80,24 @@ export class FormField extends React.PureComponent<Props> {
const errorMessage = typeof error === 'object' ? error.message : error; const errorMessage = typeof error === 'object' ? error.message : error;
const Wrapper = blockWrap const Wrapper = blockWrap
? ({ children: innerChildren }) => <fieldset-section>{innerChildren}</fieldset-section> ? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section>
: ({ children: innerChildren }) => <React.Fragment>{innerChildren}</React.Fragment>; : ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>;
let input; let input;
if (type) { if (type) {
if (type === 'radio') { if (type === 'radio') {
input = ( input = (
<Wrapper> <Wrapper>
<radio-element> <input id={name} type="radio" {...inputProps} />
<input id={name} type="radio" {...inputProps} /> <label htmlFor={name}>{label}</label>
<label htmlFor={name}>{label}</label>
<radio-toggle onClick={inputProps.onChange} />
</radio-element>
</Wrapper> </Wrapper>
); );
} else if (type === 'checkbox') { } else if (type === 'checkbox') {
// web components treat props weird
// we need to fully remove it for proper component:attribute css styling
// $FlowFixMe
const elementProps = inputProps.disabled ? { disabled: true } : {};
input = ( input = (
<Wrapper> <div className="checkbox">
<checkbox-element {...elementProps}> <input id={name} type="checkbox" {...inputProps} />
<input id={name} type="checkbox" {...inputProps} tabIndex={0} /> <label htmlFor={name}>{label}</label>
<label htmlFor={name} tabIndex={-1}> </div>
{label}
</label>
<checkbox-toggle onClick={inputProps.onChange} tabIndex={-1} />
</checkbox-element>
</Wrapper>
); );
} else if (type === 'select') { } else if (type === 'select') {
input = ( input = (
@ -172,7 +160,11 @@ export class FormField extends React.PureComponent<Props> {
input = ( input = (
<React.Fragment> <React.Fragment>
<fieldset-section> <fieldset-section>
<label htmlFor={name}>{errorMessage ? <span className="error-text">{errorMessage}</span> : label}</label> {label && (
<label htmlFor={name}>
{errorMessage ? <span className="error-text">{errorMessage}</span> : label}
</label>
)}
{prefix && <label htmlFor={name}>{prefix}</label>} {prefix && <label htmlFor={name}>{prefix}</label>}
{inner} {inner}
</fieldset-section> </fieldset-section>

View file

@ -11,7 +11,7 @@ export default function UnsupportedOnWeb(props: Props) {
return ( return (
IS_WEB && ( IS_WEB && (
<div className="card__subtitle--status"> <div className="section__subtitle--status">
{type === 'page' && __('This page is not currently supported on the web')} {type === 'page' && __('This page is not currently supported on the web')}
{type === 'feature' && __('This feature is not currently supported on the web')}.{' '} {type === 'feature' && __('This feature is not currently supported on the web')}.{' '}
<Button button="link" label={__('Download the desktop app')} href="https://lbry.com/get" /> for full feature <Button button="link" label={__('Download the desktop app')} href="https://lbry.com/get" /> for full feature

View file

@ -9,10 +9,11 @@ type Props = {
snackMessage: ?string, snackMessage: ?string,
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
label?: string, label?: string,
primaryButton?: boolean,
}; };
export default function CopyableText(props: Props) { export default function CopyableText(props: Props) {
const { copyable, doToast, snackMessage, label } = props; const { copyable, doToast, snackMessage, label, primaryButton = false } = props;
const input = useRef(); const input = useRef();
@ -43,7 +44,7 @@ export default function CopyableText(props: Props) {
onFocus={onFocus} onFocus={onFocus}
inputButton={ inputButton={
<Button <Button
button="inverse" button={primaryButton ? 'primary' : 'secondary'}
icon={ICONS.COPY} icon={ICONS.COPY}
onClick={() => { onClick={() => {
copyToClipboard(); copyToClipboard();

View file

@ -47,17 +47,18 @@ export default function EmbedArea(props: Props) {
label={label} label={label}
value={embedText || ''} value={embedText || ''}
ref={input} ref={input}
helper={
<Button
icon={ICONS.COPY}
button="link"
label={__('Copy')}
onClick={() => {
copyToClipboard();
}}
/>
}
onFocus={onFocus} onFocus={onFocus}
/> />
<div className="card__actions card__actions--center">
<Button
icon={ICONS.COPY}
button="link"
onClick={() => {
copyToClipboard();
}}
/>
</div>
</fieldset-section> </fieldset-section>
); );
} }

View file

@ -23,7 +23,7 @@ export default function Expandable(props: Props) {
return ( return (
<div ref={ref}> <div ref={ref}>
{rect && rect.height > COLLAPSED_HEIGHT ? ( {rect && rect.height > COLLAPSED_HEIGHT ? (
<div ref={ref} className="expandable"> <div ref={ref}>
<div <div
className={classnames({ className={classnames({
'expandable--open': expanded, 'expandable--open': expanded,

View file

@ -5,7 +5,6 @@ import * as React from 'react';
import { isURIValid } from 'lbry-redux'; import { isURIValid } from 'lbry-redux';
import Button from 'component/button'; import Button from 'component/button';
import ClaimLink from 'component/claimLink'; import ClaimLink from 'component/claimLink';
import { isLBRYDomain } from 'util/uri';
type Props = { type Props = {
href: string, href: string,
@ -39,8 +38,7 @@ class ExternalLink extends React.PureComponent<Props> {
label={children} label={children}
className="button--external-link" className="button--external-link"
onClick={() => { onClick={() => {
const isTrusted = isLBRYDomain(href); openModal(MODALS.CONFIRM_EXTERNAL_RESOURCE, { uri: href, isTrusted: false });
openModal(MODALS.CONFIRM_EXTERNAL_RESOURCE, { uri: href, isTrusted: isTrusted });
}} }}
/> />
); );

View file

@ -22,7 +22,7 @@ class FileActions extends React.PureComponent<Props> {
{showDelete && ( {showDelete && (
<Tooltip label={__('Remove from your library')}> <Tooltip label={__('Remove from your library')}>
<Button <Button
button="link" button="alt"
icon={ICONS.DELETE} icon={ICONS.DELETE}
description={__('Delete')} description={__('Delete')}
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })} onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
@ -31,7 +31,7 @@ class FileActions extends React.PureComponent<Props> {
)} )}
{!claimIsMine && ( {!claimIsMine && (
<Tooltip label={__('Report content')}> <Tooltip label={__('Report content')}>
<Button button="link" icon={ICONS.REPORT} href={`https://lbry.com/dmca/${claimId}`} /> <Button button="alt" icon={ICONS.REPORT} href={`https://lbry.com/dmca/${claimId}`} />
</Tooltip> </Tooltip>
)} )}
</React.Fragment> </React.Fragment>

View file

@ -4,8 +4,10 @@ import MarkdownPreview from 'component/common/markdown-preview';
import Button from 'component/button'; import Button from 'component/button';
import Expandable from 'component/expandable'; import Expandable from 'component/expandable';
import path from 'path'; import path from 'path';
import ClaimTags from 'component/claimTags';
type Props = { type Props = {
uri: string,
claim: StreamClaim, claim: StreamClaim,
fileInfo: FileListItem, fileInfo: FileListItem,
metadata: StreamMetadata, metadata: StreamMetadata,
@ -16,7 +18,7 @@ type Props = {
class FileDetails extends PureComponent<Props> { class FileDetails extends PureComponent<Props> {
render() { render() {
const { claim, contentType, fileInfo, metadata, openFolder } = this.props; const { uri, claim, contentType, fileInfo, metadata, openFolder } = this.props;
if (!claim || !metadata) { if (!claim || !metadata) {
return <span className="empty">{__('Empty claim or metadata info.')}</span>; return <span className="empty">{__('Empty claim or metadata info.')}</span>;
@ -43,51 +45,53 @@ class FileDetails extends PureComponent<Props> {
{description && ( {description && (
<Fragment> <Fragment>
<div className="media__info-text"> <div className="media__info-text">
<MarkdownPreview content={description} promptLinks /> <MarkdownPreview content={description} />
</div> </div>
</Fragment> </Fragment>
)} )}
<div className="media__info-title">Info</div> <ClaimTags uri={uri} type="large" />
<div className="media__info-text"> <table className="table table--condensed table--fixed table--file-details">
<div> <tbody>
{__('Content-Type')} <tr>
{': '} <td> {__('Content Type')}</td>
{mediaType} <td>{mediaType}</td>
</div> </tr>
{fileSize && ( {fileSize && (
<div> <tr>
{__('File Size')} <td> {__('File Size')}</td>
{': '} <td>{fileSize}</td>
{fileSize} </tr>
</div> )}
)} {languages && (
<div> <tr>
{__('Languages')} <td>{__('Languages')}</td>
{': '} <td>{languages.join(' ')}</td>
{languages ? languages.join(' ') : null} </tr>
</div> )}
<div> <tr>
{__('License')} <td>{__('License')}</td>
{': '} <td>{license}</td>
{license} </tr>
</div> {downloadPath && (
{downloadPath && ( <tr>
<div> <td>{__('Downloaded to')}</td>
{__('Downloaded to')} <td>
{': '} {/* {downloadPath.replace(/(.{10})/g, '$1\u200b')} */}
<Button <Button
button="link" button="link"
className="button--download-link" className="button--download-link"
onClick={() => { onClick={() => {
if (downloadPath) { if (downloadPath) {
openFolder(downloadPath); openFolder(downloadPath);
} }
}} }}
label={downloadNote || downloadPath} label={downloadNote || downloadPath.replace(/(.{10})/g, '$1\u200b')}
/> />
</div> </td>
)} </tr>
</div> )}
</tbody>
</table>
</Expandable> </Expandable>
</Fragment> </Fragment>
); );

View file

@ -32,7 +32,7 @@ function FileDownloadLink(props: Props) {
return ( return (
<ToolTip label={__('Open file')}> <ToolTip label={__('Open file')}>
<Button <Button
button="link" button="alt"
icon={ICONS.EXTERNAL} icon={ICONS.EXTERNAL}
onClick={() => { onClick={() => {
pause(); pause();
@ -45,7 +45,7 @@ function FileDownloadLink(props: Props) {
return ( return (
<ToolTip label={__('Add to your library')}> <ToolTip label={__('Add to your library')}>
<Button <Button
button="link" button="alt"
icon={ICONS.DOWNLOAD} icon={ICONS.DOWNLOAD}
onClick={() => { onClick={() => {
download(uri); download(uri);

View file

@ -18,10 +18,10 @@ export default function FileProperties(props: Props) {
return ( return (
<div className="file-properties"> <div className="file-properties">
<FilePrice hideFree uri={uri} />
<VideoDuration uri={uri} />
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIBE} />} {isSubscribed && <Icon tooltip icon={icons.SUBSCRIBE} />}
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />} {!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
<FilePrice hideFree uri={uri} />
<VideoDuration className="media__subtitle" uri={uri} />
</div> </div>
); );
} }

View file

@ -7,7 +7,7 @@ import ImageViewer from 'component/viewers/imageViewer';
import AppViewer from 'component/viewers/appViewer'; import AppViewer from 'component/viewers/appViewer';
import Button from 'component/button'; import Button from 'component/button';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
// @if TARGET='web' // @if TARGET='web'
import { generateStreamUrl } from 'util/lbrytv'; import { generateStreamUrl } from 'util/lbrytv';
// @endif // @endif
@ -16,11 +16,11 @@ import path from 'path';
import fs from 'fs'; import fs from 'fs';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
// @if TARGET='app'
import DocumentViewer from 'component/viewers/documentViewer'; import DocumentViewer from 'component/viewers/documentViewer';
import DocxViewer from 'component/viewers/docxViewer';
import HtmlViewer from 'component/viewers/htmlViewer';
import PdfViewer from 'component/viewers/pdfViewer'; import PdfViewer from 'component/viewers/pdfViewer';
import HtmlViewer from 'component/viewers/htmlViewer';
// @if TARGET='app'
import DocxViewer from 'component/viewers/docxViewer';
import ComicBookViewer from 'component/viewers/comicBookViewer'; import ComicBookViewer from 'component/viewers/comicBookViewer';
import ThreeViewer from 'component/viewers/threeViewer'; import ThreeViewer from 'component/viewers/threeViewer';
// @endif // @endif
@ -73,7 +73,7 @@ class FileRender extends React.PureComponent<Props> {
onEndedCb() { onEndedCb() {
const { autoplay, nextUnplayed, history } = this.props; const { autoplay, nextUnplayed, history } = this.props;
if (autoplay && nextUnplayed) { if (autoplay && nextUnplayed) {
history.push(formatLbryUriForWeb(nextUnplayed)); history.push(formatLbryUrlForWeb(nextUnplayed));
} }
} }
@ -102,26 +102,39 @@ class FileRender extends React.PureComponent<Props> {
// Add routes to viewer... // Add routes to viewer...
}; };
// Supported contentTypes
const contentTypes = {
'application/x-ext-mkv': (
<VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.onEndedCb} />
),
'video/x-matroska': (
<VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.onEndedCb} />
),
'application/pdf': <PdfViewer source={downloadPath || source} />,
'text/html': <HtmlViewer source={downloadPath || source} />,
'text/htm': <HtmlViewer source={downloadPath || source} />,
};
// Supported fileType // Supported fileType
const fileTypes = { const fileTypes = {
// @if TARGET='app' // @if TARGET='app'
pdf: <PdfViewer source={downloadPath} />,
docx: <DocxViewer source={downloadPath} />, docx: <DocxViewer source={downloadPath} />,
html: <HtmlViewer source={downloadPath} />,
htm: <HtmlViewer source={downloadPath} />,
// @endif // @endif
// Add routes to viewer... // Add routes to viewer...
}; };
// Check for a valid fileType or mediaType // Check for a valid fileType, mediaType, or contentType
let viewer = (fileType && fileTypes[fileType]) || mediaTypes[mediaType]; let viewer = (fileType && fileTypes[fileType]) || mediaTypes[mediaType] || contentTypes[contentType];
// Check for Human-readable files // Check for Human-readable files
if (!viewer && readableFiles.includes(mediaType)) { if (!viewer && readableFiles.includes(mediaType)) {
viewer = ( viewer = (
<DocumentViewer <DocumentViewer
source={{ source={{
stream: options => fs.createReadStream(downloadPath, options), // @if TARGET='app'
file: options => fs.createReadStream(downloadPath, options),
// @endif
stream: source,
fileType, fileType,
contentType, contentType,
}} }}

View file

@ -5,6 +5,7 @@ import {
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
makeSelectStreamingUrlForUri, makeSelectStreamingUrlForUri,
makeSelectMediaTypeForUri, makeSelectMediaTypeForUri,
makeSelectContentTypeForUri,
makeSelectUriIsStreamable, makeSelectUriIsStreamable,
makeSelectTitleForUri, makeSelectTitleForUri,
} from 'lbry-redux'; } from 'lbry-redux';
@ -23,6 +24,7 @@ const select = (state, props) => {
title: makeSelectTitleForUri(uri)(state), title: makeSelectTitleForUri(uri)(state),
thumbnail: makeSelectThumbnailForUri(uri)(state), thumbnail: makeSelectThumbnailForUri(uri)(state),
mediaType: makeSelectMediaTypeForUri(uri)(state), mediaType: makeSelectMediaTypeForUri(uri)(state),
contentType: makeSelectContentTypeForUri(uri)(state),
fileInfo: makeSelectFileInfoForUri(uri)(state), fileInfo: makeSelectFileInfoForUri(uri)(state),
obscurePreview: makeSelectShouldObscurePreview(uri)(state), obscurePreview: makeSelectShouldObscurePreview(uri)(state),
isPlaying: makeSelectIsPlaying(uri)(state), isPlaying: makeSelectIsPlaying(uri)(state),

View file

@ -15,6 +15,7 @@ import { onFullscreenChange } from 'util/full-screen';
type Props = { type Props = {
mediaType: string, mediaType: string,
contentType: string,
isLoading: boolean, isLoading: boolean,
isPlaying: boolean, isPlaying: boolean,
fileInfo: FileListItem, fileInfo: FileListItem,
@ -47,6 +48,7 @@ export default function FileViewer(props: Props) {
triggerAnalyticsView, triggerAnalyticsView,
claimRewards, claimRewards,
mediaType, mediaType,
contentType,
} = props; } = props;
const [playTime, setPlayTime] = useState(); const [playTime, setPlayTime] = useState();
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect'); const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
@ -56,7 +58,12 @@ export default function FileViewer(props: Props) {
}); });
const inline = pageUri === uri; const inline = pageUri === uri;
const isReadyToPlay = (IS_WEB && isStreamable) || (isStreamable && streamingUrl) || (fileInfo && fileInfo.completed); const forceVideo = ['application/x-ext-mkv', 'video/x-matroska'].includes(contentType);
const webStreamOnly = contentType === 'application/pdf' || mediaType === 'text';
const isReadyToPlay =
(IS_WEB && (isStreamable || webStreamOnly || forceVideo)) ||
((isStreamable || forceVideo) && streamingUrl) ||
(fileInfo && fileInfo.completed);
const loadingMessage = const loadingMessage =
!isStreamable && fileInfo && fileInfo.blobs_completed >= 1 && (!fileInfo.download_path || !fileInfo.written_bytes) !isStreamable && fileInfo && fileInfo.blobs_completed >= 1 && (!fileInfo.download_path || !fileInfo.written_bytes)
? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.") ? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.")

View file

@ -6,6 +6,7 @@ import {
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
makeSelectStreamingUrlForUri, makeSelectStreamingUrlForUri,
makeSelectMediaTypeForUri, makeSelectMediaTypeForUri,
makeSelectContentTypeForUri,
makeSelectUriIsStreamable, makeSelectUriIsStreamable,
} from 'lbry-redux'; } from 'lbry-redux';
import { makeSelectCostInfoForUri } from 'lbryinc'; import { makeSelectCostInfoForUri } from 'lbryinc';
@ -16,6 +17,7 @@ import FileViewer from './view';
const select = (state, props) => ({ const select = (state, props) => ({
thumbnail: makeSelectThumbnailForUri(props.uri)(state), thumbnail: makeSelectThumbnailForUri(props.uri)(state),
mediaType: makeSelectMediaTypeForUri(props.uri)(state), mediaType: makeSelectMediaTypeForUri(props.uri)(state),
contentType: makeSelectContentTypeForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
obscurePreview: makeSelectShouldObscurePreview(props.uri)(state), obscurePreview: makeSelectShouldObscurePreview(props.uri)(state),
isPlaying: makeSelectIsPlaying(props.uri)(state), isPlaying: makeSelectIsPlaying(props.uri)(state),

View file

@ -14,6 +14,7 @@ const SPACE_BAR_KEYCODE = 32;
type Props = { type Props = {
play: string => void, play: string => void,
mediaType: string, mediaType: string,
contentType: string,
isLoading: boolean, isLoading: boolean,
isPlaying: boolean, isPlaying: boolean,
fileInfo: FileListItem, fileInfo: FileListItem,
@ -31,6 +32,7 @@ export default function FileViewer(props: Props) {
const { const {
play, play,
mediaType, mediaType,
contentType,
isPlaying, isPlaying,
fileInfo, fileInfo,
uri, uri,
@ -43,9 +45,11 @@ export default function FileViewer(props: Props) {
costInfo, costInfo,
} = props; } = props;
const cost = costInfo && costInfo.cost; const cost = costInfo && costInfo.cost;
const isPlayable = ['audio', 'video'].includes(mediaType); const forceVideo = ['application/x-ext-mkv', 'video/x-matroska'].includes(contentType);
const isPlayable = ['audio', 'video'].includes(mediaType) || forceVideo;
const fileStatus = fileInfo && fileInfo.status; const fileStatus = fileInfo && fileInfo.status;
const supported = (IS_WEB && isStreamable) || !IS_WEB; const webStreamOnly = contentType === 'application/pdf' || mediaType === 'text';
const supported = (IS_WEB && (isStreamable || webStreamOnly || forceVideo)) || !IS_WEB;
// Wrap this in useCallback because we need to use it to the keyboard effect // Wrap this in useCallback because we need to use it to the keyboard effect
// If we don't a new instance will be created for every render and react will think the dependencies have changed, which will add/remove the listener for every render // If we don't a new instance will be created for every render and react will think the dependencies have changed, which will add/remove the listener for every render

View file

@ -44,7 +44,7 @@ const Header = (props: Props) => {
} = props; } = props;
const authenticated = Boolean(email); const authenticated = Boolean(email);
//on the verify page don't let anyone escape other than by closing the tab to keep session data consistent // on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
const isVerifyPage = history.location.pathname.includes(PAGES.AUTH_VERIFY); const isVerifyPage = history.location.pathname.includes(PAGES.AUTH_VERIFY);
// Sign out if they click the "x" when they are on the password prompt // Sign out if they click the "x" when they are on the password prompt
@ -78,6 +78,9 @@ const Header = (props: Props) => {
<header <header
className={classnames('header', { className={classnames('header', {
'header--minimal': authHeader, 'header--minimal': authHeader,
// @if TARGET='web'
'header--noauth-web': !authenticated,
// @endif
// @if TARGET='app' // @if TARGET='app'
'header--mac': IS_MAC, 'header--mac': IS_MAC,
// @endif // @endif
@ -118,8 +121,8 @@ const Header = (props: Props) => {
</div> </div>
{!authHeader ? ( {!authHeader ? (
<div className={classnames('header__menu', { 'header__menu--small': IS_WEB && !authenticated })}> <div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
{!IS_WEB || authenticated ? ( {(!IS_WEB || authenticated) && (
<Fragment> <Fragment>
<Menu> <Menu>
<MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton> <MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton>
@ -135,7 +138,7 @@ const Header = (props: Props) => {
</MenuList> </MenuList>
</Menu> </Menu>
<Menu> <Menu>
<MenuButton className="header__navigation-item menu__title"> <MenuButton className="header__navigation-item menu__title header__navigation-item--icon">
<Icon size={18} icon={ICONS.ACCOUNT} /> <Icon size={18} icon={ICONS.ACCOUNT} />
</MenuButton> </MenuButton>
<MenuList className="menu__list--header"> <MenuList className="menu__list--header">
@ -161,28 +164,28 @@ const Header = (props: Props) => {
)} )}
</MenuList> </MenuList>
</Menu> </Menu>
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon size={18} icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? __('Dark') : __('Light')}
</MenuItem>
</MenuList>
</Menu>
</Fragment> </Fragment>
) : ( )}
<Menu>
<MenuButton className="header__navigation-item menu__title header__navigation-item--icon">
<Icon size={18} icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.SETTINGS}`)}>
<Icon aria-hidden tootlip icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.HELP}`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? __('Dark') : __('Light')}
</MenuItem>
</MenuList>
</Menu>
{IS_WEB && !authenticated && (
<Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign In')} /> <Button navigate={`/$/${PAGES.AUTH}`} button="primary" label={__('Sign In')} />
)} )}
</div> </div>
@ -193,7 +196,7 @@ const Header = (props: Props) => {
{/* This pushes the close button to the right side */} {/* This pushes the close button to the right side */}
<span /> <span />
<Tooltip label={__('Go Back')}> <Tooltip label={__('Go Back')}>
<Button icon={ICONS.REMOVE} {...closeButtonNavigationProps} /> <Button button="link" icon={ICONS.REMOVE} {...closeButtonNavigationProps} />
</Tooltip> </Tooltip>
</div> </div>
) )

View file

@ -37,8 +37,12 @@ class InviteList extends React.PureComponent<Props> {
return ( return (
<section className="card"> <section className="card">
<div className="table__header"> <div className="table__header">
<h2 className="card__title--between"> <div className="table__header-text--between">
{__('Invite History')} <div>
<h2 className="card__title">{__('Invite History')}</h2>
<p className="section__subtitle">{rewardHelp}</p>
</div>
{referralReward && showClaimable && ( {referralReward && showClaimable && (
<RewardLink <RewardLink
button button
@ -46,8 +50,7 @@ class InviteList extends React.PureComponent<Props> {
reward_type={rewards.TYPE_REFERRAL} reward_type={rewards.TYPE_REFERRAL}
/> />
)} )}
</h2> </div>
<p className="section__subtitle">{rewardHelp}</p>
</div> </div>
<table className="table section"> <table className="table section">

View file

@ -5,17 +5,18 @@ import { Form, FormField } from 'component/common/form';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
import Card from 'component/common/card'; import Card from 'component/common/card';
type FormProps = {
inviteNew: string => void,
errorMessage: ?string,
isPending: boolean,
};
type FormState = { type FormState = {
email: string, email: string,
}; };
class FormInviteNew extends React.PureComponent<FormProps, FormState> { type Props = {
errorMessage: ?string,
inviteNew: string => void,
isPending: boolean,
referralLink: string,
};
class InviteNew extends React.PureComponent<Props, FormState> {
constructor() { constructor() {
super(); super();
@ -26,7 +27,7 @@ class FormInviteNew extends React.PureComponent<FormProps, FormState> {
(this: any).handleSubmit = this.handleSubmit.bind(this); (this: any).handleSubmit = this.handleSubmit.bind(this);
} }
handleEmailChanged(event) { handleEmailChanged(event: any) {
this.setState({ this.setState({
email: event.target.value, email: event.target.value,
}); });
@ -38,40 +39,7 @@ class FormInviteNew extends React.PureComponent<FormProps, FormState> {
} }
render() { render() {
const { errorMessage, isPending } = this.props; const { errorMessage, isPending, referralLink } = this.props;
return (
<Form onSubmit={this.handleSubmit}>
<FormField
type="text"
label="Email"
placeholder="youremail@example.org"
name="email"
value={this.state.email}
error={errorMessage}
inputButton={
<Button button="inverse" type="submit" label="Invite" disabled={isPending || !this.state.email} />
}
onChange={event => {
this.handleEmailChanged(event);
}}
/>
</Form>
);
}
}
type Props = {
errorMessage: ?string,
inviteNew: string => void,
isPending: boolean,
rewardAmount: number,
referralLink: string,
};
class InviteNew extends React.PureComponent<Props> {
render() {
const { errorMessage, inviteNew, isPending, rewardAmount, referralLink } = this.props;
return ( return (
<Card <Card
@ -79,20 +47,31 @@ class InviteNew extends React.PureComponent<Props> {
subtitle={__('When your friends start using LBRY, the network gets stronger!')} subtitle={__('When your friends start using LBRY, the network gets stronger!')}
actions={ actions={
<React.Fragment> <React.Fragment>
<FormInviteNew <Form onSubmit={this.handleSubmit}>
errorMessage={errorMessage} <FormField
inviteNew={inviteNew} type="text"
isPending={isPending} label="Email"
rewardAmount={rewardAmount} placeholder="youremail@example.org"
/> name="email"
<CopyableText label={__('Or share this link with your friends')} copyable={referralLink} /> value={this.state.email}
error={errorMessage}
inputButton={
<Button button="secondary" type="submit" label="Invite" disabled={isPending || !this.state.email} />
}
onChange={event => {
this.handleEmailChanged(event);
}}
/>
<p className="help"> <CopyableText label={__('Or share this link with your friends')} copyable={referralLink} />
{__('Earn')} <Button button="link" navigate="/$/rewards" label={__('rewards')} />{' '}
{__('for inviting your friends.')} {__('Read our')}{' '} <p className="help">
<Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />{' '} {__('Earn')} <Button button="link" navigate="/$/rewards" label={__('rewards')} />{' '}
{__('to learn more about referrals')}. {__('for inviting your friends.')} {__('Read our')}{' '}
</p> <Button button="link" label={__('FAQ')} href="https://lbry.com/faq/referrals" />{' '}
{__('to learn more about referrals')}.
</p>
</Form>
</React.Fragment> </React.Fragment>
} }
/> />

View file

@ -106,7 +106,9 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
) : ( ) : (
<div className="main--empty"> <div className="main--empty">
<section className="card card--section"> <section className="card card--section">
<h2 className="card__title">{__('Your history is empty, what are you doing here?')}</h2> <h2 className="card__title card__title--deprecated">
{__('Your history is empty, what are you doing here?')}
</h2>
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Button button="primary" navigate="/" label={__('Explore new content')} /> <Button button="primary" navigate="/" label={__('Explore new content')} />

View file

@ -5,7 +5,7 @@ import classnames from 'classnames';
import Button from 'component/button'; import Button from 'component/button';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
type Props = { type Props = {
lastViewed: number, lastViewed: number,
@ -34,14 +34,12 @@ class NavigationHistoryItem extends React.PureComponent<Props> {
render() { render() {
const { lastViewed, selected, onSelect, claim, uri, slim, history } = this.props; const { lastViewed, selected, onSelect, claim, uri, slim, history } = this.props;
let name;
let title; let title;
if (claim && claim.value) { if (claim && claim.value) {
({ name } = claim);
({ title } = claim.value); ({ title } = claim.value);
} }
const navigatePath = formatLbryUriForWeb(uri); const navigatePath = formatLbryUrlForWeb(uri);
const onClick = const onClick =
onSelect || onSelect ||
function() { function() {
@ -58,7 +56,7 @@ class NavigationHistoryItem extends React.PureComponent<Props> {
> >
{!slim && <FormField checked={selected} type="checkbox" onChange={onSelect} />} {!slim && <FormField checked={selected} type="checkbox" onChange={onSelect} />}
<span className="time time--ago">{moment(lastViewed).from(moment())}</span> <span className="time time--ago">{moment(lastViewed).from(moment())}</span>
<Button className="item-list__element" constrict button="link" label={uri} navigate={uri} /> <Button className="item-list__element" button="link" label={uri} navigate={uri} />
<span className="item-list__element">{title}</span> <span className="item-list__element">{title}</span>
</div> </div>
); );

View file

@ -4,7 +4,7 @@ import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text'; import TruncatedText from 'component/common/truncated-text';
import MarkdownPreview from 'component/common/markdown-preview'; import MarkdownPreview from 'component/common/markdown-preview';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
type Props = { type Props = {
uri: string, uri: string,
@ -17,7 +17,7 @@ type Props = {
class PreviewLink extends React.PureComponent<Props> { class PreviewLink extends React.PureComponent<Props> {
handleClick = () => { handleClick = () => {
const { uri, history } = this.props; const { uri, history } = this.props;
history.push(formatLbryUriForWeb(uri)); history.push(formatLbryUrlForWeb(uri));
}; };
render() { render() {
@ -44,7 +44,7 @@ class PreviewLink extends React.PureComponent<Props> {
<span className={'claim-preview-properties'}> <span className={'claim-preview-properties'}>
<span className={'preview-link__description media__subtitle'}> <span className={'preview-link__description media__subtitle'}>
<TruncatedText lines={2} showTooltip={false}> <TruncatedText lines={2} showTooltip={false}>
<MarkdownPreview content={description} promptLinks strip /> <MarkdownPreview content={description} strip />
</TruncatedText> </TruncatedText>
</span> </span>
</span> </span>

View file

@ -74,6 +74,7 @@ function PublishFile(props: Props) {
} }
return ( return (
<Card <Card
actionIconPadding={false}
icon={ICONS.PUBLISH} icon={ICONS.PUBLISH}
disabled={disabled || balance === 0} disabled={disabled || balance === 0}
title={title} title={title}

View file

@ -138,12 +138,12 @@ function PublishForm(props: Props) {
<Card actions={<SelectThumbnail />} /> <Card actions={<SelectThumbnail />} />
<TagsSelect <TagsSelect
title={__('Add Tags')}
suggestMature suggestMature
disableAutoFocus disableAutoFocus
help={__('The better your tags are, the easier it will be for people to discover your content.')} hideHeader
label={__('Selected Tags')}
empty={__('No tags added')} empty={__('No tags added')}
placeholder={__('Add a tag')} placeholder={__('Add a tag...')}
onSelect={newTags => { onSelect={newTags => {
const validatedTags = []; const validatedTags = [];
newTags.forEach(newTag => { newTags.forEach(newTag => {

View file

@ -57,7 +57,7 @@ function PublishName(props: Props) {
}, [name]); }, [name]);
useEffect(() => { useEffect(() => {
const totalAvailableBidAmount = previousBidAmount + balance; const totalAvailableBidAmount = previousBidAmount ? previousBidAmount + balance : balance;
let bidError; let bidError;
if (bid === 0) { if (bid === 0) {

View file

@ -25,14 +25,16 @@ const RewardListClaimed = (props: Props) => {
return ( return (
<section className="card"> <section className="card">
<header className="table__header"> <header className="table__header">
<h2 className="card__title">{__('Claimed Rewards')}</h2> <div className="table__header-text">
<h2 className="card__title card__title--deprecated">{__('Claimed Rewards')}</h2>
<p className="card__subtitle"> <p className="section__subtitle">
{__( {__(
'Reward history is tied to your email. In case of lost or multiple wallets, your balance may differ from the amounts claimed' 'Reward history is tied to your email. In case of lost or multiple wallets, your balance may differ from the amounts claimed'
)} )}
. .
</p> </p>
</div>
</header> </header>
<table className="table table--rewards"> <table className="table table--rewards">

View file

@ -25,6 +25,7 @@ class RewardSummary extends React.Component<Props> {
tokens={{ tokens={{
credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />, credit_amount: <CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />,
}} }}
f
> >
You have %credit_amount% in unclaimed rewards. You have %credit_amount% in unclaimed rewards.
</I18nMessage> </I18nMessage>
@ -34,14 +35,14 @@ class RewardSummary extends React.Component<Props> {
</React.Fragment> </React.Fragment>
} }
actions={ actions={
<div className="section__actions"> <React.Fragment>
<Button <Button
button="primary" button="primary"
navigate="/$/rewards" navigate="/$/rewards"
label={hasRewards ? __('Claim Rewards') : __('View Rewards')} label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
/> />
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />. <Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />
</div> </React.Fragment>
} }
/> />
); );

View file

@ -1,6 +1,6 @@
// @flow // @flow
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React, { useEffect, lazy, Suspense } from 'react'; import React, { useEffect } from 'react';
import { Route, Redirect, Switch, withRouter } from 'react-router-dom'; import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
import SettingsPage from 'page/settings'; import SettingsPage from 'page/settings';
import HelpPage from 'page/help'; import HelpPage from 'page/help';
@ -17,8 +17,6 @@ import InvitePage from 'page/invite';
import SearchPage from 'page/search'; import SearchPage from 'page/search';
import LibraryPage from 'page/library'; import LibraryPage from 'page/library';
import WalletPage from 'page/wallet'; import WalletPage from 'page/wallet';
import WalletSendPage from 'page/walletSend';
import WalletReceivePage from 'page/walletReceive';
import TagsPage from 'page/tags'; import TagsPage from 'page/tags';
import FollowingPage from 'page/following'; import FollowingPage from 'page/following';
import ListBlockedPage from 'page/listBlocked'; import ListBlockedPage from 'page/listBlocked';
@ -71,39 +69,35 @@ function AppRouter(props: Props) {
}, [currentScroll, pathname]); }, [currentScroll, pathname]);
return ( return (
<Suspense fallback={<span>LODIFJDSLKJFSLDKJFLDJ</span>}> <Switch>
<Switch> <Route path="/" exact component={DiscoverPage} />
<Route path="/" exact component={DiscoverPage} /> <Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} /> <Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} /> <Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} /> <Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} /> <Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} /> <Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} /> <Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} /> <PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} /> <PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} /> <PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} /> <PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} /> <PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS}`} component={SettingsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} /> <PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} /> <PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} /> <PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} /> <PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} /> <PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET_SEND}`} exact component={WalletSendPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET_RECEIVE}`} exact component={WalletReceivePage} />
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */} {/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} /> <Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} /> <Route path="/:claimName/:streamName" exact component={ShowPage} />
<Route path="/*" component={FourOhFourPage} /> <Route path="/*" component={FourOhFourPage} />
</Switch> </Switch>
</Suspense>
); );
} }

View file

@ -27,7 +27,7 @@ const SearchOptions = (props: Props) => {
{expanded && ( {expanded && (
<Form className="search__options"> <Form className="search__options">
<fieldset> <fieldset>
<legend className="search__legend--1">{__('Search For')}</legend> <legend className="search__legend">{__('Search For')}</legend>
{[ {[
{ {
option: SEARCH_OPTIONS.INCLUDE_FILES, option: SEARCH_OPTIONS.INCLUDE_FILES,
@ -55,7 +55,7 @@ const SearchOptions = (props: Props) => {
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend className="search__legend--2">{__('File Types')}</legend> <legend className="search__legend">{__('File Types')}</legend>
{[ {[
{ {
option: SEARCH_OPTIONS.MEDIA_VIDEO, option: SEARCH_OPTIONS.MEDIA_VIDEO,
@ -92,7 +92,7 @@ const SearchOptions = (props: Props) => {
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend className="search__legend--3">{__('Other Options')}</legend> <legend className="search__legend">{__('Other Options')}</legend>
<FormField <FormField
type="select" type="select"
name="result-count" name="result-count"

View file

@ -6,7 +6,7 @@ import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button'; import Button from 'component/button';
import analytics from 'analytics'; import analytics from 'analytics';
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, INVALID_NAME_ERROR } from 'constants/claim'; import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
type Props = { type Props = {
channel: string, // currently selected channel channel: string, // currently selected channel
@ -102,6 +102,8 @@ class ChannelSection extends React.PureComponent<Props, State> {
newChannelBidError = __('Please decrease your deposit to account for transaction fees'); newChannelBidError = __('Please decrease your deposit to account for transaction fees');
} else if (newChannelBid > balance) { } else if (newChannelBid > balance) {
newChannelBidError = __('Deposit cannot be higher than your balance'); newChannelBidError = __('Deposit cannot be higher than your balance');
} else if (newChannelBid < MINIMUM_PUBLISH_BID) {
newChannelBidError = __('Your deposit must be higher');
} }
this.setState({ this.setState({

View file

@ -5,7 +5,6 @@ import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import Tag from 'component/tag'; import Tag from 'component/tag';
import StickyBox from 'react-sticky-box/dist/esnext'; import StickyBox from 'react-sticky-box/dist/esnext';
import 'css-doodle';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
type Props = { type Props = {
@ -32,7 +31,6 @@ function SideBar(props: Props) {
<div className="card navigation--placeholder"> <div className="card navigation--placeholder">
<div className="wrap"> <div className="wrap">
<h2>LBRY</h2> <h2>LBRY</h2>
<p>{__('The best decentralized content platform on the web.')}</p> <p>{__('The best decentralized content platform on the web.')}</p>
</div> </div>
</div> </div>

View file

@ -7,7 +7,6 @@ import EmbedArea from 'component/embedArea';
type Props = { type Props = {
claim: Claim, claim: Claim,
onDone: () => void,
webShareable: boolean, webShareable: boolean,
isChannel: boolean, isChannel: boolean,
}; };
@ -28,7 +27,7 @@ class SocialShare extends React.PureComponent<Props> {
render() { render() {
const { claim, isChannel } = this.props; const { claim, isChannel } = this.props;
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim; const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim;
const { webShareable, onDone } = this.props; const { webShareable } = this.props;
const OPEN_URL = 'https://open.lbry.com/'; const OPEN_URL = 'https://open.lbry.com/';
const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1]; const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
const lbryWebUrl = lbryUrl.replace(/#/g, ':'); const lbryWebUrl = lbryUrl.replace(/#/g, ':');
@ -41,13 +40,13 @@ class SocialShare extends React.PureComponent<Props> {
return ( return (
<React.Fragment> <React.Fragment>
<CopyableText label={__('LBRY App Link')} copyable={lbryURL} noSnackbar /> <CopyableText label={__('LBRY App Link')} copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center"> <div className="">
<Button <Button
icon={ICONS.FACEBOOK} icon={ICONS.FACEBOOK}
button="link" button="link"
description={shareOnFb} description={shareOnFb}
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`} href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`}
/> />{' '}
<Button <Button
icon={ICONS.TWITTER} icon={ICONS.TWITTER}
button="link" button="link"
@ -56,9 +55,6 @@ class SocialShare extends React.PureComponent<Props> {
/> />
</div> </div>
{webShareable && !isChannel && <EmbedArea label={__('Embedded')} claim={claim} noSnackbar />} {webShareable && !isChannel && <EmbedArea label={__('Embedded')} claim={claim} noSnackbar />}
<div className="card__actions">
<Button button="link" label={__('Done')} onClick={onDone} />
</div>
</React.Fragment> </React.Fragment>
); );
} }

View file

@ -8,6 +8,8 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon'; import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
import ModalUpgrade from 'modal/modalUpgrade'; import ModalUpgrade from 'modal/modalUpgrade';
import ModalDownloading from 'modal/modalDownloading'; import ModalDownloading from 'modal/modalDownloading';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import 'css-doodle'; import 'css-doodle';
const FORTY_FIVE_SECONDS = 45 * 1000; const FORTY_FIVE_SECONDS = 45 * 1000;
@ -227,7 +229,7 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
{!animationHidden && ( {!animationHidden && (
<css-doodle class="doodle"> <css-doodle class="doodle">
{` {`
--color: @p(var(--lbry-teal-1), var(--lbry-orange-1), var(--lbry-cyan-3), var(--lbry-pink-5)); --color: @p(var(--color-primary), var(--color-secondary), var(--color-focus), var(--color-nothing));
:doodle { :doodle {
@grid: 30x1 / 18vmin; @grid: 30x1 / 18vmin;
--deg: @p(-180deg, 180deg); --deg: @p(-180deg, 180deg);
@ -267,21 +269,28 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
onClick={() => setClientSetting(SETTINGS.HIDE_SPLASH_ANIMATION, !animationHidden)} onClick={() => setClientSetting(SETTINGS.HIDE_SPLASH_ANIMATION, !animationHidden)}
/> />
{error && ( {error && (
<div className="splash__error card card--section"> <Card
<p className="card__subtitle"> className="splash__error"
{__('Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.')} title={__('Error Starting Up')}
</p> subtitle={
<div className="card__actions--center"> <React.Fragment>
<Button button="primary" label={__('Refresh')} onClick={() => window.location.reload()} /> <p>
</div> Try refreshing to fix it. If you still have issues, your anti-virus software or firewall may be
<div className="help"> preventing startup.
<p>{__('If you still have issues, your anti-virus software or firewall may be preventing startup.')}</p> </p>
<p> <I18nMessage
{__('Reach out to hello@lbry.com for help, or check out')}{' '} tokens={{
<Button button="link" href="https://lbry.com/faq/startup-troubleshooting" label="this link" />. help_link: (
</p> <Button button="link" href="https://lbry.com/faq/startup-troubleshooting" label="this link" />
</div> ),
</div> }}
>
Reach out to hello@lbry.com for help, or check out %help_link%.
</I18nMessage>
</React.Fragment>
}
actions={<Button button="primary" label={__('Refresh')} onClick={() => window.location.reload()} />}
/>
)} )}
{/* Temp hack: don't show any modals on splash screen daemon is running; {/* Temp hack: don't show any modals on splash screen daemon is running;
daemon doesn't let you quit during startup, so the "Quit" buttons daemon doesn't let you quit during startup, so the "Quit" buttons

View file

@ -30,6 +30,7 @@ export default function Tag(props: Props) {
disabled={disabled} disabled={disabled}
title={title} title={title}
className={classnames('tag', { className={classnames('tag', {
'tag--large': type === 'large',
'tag--remove': type === 'remove', 'tag--remove': type === 'remove',
// tag--add only adjusts the color, which causes issues with mature tag color clashing // tag--add only adjusts the color, which causes issues with mature tag color clashing
'tag--add': !isMature && type === 'add', 'tag--add': !isMature && type === 'add',
@ -37,7 +38,7 @@ export default function Tag(props: Props) {
})} })}
label={name} label={name}
iconSize={12} iconSize={12}
iconRight={type !== 'link' && (type === 'remove' ? ICONS.REMOVE : ICONS.ADD)} iconRight={type !== 'link' && type !== 'large' && (type === 'remove' ? ICONS.REMOVE : ICONS.ADD)}
/> />
); );
} }

View file

@ -15,6 +15,7 @@ type Props = {
disableAutoFocus?: boolean, disableAutoFocus?: boolean,
onRemove: Tag => void, onRemove: Tag => void,
placeholder?: string, placeholder?: string,
label?: string,
}; };
/* /*
@ -36,6 +37,7 @@ export default function TagsSearch(props: Props) {
suggestMature, suggestMature,
disableAutoFocus, disableAutoFocus,
placeholder, placeholder,
label,
} = props; } = props;
const [newTag, setNewTag] = useState(''); const [newTag, setNewTag] = useState('');
const doesTagMatch = name => { const doesTagMatch = name => {
@ -107,6 +109,7 @@ export default function TagsSearch(props: Props) {
return ( return (
<React.Fragment> <React.Fragment>
<Form className="tags__input-wrapper" onSubmit={handleSubmit}> <Form className="tags__input-wrapper" onSubmit={handleSubmit}>
<label>{label || __('Following')}</label>
<ul className="tags--remove"> <ul className="tags--remove">
{tagsPassedIn.map(tag => ( {tagsPassedIn.map(tag => (
<Tag <Tag
@ -130,6 +133,7 @@ export default function TagsSearch(props: Props) {
</li> </li>
</ul> </ul>
</Form> </Form>
<label>{__('Suggested')}</label>
<ul className="tags"> <ul className="tags">
{suggestedTags.map(tag => ( {suggestedTags.map(tag => (
<Tag key={`suggested${tag}`} name={tag} type="add" onClick={() => handleTagClick(tag)} /> <Tag key={`suggested${tag}`} name={tag} type="add" onClick={() => handleTagClick(tag)} />

View file

@ -17,11 +17,13 @@ type Props = {
// The default component is for following tags // The default component is for following tags
title?: string | boolean, title?: string | boolean,
help?: string, help?: string,
label?: string,
tagsChosen?: Array<Tag>, tagsChosen?: Array<Tag>,
onSelect?: (Array<Tag>) => void, onSelect?: (Array<Tag>) => void,
onRemove?: Tag => void, onRemove?: Tag => void,
placeholder?: string, placeholder?: string,
disableAutoFocus?: boolean, disableAutoFocus?: boolean,
hideHeader?: boolean,
}; };
/* /*
@ -40,6 +42,8 @@ export default function TagsSelect(props: Props) {
suggestMature, suggestMature,
disableAutoFocus, disableAutoFocus,
placeholder, placeholder,
hideHeader,
label,
} = props; } = props;
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false); const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
const tagsToDisplay = tagsChosen || followedTags; const tagsToDisplay = tagsChosen || followedTags;
@ -71,14 +75,17 @@ export default function TagsSelect(props: Props) {
return ( return (
((showClose && !hasClosed) || !showClose) && ( ((showClose && !hasClosed) || !showClose) && (
<Card <Card
actionIconPadding={false}
icon={ICONS.TAG} icon={ICONS.TAG}
title={ title={
<React.Fragment> hideHeader ? null : (
{title} <React.Fragment>
{showClose && tagsToDisplay.length > 0 && !hasClosed && ( {title}
<Button button="close" icon={ICONS.REMOVE} onClick={handleClose} /> {showClose && tagsToDisplay.length > 0 && !hasClosed && (
)} <Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />
</React.Fragment> )}
</React.Fragment>
)
} }
subtitle={ subtitle={
help !== false && ( help !== false && (
@ -91,6 +98,7 @@ export default function TagsSelect(props: Props) {
actions={ actions={
<React.Fragment> <React.Fragment>
<TagsSearch <TagsSearch
label={label}
onRemove={handleTagClick} onRemove={handleTagClick}
onSelect={onSelect} onSelect={onSelect}
suggestMature={suggestMature && !hasMatureTag} suggestMature={suggestMature && !hasMatureTag}

View file

@ -42,8 +42,8 @@ function TransactionList(props: Props) {
return ( return (
<React.Fragment> <React.Fragment>
<header className="table__header"> <header className="table__header">
<h2 className="card__title--between"> <div className="table__header-text--between">
{title} <h2 className="card__title">{title}</h2>
<div className="card__actions--inline"> <div className="card__actions--inline">
<RefreshTransactionButton slim={slim} /> <RefreshTransactionButton slim={slim} />
{/* @if TARGET='app' */} {/* @if TARGET='app' */}
@ -82,7 +82,7 @@ function TransactionList(props: Props) {
)} )}
{slim && <Button button="primary" navigate={`/$/${PAGES.TRANSACTIONS}`} label={__('Full History')} />} {slim && <Button button="primary" navigate={`/$/${PAGES.TRANSACTIONS}`} label={__('Full History')} />}
</div> </div>
</h2> </div>
</header> </header>
{((loading && !transactions.length) || !transactions.length) && ( {((loading && !transactions.length) || !transactions.length) && (

View file

@ -26,9 +26,9 @@ class TransactionListItem extends React.PureComponent<Props> {
getLink(type: string) { getLink(type: string) {
if (type === TXN_TYPES.TIP) { if (type === TXN_TYPES.TIP) {
return <Button icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />; return <Button button="secondary" icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />;
} }
return <Button icon={ICONS.DELETE} onClick={this.abandonClaim} title={__('Abandon Claim')} />; return <Button button="secondary" icon={ICONS.DELETE} onClick={this.abandonClaim} title={__('Abandon Claim')} />;
} }
abandonClaim() { abandonClaim() {
@ -65,13 +65,15 @@ class TransactionListItem extends React.PureComponent<Props> {
return ( return (
<tr> <tr>
<td> <td>
<CreditAmount badge={false} showPlus amount={amount} precision={8} /> {date ? (
<br /> <div>
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
{fee !== 0 && ( <div className="table__item-label">
<span className="table__item-label"> <DateTime date={date} show={DateTime.SHOW_TIME} />
<CreditAmount badge={false} fee amount={fee} precision={8} /> </div>
</span> </div>
) : (
<span className="empty">{__('Pending')}</span>
)} )}
</td> </td>
<td className="table__item--actionable"> <td className="table__item--actionable">
@ -85,16 +87,14 @@ class TransactionListItem extends React.PureComponent<Props> {
<td> <td>
<ButtonTransaction id={txid} /> <ButtonTransaction id={txid} />
</td> </td>
<td> <td className="table__item--align-right">
{date ? ( <CreditAmount badge={false} showPlus amount={amount} precision={8} />
<div> <br />
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
<div className="table__item-label"> {fee !== 0 && (
<DateTime date={date} show={DateTime.SHOW_TIME} /> <span className="table__item-label">
</div> <CreditAmount badge={false} fee amount={fee} precision={8} />
</div> </span>
) : (
<span className="empty">{__('Pending')}</span>
)} )}
</td> </td>
</tr> </tr>

View file

@ -36,11 +36,11 @@ function TransactionListTable(props: Props) {
<table className="table table--transactions"> <table className="table table--transactions">
<thead> <thead>
<tr> <tr>
<th>{__('Amount')}</th> <th>{__('Date')}</th>
<th>{__('Type')} </th> <th>{__('Type')} </th>
<th>{__('Details')} </th> <th>{__('Details')} </th>
<th>{__('Transaction')}</th> <th>{__('Transaction')}</th>
<th>{__('Date')}</th> <th className="table__item--align-right">{__('Amount')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -45,7 +45,7 @@ class TransactionRefreshButton extends PureComponent<Props, State> {
const { fetchingTransactions } = this.props; const { fetchingTransactions } = this.props;
const { label, disabled } = this.state; const { label, disabled } = this.state;
return ( return (
<Button button="link" label={label} onClick={this.handleClick} disabled={disabled || fetchingTransactions} /> <Button button="secondary" label={label} onClick={this.handleClick} disabled={disabled || fetchingTransactions} />
); );
} }
} }

View file

@ -59,7 +59,7 @@ function UserEmail(props: Props) {
/> />
</React.Fragment> </React.Fragment>
} }
inputButton={<UserSignOutButton button="inverse" />} inputButton={<UserSignOutButton button="secondary" />}
value={email || ''} value={email || ''}
/> />
) : ( ) : (

View file

@ -72,7 +72,12 @@ function UserEmailNew(props: Props) {
<I18nMessage <I18nMessage
tokens={{ tokens={{
terms: ( terms: (
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('Terms of Service')} /> <Button
tabIndex="2"
button="link"
href="https://www.lbry.com/termsofservice"
label={__('Terms of Service')}
/>
), ),
}} }}
> >

View file

@ -83,7 +83,7 @@ class UserPhoneNew extends React.PureComponent<Props, State> {
return ( return (
<React.Fragment> <React.Fragment>
<p className="card__subtitle"> <p className="section__subtitle">
{__( {__(
'Enter your phone number and we will send you a verification code. We will not share your phone number with third parties.' 'Enter your phone number and we will send you a verification code. We will not share your phone number with third parties.'
)} )}

View file

@ -1,33 +1,37 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import Villain from 'villain'; import Villain from 'villain-react';
import 'villain/dist/style.css'; import 'villain-react/dist/style.css';
type Props = { type Props = {
theme: string,
source: { source: {
fileType: string, fileType: string,
downloadPath: string, downloadPath: string,
}, },
}; };
let workerPath = 'webworkers/worker-bundle.js'; let workerUrl = 'webworkers/worker-bundle.js';
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
// Don't add a leading slash in production because electron treats it as an absolute path // Don't add a leading slash in production because electron treats it as an absolute path
workerPath = `/${workerPath}`; workerUrl = `/${workerUrl}`;
} }
const opts = {
workerPath,
allowFullScreen: false,
autoHideControls: true,
};
class ComicBookViewer extends React.PureComponent<Props> { class ComicBookViewer extends React.PureComponent<Props> {
render() { render() {
const { downloadPath } = this.props.source || {}; const { downloadPath } = this.props.source || {};
// Archive source
const file = `file://${downloadPath}`; const file = `file://${downloadPath}`;
// Villain options
const opts = {
theme: this.props.theme === 'dark' ? 'Dark' : 'Light',
allowFullScreen: true,
autoHideControls: false,
allowGlobalShortcuts: true,
};
return <Villain file={file} width={'100%'} height={'100%'} options={opts} />; return <Villain file={file} style={{ width: '100%', height: '100%' }} options={opts} workerUrl={workerUrl} />;
} }
} }

View file

@ -1,14 +1,16 @@
// @flow // @flow
import React, { Suspense } from 'react'; import React from 'react';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';
import MarkdownPreview from 'component/common/markdown-preview'; import MarkdownPreview from 'component/common/markdown-preview';
import CodeViewer from 'component/viewers/codeViewer'; import CodeViewer from 'component/viewers/codeViewer';
import * as https from 'https';
type Props = { type Props = {
theme: string, theme: string,
source: { source: {
stream: string => any, file: (?string) => any,
stream: string,
fileType: string, fileType: string,
contentType: string, contentType: string,
}, },
@ -32,9 +34,9 @@ class DocumentViewer extends React.PureComponent<Props, State> {
componentDidMount() { componentDidMount() {
const { source } = this.props; const { source } = this.props;
// @if TARGET='app'
if (source && source.stream) { if (source && source.file) {
const stream = source.stream('utf8'); const stream = source.file('utf8');
let data = ''; let data = '';
@ -50,6 +52,30 @@ class DocumentViewer extends React.PureComponent<Props, State> {
this.setState({ error: true, loading: false }); this.setState({ error: true, loading: false });
}); });
} }
// @endif
// @if TARGET='web'
if (source && source.stream) {
https.get(
source.stream,
function(response) {
if (response.statusCode === 200) {
let data = '';
response.on('data', function(chunk) {
data += chunk;
});
response.on(
'end',
function() {
this.setState({ content: data, loading: false });
}.bind(this)
);
} else {
this.setState({ error: true, loading: false });
}
}.bind(this)
);
}
// @endif
} }
renderDocument() { renderDocument() {
@ -58,10 +84,9 @@ class DocumentViewer extends React.PureComponent<Props, State> {
const { source, theme } = this.props; const { source, theme } = this.props;
const { fileType, contentType } = source; const { fileType, contentType } = source;
const markdownType = ['md', 'markdown']; const markdownType = ['md', 'markdown'];
if (markdownType.includes(fileType) || contentType === 'text/markdown' || contentType === 'text/md') {
if (markdownType.includes(fileType)) {
// Render markdown // Render markdown
viewer = <MarkdownPreview content={content} promptLinks />; viewer = <MarkdownPreview content={content} />;
} else { } else {
// Render plain text // Render plain text
viewer = <CodeViewer value={content} contentType={contentType} theme={theme} />; viewer = <CodeViewer value={content} contentType={contentType} theme={theme} />;

View file

@ -11,7 +11,12 @@ class HtmlViewer extends React.PureComponent<Props> {
const { source } = this.props; const { source } = this.props;
return ( return (
<div className="file-render__viewer" onContextMenu={stopContextMenu}> <div className="file-render__viewer" onContextMenu={stopContextMenu}>
{/* @if TARGET='app' */}
<iframe sandbox="" title={__('File preview')} src={`file://${source}`} /> <iframe sandbox="" title={__('File preview')} src={`file://${source}`} />
{/* @endif */}
{/* @if TARGET='web' */}
<iframe sandbox="" title={__('File preview')} src={source} />
{/* @endif */}
</div> </div>
); );
} }

View file

@ -27,21 +27,27 @@ class PdfViewer extends React.PureComponent<Props> {
// @if TARGET='app' // @if TARGET='app'
shell.openExternal(path); shell.openExternal(path);
// @endif // @endif
// @if TARGET='web'
console.error('provide stub for shell.openExternal'); // eslint-disable-line
// @endif
} }
render() { render() {
// We used to be able to just render a webview and display the pdf inside the app // We used to be able to just render a webview and display the pdf inside the app
// This was disabled on electron@3 // This was disabled on electron@3
// https://github.com/electron/electron/issues/12337 // https://github.com/electron/electron/issues/12337
const { source } = this.props;
return ( return (
<div className="file-render__viewer--pdf" onContextMenu={stopContextMenu}> <div className="file-render__viewer--pdf" onContextMenu={stopContextMenu}>
{/* @if TARGET='app' */}
<p> <p>
{__('PDF opened externally.')} <Button button="link" label={__('Click here')} onClick={this.openFile} />{' '} {__('PDF opened externally.')} <Button button="link" label={__('Click here')} onClick={this.openFile} />{' '}
{__('to open it again.')} {__('to open it again.')}
</p> </p>
{/* @endif */}
{/* @if TARGET='web' */}
<div className="file-render__viewer">
<iframe title={__('File preview')} src={source} />
</div>
{/* @endif */}
</div> </div>
); );
} }

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectFileInfoForUri } from 'lbry-redux'; import { makeSelectFileInfoForUri, makeSelectThumbnailForUri } from 'lbry-redux';
import { doChangeVolume, doChangeMute } from 'redux/actions/app'; import { doChangeVolume, doChangeMute } from 'redux/actions/app';
import { selectVolume, selectMute } from 'redux/selectors/app'; import { selectVolume, selectMute } from 'redux/selectors/app';
import { savePosition, doSetPlayingUri } from 'redux/actions/content'; import { savePosition, doSetPlayingUri } from 'redux/actions/content';
@ -11,6 +11,7 @@ const select = (state, props) => ({
position: makeSelectContentPositionForUri(props.uri)(state), position: makeSelectContentPositionForUri(props.uri)(state),
muted: selectMute(state), muted: selectMute(state),
hasFileInfo: Boolean(makeSelectFileInfoForUri(props.uri)(state)), hasFileInfo: Boolean(makeSelectFileInfoForUri(props.uri)(state)),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -20,11 +20,11 @@ const SEEK_BACKWARD_KEYCODE = ARROW_LEFT_KEYCODE;
const SEEK_STEP = 10; // time to seek in seconds const SEEK_STEP = 10; // time to seek in seconds
const VIDEO_JS_OPTIONS = { const VIDEO_JS_OPTIONS: { poster?: string } = {
autoplay: true,
controls: true, controls: true,
preload: 'auto', preload: 'auto',
playbackRates: [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 2], playbackRates: [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 2],
responsive: true,
}; };
type Props = { type Props = {
@ -38,13 +38,25 @@ type Props = {
setPlayingUri: (string | null) => void, setPlayingUri: (string | null) => void,
source: string, source: string,
contentType: string, contentType: string,
thumbnail: string,
hasFileInfo: boolean, hasFileInfo: boolean,
onEndedCB: any, onEndedCB: any,
}; };
function VideoViewer(props: Props) { function VideoViewer(props: Props) {
const { contentType, source, setPlayingUri, onEndedCB, changeVolume, changeMute, volume, muted } = props; const { contentType, source, setPlayingUri, onEndedCB, changeVolume, changeMute, volume, muted, thumbnail } = props;
const videoRef = useRef(); const videoRef = useRef();
const isAudio = contentType.includes('audio');
let forceTypes = [
'video/quicktime',
'application/x-ext-mkv',
'video/x-matroska',
'application/octet-stream',
'video/x-ms-wmv',
'video/x-msvideo',
'video/mpeg',
];
const forceMp4 = forceTypes.includes(contentType);
const [requireRedraw, setRequireRedraw] = useState(false); const [requireRedraw, setRequireRedraw] = useState(false);
let player; let player;
@ -88,16 +100,25 @@ function VideoViewer(props: Props) {
sources: [ sources: [
{ {
src: source, src: source,
type: contentType, type: forceMp4 ? 'video/mp4' : contentType,
}, },
], ],
}; };
if (isAudio) {
videoJsOptions.poster = thumbnail;
}
if (!requireRedraw) { if (!requireRedraw) {
player = videojs(videoNode, videoJsOptions, function() { player = videojs(videoNode, videoJsOptions, function() {
const self = this; player.volume(volume);
self.volume(volume); player.muted(muted);
self.muted(muted); player.ready(() => {
// In the future this should be replaced with something that checks if the
// video is actually playing after calling play()
// If it's not, fall back to the play button
player.play();
});
}); });
} }
@ -172,7 +193,7 @@ function VideoViewer(props: Props) {
<div className="file-render__viewer" onContextMenu={stopContextMenu}> <div className="file-render__viewer" onContextMenu={stopContextMenu}>
{!requireRedraw && ( {!requireRedraw && (
<div data-vjs-player> <div data-vjs-player>
<video ref={videoRef} className="video-js" /> {isAudio ? <audio ref={videoRef} className="video-js" /> : <video ref={videoRef} className="video-js" />}
</div> </div>
)} )}
</div> </div>

View file

@ -1,6 +1,5 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import * as ICONS from 'constants/icons';
import Button from 'component/button'; import Button from 'component/button';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
import QRCode from 'component/common/qr-code'; import QRCode from 'component/common/qr-code';
@ -11,7 +10,6 @@ type Props = {
receiveAddress: string, receiveAddress: string,
getNewAddress: () => void, getNewAddress: () => void,
gettingNewAddress: boolean, gettingNewAddress: boolean,
history: { goBack: () => void },
}; };
type State = { type State = {
@ -45,26 +43,26 @@ class WalletAddress extends React.PureComponent<Props, State> {
} }
render() { render() {
const { receiveAddress, getNewAddress, gettingNewAddress, history } = this.props; const { receiveAddress, getNewAddress, gettingNewAddress } = this.props;
const { showQR } = this.state; const { showQR } = this.state;
return ( return (
<Card <Card
title={ title={__('Receive Credits')}
<React.Fragment>
{__('Receive Credits')}
<Button button="close" icon={ICONS.REMOVE} onClick={() => history.goBack()} />
</React.Fragment>
}
subtitle={__('Use this address to receive LBC.')} subtitle={__('Use this address to receive LBC.')}
actions={ actions={
<React.Fragment> <React.Fragment>
<CopyableText label={__('Your Address')} copyable={receiveAddress} snackMessage={__('Address copied.')} /> <CopyableText
primaryButton
label={__('Your Address')}
copyable={receiveAddress}
snackMessage={__('Address copied.')}
/>
<div className="card__actions"> <div className="card__actions">
{!IS_WEB && ( {!IS_WEB && (
<Button <Button
button="inverse" button="secondary"
label={__('Get New Address')} label={__('Get New Address')}
onClick={getNewAddress} onClick={getNewAddress}
disabled={gettingNewAddress} disabled={gettingNewAddress}

View file

@ -6,6 +6,7 @@ import {
selectSupportsBalance, selectSupportsBalance,
selectTipsBalance, selectTipsBalance,
} from 'lbry-redux'; } from 'lbry-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectClaimedRewards } from 'lbryinc'; import { selectClaimedRewards } from 'lbryinc';
import WalletBalance from './view'; import WalletBalance from './view';
@ -18,4 +19,9 @@ const select = state => ({
rewards: selectClaimedRewards(state), rewards: selectClaimedRewards(state),
}); });
export default connect(select)(WalletBalance); export default connect(
select,
{
doOpenModal,
}
)(WalletBalance);

View file

@ -1,6 +1,6 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as MODALS from 'constants/modal_types';
import React from 'react'; import React from 'react';
import CreditAmount from 'component/common/credit-amount'; import CreditAmount from 'component/common/credit-amount';
import Button from 'component/button'; import Button from 'component/button';
@ -12,10 +12,11 @@ type Props = {
claimsBalance: number, claimsBalance: number,
supportsBalance: number, supportsBalance: number,
tipsBalance: number, tipsBalance: number,
doOpenModal: string => void,
}; };
const WalletBalance = (props: Props) => { const WalletBalance = (props: Props) => {
const { balance, claimsBalance, supportsBalance, tipsBalance } = props; const { balance, claimsBalance, supportsBalance, tipsBalance, doOpenModal } = props;
return ( return (
<React.Fragment> <React.Fragment>
@ -27,8 +28,13 @@ const WalletBalance = (props: Props) => {
</span> </span>
<div className="section__actions"> <div className="section__actions">
<Button button="inverse" icon={ICONS.SEND} label={__('Send Credits')} navigate={`$/${PAGES.WALLET_SEND}`} /> <Button button="primary" label={__('Your Address')} onClick={() => doOpenModal(MODALS.WALLET_RECEIVE)} />
<Button button="inverse" label={__('Your Address')} navigate={`$/${PAGES.WALLET_RECEIVE}`} /> <Button
button="secondary"
icon={ICONS.SEND}
label={__('Send Credits')}
onClick={() => doOpenModal(MODALS.WALLET_SEND)}
/>
</div> </div>
</div> </div>

View file

@ -1,6 +1,5 @@
// @flow // @flow
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { Form, FormField } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
@ -16,7 +15,6 @@ type DraftTransaction = {
type Props = { type Props = {
openModal: (id: string, { address: string, amount: number }) => void, openModal: (id: string, { address: string, amount: number }) => void,
balance: number, balance: number,
history: { goBack: () => void },
}; };
class WalletSend extends React.PureComponent<Props> { class WalletSend extends React.PureComponent<Props> {
@ -36,16 +34,11 @@ class WalletSend extends React.PureComponent<Props> {
} }
render() { render() {
const { balance, history } = this.props; const { balance } = this.props;
return ( return (
<Card <Card
title={ title={__('Send Credits')}
<React.Fragment>
{__('Send Credits')}
<Button button="close" icon={ICONS.REMOVE} onClick={() => history.goBack()} />
</React.Fragment>
}
subtitle={__('Send LBC to your friends or favorite creators.')} subtitle={__('Send LBC to your friends or favorite creators.')}
actions={ actions={
<Formik <Formik

View file

@ -12,7 +12,7 @@ export default function WebUploadItem(props: Props) {
const { params, progress, xhr } = props; const { params, progress, xhr } = props;
return ( return (
<li className={'claim-preview card--inline'}> <li className={'claim-preview claim-preview--inactive card--inline'}>
<CardMedia thumbnail={params.thumbnail_url} /> <CardMedia thumbnail={params.thumbnail_url} />
<div className={'claim-preview-metadata'}> <div className={'claim-preview-metadata'}>
<div className="claim-preview-info"> <div className="claim-preview-info">

View file

@ -11,7 +11,7 @@ export type UploadItem = {
type Props = { type Props = {
currentUploads: { [key: string]: UploadItem }, currentUploads: { [key: string]: UploadItem },
uploadCount: ?number, uploadCount: number,
}; };
export default function WebUploadList(props: Props) { export default function WebUploadList(props: Props) {

View file

@ -11,7 +11,7 @@ import {
import analytics from 'analytics'; import analytics from 'analytics';
import Wunderbar from './view'; import Wunderbar from './view';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
const select = state => ({ const select = state => ({
suggestions: selectSearchSuggestions(state), suggestions: selectSearchSuggestions(state),
@ -26,7 +26,7 @@ const perform = (dispatch, ownProps) => ({
analytics.apiLogSearch(); analytics.apiLogSearch();
}, },
onSubmit: uri => { onSubmit: uri => {
const path = formatLbryUriForWeb(uri); const path = formatLbryUrlForWeb(uri);
ownProps.history.push(path); ownProps.history.push(path);
dispatch(doUpdateSearchQuery('')); dispatch(doUpdateSearchQuery(''));
}, },

View file

@ -370,11 +370,10 @@ export default class Autocomplete extends React.Component {
const matchedItem = this.getFilteredItems(props)[index]; const matchedItem = this.getFilteredItems(props)[index];
if (value !== '' && matchedItem) { if (value !== '' && matchedItem) {
const itemValue = getItemValue(matchedItem); const itemValue = getItemValue(matchedItem);
const itemValueDoesMatch = const itemValueDoesMatch = itemValue.toLowerCase().includes(
itemValue.toLowerCase().includes( value.toLowerCase()
value.toLowerCase() // below line is the the only thing that is changed from the real component
// below line is the the only thing that is changed from the real component );
);
if (itemValueDoesMatch) { if (itemValueDoesMatch) {
return { highlightedIndex: index }; return { highlightedIndex: index };
} }

View file

@ -31,7 +31,7 @@ export default class extends React.PureComponent<Props> {
<img alt="Friendly gerbil" className={classnames('yrbl', className)} src={`${image}`} /> <img alt="Friendly gerbil" className={classnames('yrbl', className)} src={`${image}`} />
{title && subtitle && ( {title && subtitle && (
<div className="yrbl__content"> <div className="yrbl__content">
<h2 className="card__title">{title}</h2> <h2 className="card__title card__title--deprecated">{title}</h2>
<p>{subtitle}</p> <p>{subtitle}</p>
</div> </div>
)} )}

View file

@ -29,3 +29,5 @@ export const WALLET_DECRYPT = 'wallet_decrypt';
export const WALLET_UNLOCK = 'wallet_unlock'; export const WALLET_UNLOCK = 'wallet_unlock';
export const WALLET_SYNC = 'wallet_sync'; export const WALLET_SYNC = 'wallet_sync';
export const WALLET_PASSWORD_UNSAVE = 'wallet_password_unsave'; export const WALLET_PASSWORD_UNSAVE = 'wallet_password_unsave';
export const WALLET_SEND = 'wallet_send';
export const WALLET_RECEIVE = 'wallet_receive';

View file

@ -1,27 +1,25 @@
export const AUTH = 'signin'; exports.AUTH = 'signin';
export const AUTH_VERIFY = 'verify'; exports.AUTH_VERIFY = 'verify';
export const BACKUP = 'backup'; exports.BACKUP = 'backup';
export const CHANNEL = 'channel'; exports.CHANNEL = 'channel';
export const DISCOVER = 'discover'; exports.DISCOVER = 'discover';
export const DOWNLOADED = 'downloaded'; exports.DOWNLOADED = 'downloaded';
export const HELP = 'help'; exports.HELP = 'help';
export const LIBRARY = 'library'; exports.LIBRARY = 'library';
export const INVITE = 'invite'; exports.INVITE = 'invite';
export const PUBLISH = 'publish'; exports.PUBLISH = 'publish';
export const PUBLISHED = 'published'; exports.PUBLISHED = 'published';
export const GET_CREDITS = 'getcredits'; exports.GET_CREDITS = 'getcredits';
export const REPORT = 'report'; exports.REPORT = 'report';
export const REWARDS = 'rewards'; exports.REWARDS = 'rewards';
export const SEND = 'send'; exports.SEND = 'send';
export const SETTINGS = 'settings'; exports.SETTINGS = 'settings';
export const SHOW = 'show'; exports.SHOW = 'show';
export const ACCOUNT = 'account'; exports.ACCOUNT = 'account';
export const FOLLOWING = 'following'; exports.FOLLOWING = 'following';
export const SEARCH = 'search'; exports.SEARCH = 'search';
export const TRANSACTIONS = 'transactions'; exports.TRANSACTIONS = 'transactions';
export const TAGS = 'tags'; exports.TAGS = 'tags';
export const WALLET = 'wallet'; exports.WALLET = 'wallet';
export const WALLET_SEND = 'wallet/send'; exports.BLOCKED = 'blocked';
export const WALLET_RECEIVE = 'wallet/receive'; exports.CHANNELS = 'channels';
export const BLOCKED = 'blocked';
export const CHANNELS = 'channels';

View file

@ -1,3 +1,5 @@
import 'babel-polyfill';
import ErrorBoundary from 'component/errorBoundary'; import ErrorBoundary from 'component/errorBoundary';
import App from 'component/app'; import App from 'component/app';
import SnackBar from 'component/snackBar'; import SnackBar from 'component/snackBar';
@ -11,7 +13,7 @@ import * as MODALS from 'constants/modal_types';
import React, { Fragment, useState, useEffect } from 'react'; import React, { Fragment, useState, useEffect } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app'; import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal } from 'redux/actions/app';
import { Lbry, doToast, isURIValid, setSearchApi, apiCall } from 'lbry-redux'; import { Lbry, doToast, isURIValid, setSearchApi, apiCall } from 'lbry-redux';
import { doSetLanguage, doUpdateIsNightAsync } from 'redux/actions/settings'; import { doSetLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
import { import {
@ -26,7 +28,7 @@ import pjson from 'package.json';
import app from './app'; import app from './app';
import doLogWarningConsoleMessage from './logWarningConsoleMessage'; import doLogWarningConsoleMessage from './logWarningConsoleMessage';
import { ConnectedRouter, push } from 'connected-react-router'; import { ConnectedRouter, push } from 'connected-react-router';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb, formatInAppUrl } from 'util/url';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
import analytics from 'analytics'; import analytics from 'analytics';
import { getAuthToken, setAuthToken } from 'util/saved-passwords'; import { getAuthToken, setAuthToken } from 'util/saved-passwords';
@ -68,7 +70,6 @@ Lbry.setOverride(
const startTime = Date.now(); const startTime = Date.now();
analytics.startupEvent(); analytics.startupEvent();
const APPPAGEURL = 'lbry://?';
// @if TARGET='app' // @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater'); const { autoUpdater } = remote.require('electron-updater');
autoUpdater.logger = remote.require('electron-log'); autoUpdater.logger = remote.require('electron-log');
@ -155,25 +156,29 @@ rewards.setCallback('claimRewardSuccess', () => {
}); });
// @if TARGET='app' // @if TARGET='app'
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => { ipcRenderer.on('open-uri-requested', (event, url, newSession) => {
if (uri && uri.startsWith('lbry://')) { function handleError() {
if (uri.startsWith('lbry://?verify')) { app.store.dispatch(
app.store.dispatch(doConditionalAuthNavigate(newSession)); doToast({
} else if (uri.startsWith(APPPAGEURL)) { message: __('Invalid LBRY URL requested'),
const navpage = uri.replace(APPPAGEURL, '').toLowerCase(); })
app.store.dispatch(push(`/$/${navpage}`)); );
} else if (isURIValid(uri)) {
const formattedUri = formatLbryUriForWeb(uri);
app.store.dispatch(push(formattedUri));
analytics.openUrlEvent(formattedUri);
} else {
app.store.dispatch(
doToast({
message: __('Invalid LBRY URL requested'),
})
);
}
} }
const path = url.slice('lbry://'.length);
if (path.startsWith('?')) {
const redirectUrl = formatInAppUrl(path);
return app.store.dispatch(push(redirectUrl));
}
if (isURIValid(url)) {
const formattedUrl = formatLbryUrlForWeb(url);
analytics.openUrlEvent(formattedUrl);
return app.store.dispatch(push(formattedUrl));
}
// If nothing redirected before here the url must be messed up
handleError();
}); });
ipcRenderer.on('language-set', (event, language) => { ipcRenderer.on('language-set', (event, language) => {

View file

@ -1,5 +1,6 @@
// @flow // @flow
// These should probably just be combined into one modal component // These should probably just be combined into one modal component
import * as ICONS from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import Button from 'component/button'; import Button from 'component/button';
@ -50,12 +51,15 @@ export class Modal extends React.PureComponent<ModalProps> {
<ReactModal <ReactModal
{...modalProps} {...modalProps}
onRequestClose={onAborted || onConfirmed} onRequestClose={onAborted || onConfirmed}
className={classnames('card modal', className)} className={classnames('modal', className, {
'modal--card-internal': type === 'card',
})}
overlayClassName="modal-overlay" overlayClassName="modal-overlay"
> >
{title && <h1 className="card__title">{title}</h1>} {title && <h1 className="card__title card__title--deprecated">{title}</h1>}
{type === 'card' && <Button button="close" icon={ICONS.REMOVE} onClick={onAborted} />}
{children} {children}
{type === 'custom' ? null : ( // custom modals define their own buttons {type === 'custom' || type === 'card' ? null : ( // custom modals define their own buttons
<div className="card__actions"> <div className="card__actions">
<Button <Button
button="primary" button="primary"

View file

@ -39,7 +39,7 @@ class ModalAffirmPurchase extends React.PureComponent<Props> {
onConfirmed={this.onAffirmPurchase} onConfirmed={this.onAffirmPurchase}
onAborted={cancelPurchase} onAborted={cancelPurchase}
> >
<p className="card__subtitle"> <p className="section__subtitle">
{__('This will purchase')} <strong>{title ? `"${title}"` : uri}</strong> {__('for')}{' '} {__('This will purchase')} <strong>{title ? `"${title}"` : uri}</strong> {__('for')}{' '}
<strong> <strong>
<FilePrice uri={uri} showFullPrice inheritStyle showLBC={false} /> <FilePrice uri={uri} showFullPrice inheritStyle showLBC={false} />

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import { formatPathForWeb } from 'util/uri'; import { formatFileSystemPath } from 'util/url';
type Props = { type Props = {
upload: WebFile => void, upload: WebFile => void,
@ -15,15 +15,18 @@ function ModalAutoGenerateThumbnail(props: Props) {
const playerRef = useRef(); const playerRef = useRef();
let videoSrc; let videoSrc;
if (typeof filePath === 'string') { if (typeof filePath === 'string') {
videoSrc = formatPathForWeb(filePath); videoSrc = formatFileSystemPath(filePath);
} else { } else {
videoSrc = URL.createObjectURL(filePath); videoSrc = URL.createObjectURL(filePath);
} }
function uploadImage() { function uploadImage() {
const imageBuffer = captureSnapshot(); const imageBuffer = captureSnapshot();
// $FlowFixMe
const file = new File([imageBuffer], 'thumbnail.png', { type: 'image/png' }); const file = new File([imageBuffer], 'thumbnail.png', { type: 'image/png' });
if (imageBuffer) { if (imageBuffer) {
// $FlowFixMe
upload(file); upload(file);
closeModal(); closeModal();
} else { } else {
@ -75,7 +78,7 @@ function ModalAutoGenerateThumbnail(props: Props) {
onConfirmed={uploadImage} onConfirmed={uploadImage}
onAborted={closeModal} onAborted={closeModal}
> >
<p className="card__subtitle">{__('Pause at any time to select a thumbnail from your video')}.</p> <p className="section__subtitle">{__('Pause at any time to select a thumbnail from your video')}.</p>
<video ref={playerRef} src={videoSrc} onLoadedMetadata={resize} onError={onError} controls /> <video ref={playerRef} src={videoSrc} onLoadedMetadata={resize} onError={onError} controls />
</Modal> </Modal>
); );

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import { formatPathForWeb } from 'util/uri'; import { formatFileSystemPath } from 'util/url';
// @if TARGET='app' // @if TARGET='app'
import { shell } from 'electron'; import { shell } from 'electron';
// @endif // @endif
@ -37,7 +37,7 @@ function ModalOpenExternalResource(props: Props) {
if (uri) { if (uri) {
window.open(uri); window.open(uri);
} else if (path) { } else if (path) {
window.open(formatPathForWeb(path)); window.open(formatFileSystemPath(path));
} }
// @endif // @endif

View file

@ -36,7 +36,7 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
closeModal(); closeModal();
}} }}
> >
<p className="card__subtitle"> <p className="section__subtitle">
{__(`Your %publishMessage% pending on LBRY. It will take a few minutes to appear for other users.`, { {__(`Your %publishMessage% pending on LBRY. It will take a few minutes to appear for other users.`, {
publishMessage, publishMessage,
})} })}

View file

@ -27,6 +27,8 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalRewardCode from 'modal/modalRewardCode'; import ModalRewardCode from 'modal/modalRewardCode';
import ModalPasswordUnsave from 'modal/modalPasswordUnsave'; import ModalPasswordUnsave from 'modal/modalPasswordUnsave';
import ModalCommentAcknowledgement from 'modal/modalCommentAcknowledgement'; import ModalCommentAcknowledgement from 'modal/modalCommentAcknowledgement';
import ModalWalletSend from 'modal/modalWalletSend';
import ModalWalletReceive from 'modal/modalWalletReceive';
type Props = { type Props = {
modal: { id: string, modalProps: {} }, modal: { id: string, modalProps: {} },
@ -99,6 +101,10 @@ function ModalRouter(props: Props) {
return <ModalRewardCode {...modalProps} />; return <ModalRewardCode {...modalProps} />;
case MODALS.COMMENT_ACKNOWEDGEMENT: case MODALS.COMMENT_ACKNOWEDGEMENT:
return <ModalCommentAcknowledgement {...modalProps} />; return <ModalCommentAcknowledgement {...modalProps} />;
case MODALS.WALLET_SEND:
return <ModalWalletSend {...modalProps} />;
case MODALS.WALLET_RECEIVE:
return <ModalWalletReceive {...modalProps} />;
default: default:
return null; return null;
} }

View file

@ -14,8 +14,8 @@ class ModalSocialShare extends React.PureComponent<Props> {
render() { render() {
const { closeModal, uri, webShareable, isChannel } = this.props; const { closeModal, uri, webShareable, isChannel } = this.props;
return ( return (
<Modal isOpen onAborted={closeModal} type="custom" title={__('Share')}> <Modal isOpen onAborted={closeModal} onConfirmed={closeModal} title={__('Share')}>
<SocialShare uri={uri} onDone={closeModal} webShareable={webShareable} isChannel={isChannel} /> <SocialShare uri={uri} webShareable={webShareable} isChannel={isChannel} />
</Modal> </Modal>
); );
} }

View file

@ -150,7 +150,7 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
/> />
</fieldset-section> </fieldset-section>
<div className="card__subtitle--status"> <div className="section__subtitle--status">
{__( {__(
'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.' 'If your password is lost, it cannot be recovered. You will not be able to access your wallet without a password.'
)} )}

View file

@ -1,9 +1,12 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import WalletReceive from './view'; import WalletReceive from './view';
const select = state => ({}); const select = state => ({});
const perform = dispatch => ({}); const perform = {
doHideModal,
};
export default connect( export default connect(
select, select,

View file

@ -0,0 +1,16 @@
// @flow
import React from 'react';
import WalletAddress from 'component/walletAddress';
import { Modal } from 'modal/modal';
type Props = { doHideModal: () => void };
const WalletAddressPage = (props: Props) => {
const { doHideModal } = props;
return (
<Modal isOpen type="card" onAborted={doHideModal}>
<WalletAddress />
</Modal>
);
};
export default WalletAddressPage;

View file

@ -1,9 +1,12 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import WalletSend from './view'; import WalletSend from './view';
const select = state => ({}); const select = state => ({});
const perform = dispatch => ({}); const perform = {
doHideModal,
};
export default connect( export default connect(
select, select,

View file

@ -0,0 +1,16 @@
// @flow
import React from 'react';
import WalletSend from 'component/walletSend';
import { Modal } from 'modal/modal';
type Props = { doHideModal: () => void };
const WalletSendModal = (props: Props) => {
const { doHideModal } = props;
return (
<Modal isOpen type="card" onAborted={doHideModal}>
<WalletSend />
</Modal>
);
};
export default WalletSendModal;

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import WalletAddress from 'component/walletAddress'; import WalletAddress from './node_modules/component/walletAddress';
import Page from 'component/page'; import Page from './node_modules/component/page';
const WalletAddressPage = () => ( const WalletAddressPage = () => (
<Page className="main--contained"> <Page className="main--contained">

View file

@ -0,0 +1,10 @@
import React from 'react';
import WalletSend from './node_modules/component/walletSend';
const WalletSendModal = () => (
<div>
<WalletSend />
</div>
);
export default WalletSendModal;

View file

@ -8,7 +8,7 @@ import ShareButton from 'component/shareButton';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import Button from 'component/button'; import Button from 'component/button';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUrlForWeb } from 'util/url';
import ChannelContent from 'component/channelContent'; import ChannelContent from 'component/channelContent';
import ChannelAbout from 'component/channelAbout'; import ChannelAbout from 'component/channelAbout';
import ChannelDiscussion from 'component/channelDiscussion'; import ChannelDiscussion from 'component/channelDiscussion';
@ -93,7 +93,7 @@ function ChannelPage(props: Props) {
const tabIndex = currentView === ABOUT_PAGE || editing ? 1 : currentView === DISCUSSION_PAGE ? 2 : 0; const tabIndex = currentView === ABOUT_PAGE || editing ? 1 : currentView === DISCUSSION_PAGE ? 2 : 0;
function onTabChange(newTabIndex) { function onTabChange(newTabIndex) {
let url = formatLbryUriForWeb(uri); let url = formatLbryUrlForWeb(uri);
let search = '?'; let search = '?';
if (newTabIndex === 0) { if (newTabIndex === 0) {
@ -196,7 +196,7 @@ function ChannelPage(props: Props) {
)} )}
{editing && <img className="channel-cover__custom" src={coverPreview} />} {editing && <img className="channel-cover__custom" src={coverPreview} />}
{/* component that offers select/upload */} {/* component that offers select/upload */}
<div className="channel__primary-info "> <div className="channel__primary-info">
{!editing && ( {!editing && (
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} obscure={channelIsBlocked} /> <ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} obscure={channelIsBlocked} />
)} )}
@ -207,14 +207,12 @@ function ChannelPage(props: Props) {
thumbnailPreview={thumbPreview} thumbnailPreview={thumbPreview}
/> />
)} )}
<h1 className="channel__title"> <h1 className="channel__title">{title || '@' + channelName}</h1>
{title || '@' + channelName} {channelIsMine && !editing && (
{channelIsMine && !editing && ( <Button button="alt" title={__('Edit')} onClick={() => setEditing(!editing)} icon={ICONS.EDIT} />
<Button title={__('Edit')} onClick={() => setEditing(!editing)} icon={ICONS.EDIT} iconSize={49} /> )}
)}
</h1>
<div className="channel__meta"> <div className="channel__meta">
<ClaimUri uri={uri} /> <ClaimUri uri={uri} inline />
<span> <span>
{subCount} {subCount !== 1 ? __('Subscribers') : __('Subscriber')} {subCount} {subCount !== 1 ? __('Subscribers') : __('Subscriber')}
<HelpLink href="https://lbry.com/faq/views" /> <HelpLink href="https://lbry.com/faq/views" />
@ -226,7 +224,7 @@ function ChannelPage(props: Props) {
<TabList className="tabs__list--channel-page"> <TabList className="tabs__list--channel-page">
<Tab disabled={editing}>{__('Content')}</Tab> <Tab disabled={editing}>{__('Content')}</Tab>
<Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab> <Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab>
<Tab disabled={editing}>{__('Discussion')}</Tab> <Tab disabled={editing}>{__('Comments')}</Tab>
{/* only render searchbar on content page (tab index 0 === content page) */} {/* only render searchbar on content page (tab index 0 === content page) */}
{tabIndex === 0 ? ( {tabIndex === 0 ? (
<Form onSubmit={handleSearch} className="wunderbar--inline"> <Form onSubmit={handleSearch} className="wunderbar--inline">

View file

@ -4,6 +4,7 @@ import ClaimList from 'component/claimList';
import Page from 'component/page'; import Page from 'component/page';
import Button from 'component/button'; import Button from 'component/button';
import YoutubeTransferStatus from 'component/youtubeTransferStatus'; import YoutubeTransferStatus from 'component/youtubeTransferStatus';
import Spinner from 'component/spinner';
type Props = { type Props = {
channels: Array<ChannelClaim>, channels: Array<ChannelClaim>,
@ -36,7 +37,7 @@ export default function ChannelsPage(props: Props) {
<Page> <Page>
{hasYoutubeChannels && <YoutubeTransferStatus hideChannelLink />} {hasYoutubeChannels && <YoutubeTransferStatus hideChannelLink />}
{channels && channels.length ? ( {channels && Boolean(channels.length) && (
<div className="card"> <div className="card">
<ClaimList <ClaimList
header={__('Your Channels')} header={__('Your Channels')}
@ -44,16 +45,30 @@ export default function ChannelsPage(props: Props) {
uris={channels.map(channel => channel.permanent_url)} uris={channels.map(channel => channel.permanent_url)}
/> />
</div> </div>
) : ( )}
<section className="main--empty"> {!(channels && channels.length) && (
<div className=" section--small"> <React.Fragment>
<h2 className="section__title--large">{__('No Channels Created Yet')}</h2> {!fetchingChannels ? (
<section className="main--empty">
<div className=" section--small">
<h2 className="section__title--large">{__('No Channels Created Yet')}</h2>
<div className="section__actions"> <div className="section__actions">
<Button button="primary" navigate="/$/publish" label={__('Create A Channel')} /> <Button button="primary" navigate="/$/publish" label={__('Create A Channel')} />
</div> </div>
</div> </div>
</section> </section>
) : (
<section className="main--empty">
<div className=" section--small">
<h2 className="section__title--small">
{__('Checking for channels')}
<Spinner type="small" />
</h2>
</div>
</section>
)}
</React.Fragment>
)} )}
</Page> </Page>
); );

Some files were not shown because too many files have changed in this diff Show more