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);
return {
presets: ['@babel/env', '@babel/react', '@babel/flow'],
presets: [['@babel/env', { loose: true, modules: false }], '@babel/react', '@babel/flow'],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
'import-glob',
'@babel/plugin-transform-runtime',
['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],

View file

@ -1,5 +1,7 @@
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) {
const requestHost = ctx.host;
@ -16,8 +18,16 @@ async function redirectMiddleware(ctx, next) {
return;
}
if (redirectHosts.includes(requestHost)) {
const redirectUrl = config.URL + path;
if (requestHost === 'open.lbry.com' || requestHost === 'open.lbry.io') {
const openQuery = '?src=open';
let redirectUrl = config.URL + formatInAppUrl(url, openQuery);
if (redirectUrl.includes('?')) {
redirectUrl = redirectUrl.replace('?', `${openQuery}&`);
} else {
redirectUrl += openQuery;
}
ctx.redirect(redirectUrl);
return;
}

View file

@ -38,6 +38,7 @@
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.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-runtime": "^7.4.3",
"@babel/polyfill": "^7.2.5",

View file

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

View file

@ -820,7 +820,6 @@
"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.",
"%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",
"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!",
@ -890,4 +889,18 @@
"allow you to receive notifications related to new content.": "allow you to receive notifications related to new content.",
"files": "files",
"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(() => {
// $FlowFixMe
document.documentElement.setAttribute('data-mode', theme);
document.documentElement.setAttribute('theme', theme);
}, [theme]);
useEffect(() => {

View file

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

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@ function ChannelContent(props: Props) {
{!fetching && !hasContent && !channelIsBlocked && !channelIsBlackListed && (
<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>
)}

View file

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

View file

@ -121,7 +121,13 @@ export default function ClaimList(props: Props) {
{urisLength > 0 && (
<ul className="ul--no-style">
{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>
)}

View file

@ -1,5 +1,6 @@
// @flow
import type { Node } from 'react';
import * as PAGES from 'constants/pages';
import React, { Fragment, useEffect, useState } from 'react';
import { withRouter } from 'react-router';
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
@ -173,7 +174,26 @@ function ClaimListDiscover(props: Props) {
</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() {
let search = `?`;
@ -234,9 +254,9 @@ function ClaimListDiscover(props: Props) {
</FormField>
{!hideCustomization && (
<Fragment>
<span>{__('For')}</span>
<span className="claim-list__conjuction">{__('for')}</span>
{!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
type="select"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -80,36 +80,24 @@ export class FormField extends React.PureComponent<Props> {
const errorMessage = typeof error === 'object' ? error.message : error;
const Wrapper = blockWrap
? ({ children: innerChildren }) => <fieldset-section>{innerChildren}</fieldset-section>
: ({ children: innerChildren }) => <React.Fragment>{innerChildren}</React.Fragment>;
? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section>
: ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>;
let input;
if (type) {
if (type === 'radio') {
input = (
<Wrapper>
<radio-element>
<input id={name} type="radio" {...inputProps} />
<label htmlFor={name}>{label}</label>
<radio-toggle onClick={inputProps.onChange} />
</radio-element>
<input id={name} type="radio" {...inputProps} />
<label htmlFor={name}>{label}</label>
</Wrapper>
);
} 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 = (
<Wrapper>
<checkbox-element {...elementProps}>
<input id={name} type="checkbox" {...inputProps} tabIndex={0} />
<label htmlFor={name} tabIndex={-1}>
{label}
</label>
<checkbox-toggle onClick={inputProps.onChange} tabIndex={-1} />
</checkbox-element>
</Wrapper>
<div className="checkbox">
<input id={name} type="checkbox" {...inputProps} />
<label htmlFor={name}>{label}</label>
</div>
);
} else if (type === 'select') {
input = (
@ -172,7 +160,11 @@ export class FormField extends React.PureComponent<Props> {
input = (
<React.Fragment>
<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>}
{inner}
</fieldset-section>

View file

@ -11,7 +11,7 @@ export default function UnsupportedOnWeb(props: Props) {
return (
IS_WEB && (
<div className="card__subtitle--status">
<div className="section__subtitle--status">
{type === 'page' && __('This page 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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ class FileActions extends React.PureComponent<Props> {
{showDelete && (
<Tooltip label={__('Remove from your library')}>
<Button
button="link"
button="alt"
icon={ICONS.DELETE}
description={__('Delete')}
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
@ -31,7 +31,7 @@ class FileActions extends React.PureComponent<Props> {
)}
{!claimIsMine && (
<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>
)}
</React.Fragment>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,6 +15,7 @@ import { onFullscreenChange } from 'util/full-screen';
type Props = {
mediaType: string,
contentType: string,
isLoading: boolean,
isPlaying: boolean,
fileInfo: FileListItem,
@ -47,6 +48,7 @@ export default function FileViewer(props: Props) {
triggerAnalyticsView,
claimRewards,
mediaType,
contentType,
} = props;
const [playTime, setPlayTime] = useState();
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
@ -56,7 +58,12 @@ export default function FileViewer(props: Props) {
});
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 =
!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.")

View file

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

View file

@ -14,6 +14,7 @@ const SPACE_BAR_KEYCODE = 32;
type Props = {
play: string => void,
mediaType: string,
contentType: string,
isLoading: boolean,
isPlaying: boolean,
fileInfo: FileListItem,
@ -31,6 +32,7 @@ export default function FileViewer(props: Props) {
const {
play,
mediaType,
contentType,
isPlaying,
fileInfo,
uri,
@ -43,9 +45,11 @@ export default function FileViewer(props: Props) {
costInfo,
} = props;
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 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
// 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;
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);
// Sign out if they click the "x" when they are on the password prompt
@ -78,6 +78,9 @@ const Header = (props: Props) => {
<header
className={classnames('header', {
'header--minimal': authHeader,
// @if TARGET='web'
'header--noauth-web': !authenticated,
// @endif
// @if TARGET='app'
'header--mac': IS_MAC,
// @endif
@ -118,8 +121,8 @@ const Header = (props: Props) => {
</div>
{!authHeader ? (
<div className={classnames('header__menu', { 'header__menu--small': IS_WEB && !authenticated })}>
{!IS_WEB || authenticated ? (
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
{(!IS_WEB || authenticated) && (
<Fragment>
<Menu>
<MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton>
@ -135,7 +138,7 @@ const Header = (props: Props) => {
</MenuList>
</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} />
</MenuButton>
<MenuList className="menu__list--header">
@ -161,28 +164,28 @@ const Header = (props: Props) => {
)}
</MenuList>
</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>
) : (
)}
<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')} />
)}
</div>
@ -193,7 +196,7 @@ const Header = (props: Props) => {
{/* This pushes the close button to the right side */}
<span />
<Tooltip label={__('Go Back')}>
<Button icon={ICONS.REMOVE} {...closeButtonNavigationProps} />
<Button button="link" icon={ICONS.REMOVE} {...closeButtonNavigationProps} />
</Tooltip>
</div>
)

View file

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

View file

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

View file

@ -106,7 +106,9 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
) : (
<div className="main--empty">
<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">
<Button button="primary" navigate="/" label={__('Explore new content')} />

View file

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

View file

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

View file

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

View file

@ -138,12 +138,12 @@ function PublishForm(props: Props) {
<Card actions={<SelectThumbnail />} />
<TagsSelect
title={__('Add Tags')}
suggestMature
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')}
placeholder={__('Add a tag')}
placeholder={__('Add a tag...')}
onSelect={newTags => {
const validatedTags = [];
newTags.forEach(newTag => {

View file

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

View file

@ -25,14 +25,16 @@ const RewardListClaimed = (props: Props) => {
return (
<section className="card">
<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">
{__(
'Reward history is tied to your email. In case of lost or multiple wallets, your balance may differ from the amounts claimed'
)}
.
</p>
<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'
)}
.
</p>
</div>
</header>
<table className="table table--rewards">

View file

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

View file

@ -1,6 +1,6 @@
// @flow
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 SettingsPage from 'page/settings';
import HelpPage from 'page/help';
@ -17,8 +17,6 @@ import InvitePage from 'page/invite';
import SearchPage from 'page/search';
import LibraryPage from 'page/library';
import WalletPage from 'page/wallet';
import WalletSendPage from 'page/walletSend';
import WalletReceivePage from 'page/walletReceive';
import TagsPage from 'page/tags';
import FollowingPage from 'page/following';
import ListBlockedPage from 'page/listBlocked';
@ -71,39 +69,35 @@ function AppRouter(props: Props) {
}, [currentScroll, pathname]);
return (
<Suspense fallback={<span>LODIFJDSLKJFSLDKJFLDJ</span>}>
<Switch>
<Route path="/" exact component={DiscoverPage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Switch>
<Route path="/" exact component={DiscoverPage} />
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
<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.DOWNLOADED}`} component={FileListDownloaded} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
<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.LIBRARY}`} component={LibraryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<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} />
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
<PrivateRoute {...props} path={`/$/${PAGES.DOWNLOADED}`} component={FileListDownloaded} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISHED}`} component={FileListPublished} />
<PrivateRoute {...props} path={`/$/${PAGES.PUBLISH}`} component={PublishPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REPORT}`} component={ReportPage} />
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
<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 */}
<Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} />
<Route path="/*" component={FourOhFourPage} />
</Switch>
</Suspense>
{/* 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/:streamName" exact component={ShowPage} />
<Route path="/*" component={FourOhFourPage} />
</Switch>
);
}

View file

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

View file

@ -6,7 +6,7 @@ import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';
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 = {
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');
} else if (newChannelBid > balance) {
newChannelBidError = __('Deposit cannot be higher than your balance');
} else if (newChannelBid < MINIMUM_PUBLISH_BID) {
newChannelBidError = __('Your deposit must be higher');
}
this.setState({

View file

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

View file

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

View file

@ -8,6 +8,8 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
import ModalUpgrade from 'modal/modalUpgrade';
import ModalDownloading from 'modal/modalDownloading';
import Card from 'component/common/card';
import I18nMessage from 'component/i18nMessage';
import 'css-doodle';
const FORTY_FIVE_SECONDS = 45 * 1000;
@ -227,7 +229,7 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
{!animationHidden && (
<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 {
@grid: 30x1 / 18vmin;
--deg: @p(-180deg, 180deg);
@ -267,21 +269,28 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
onClick={() => setClientSetting(SETTINGS.HIDE_SPLASH_ANIMATION, !animationHidden)}
/>
{error && (
<div className="splash__error card card--section">
<p className="card__subtitle">
{__('Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.')}
</p>
<div className="card__actions--center">
<Button button="primary" label={__('Refresh')} onClick={() => window.location.reload()} />
</div>
<div className="help">
<p>{__('If you still have issues, your anti-virus software or firewall may be preventing startup.')}</p>
<p>
{__('Reach out to hello@lbry.com for help, or check out')}{' '}
<Button button="link" href="https://lbry.com/faq/startup-troubleshooting" label="this link" />.
</p>
</div>
</div>
<Card
className="splash__error"
title={__('Error Starting Up')}
subtitle={
<React.Fragment>
<p>
Try refreshing to fix it. If you still have issues, your anti-virus software or firewall may be
preventing startup.
</p>
<I18nMessage
tokens={{
help_link: (
<Button button="link" href="https://lbry.com/faq/startup-troubleshooting" label="this link" />
),
}}
>
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;
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}
title={title}
className={classnames('tag', {
'tag--large': type === 'large',
'tag--remove': type === 'remove',
// tag--add only adjusts the color, which causes issues with mature tag color clashing
'tag--add': !isMature && type === 'add',
@ -37,7 +38,7 @@ export default function Tag(props: Props) {
})}
label={name}
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,
onRemove: Tag => void,
placeholder?: string,
label?: string,
};
/*
@ -36,6 +37,7 @@ export default function TagsSearch(props: Props) {
suggestMature,
disableAutoFocus,
placeholder,
label,
} = props;
const [newTag, setNewTag] = useState('');
const doesTagMatch = name => {
@ -107,6 +109,7 @@ export default function TagsSearch(props: Props) {
return (
<React.Fragment>
<Form className="tags__input-wrapper" onSubmit={handleSubmit}>
<label>{label || __('Following')}</label>
<ul className="tags--remove">
{tagsPassedIn.map(tag => (
<Tag
@ -130,6 +133,7 @@ export default function TagsSearch(props: Props) {
</li>
</ul>
</Form>
<label>{__('Suggested')}</label>
<ul className="tags">
{suggestedTags.map(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
title?: string | boolean,
help?: string,
label?: string,
tagsChosen?: Array<Tag>,
onSelect?: (Array<Tag>) => void,
onRemove?: Tag => void,
placeholder?: string,
disableAutoFocus?: boolean,
hideHeader?: boolean,
};
/*
@ -40,6 +42,8 @@ export default function TagsSelect(props: Props) {
suggestMature,
disableAutoFocus,
placeholder,
hideHeader,
label,
} = props;
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
const tagsToDisplay = tagsChosen || followedTags;
@ -71,14 +75,17 @@ export default function TagsSelect(props: Props) {
return (
((showClose && !hasClosed) || !showClose) && (
<Card
actionIconPadding={false}
icon={ICONS.TAG}
title={
<React.Fragment>
{title}
{showClose && tagsToDisplay.length > 0 && !hasClosed && (
<Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />
)}
</React.Fragment>
hideHeader ? null : (
<React.Fragment>
{title}
{showClose && tagsToDisplay.length > 0 && !hasClosed && (
<Button button="close" icon={ICONS.REMOVE} onClick={handleClose} />
)}
</React.Fragment>
)
}
subtitle={
help !== false && (
@ -91,6 +98,7 @@ export default function TagsSelect(props: Props) {
actions={
<React.Fragment>
<TagsSearch
label={label}
onRemove={handleTagClick}
onSelect={onSelect}
suggestMature={suggestMature && !hasMatureTag}

View file

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

View file

@ -26,9 +26,9 @@ class TransactionListItem extends React.PureComponent<Props> {
getLink(type: string) {
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() {
@ -65,13 +65,15 @@ class TransactionListItem extends React.PureComponent<Props> {
return (
<tr>
<td>
<CreditAmount badge={false} showPlus amount={amount} precision={8} />
<br />
{fee !== 0 && (
<span className="table__item-label">
<CreditAmount badge={false} fee amount={fee} precision={8} />
</span>
{date ? (
<div>
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
<div className="table__item-label">
<DateTime date={date} show={DateTime.SHOW_TIME} />
</div>
</div>
) : (
<span className="empty">{__('Pending')}</span>
)}
</td>
<td className="table__item--actionable">
@ -85,16 +87,14 @@ class TransactionListItem extends React.PureComponent<Props> {
<td>
<ButtonTransaction id={txid} />
</td>
<td>
{date ? (
<div>
<DateTime date={date} show={DateTime.SHOW_DATE} formatOptions={dateFormat} />
<div className="table__item-label">
<DateTime date={date} show={DateTime.SHOW_TIME} />
</div>
</div>
) : (
<span className="empty">{__('Pending')}</span>
<td className="table__item--align-right">
<CreditAmount badge={false} showPlus amount={amount} precision={8} />
<br />
{fee !== 0 && (
<span className="table__item-label">
<CreditAmount badge={false} fee amount={fee} precision={8} />
</span>
)}
</td>
</tr>

View file

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

View file

@ -45,7 +45,7 @@ class TransactionRefreshButton extends PureComponent<Props, State> {
const { fetchingTransactions } = this.props;
const { label, disabled } = this.state;
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>
}
inputButton={<UserSignOutButton button="inverse" />}
inputButton={<UserSignOutButton button="secondary" />}
value={email || ''}
/>
) : (

View file

@ -72,7 +72,12 @@ function UserEmailNew(props: Props) {
<I18nMessage
tokens={{
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 (
<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.'
)}

View file

@ -1,33 +1,37 @@
// @flow
import * as React from 'react';
import Villain from 'villain';
import 'villain/dist/style.css';
import Villain from 'villain-react';
import 'villain-react/dist/style.css';
type Props = {
theme: string,
source: {
fileType: string,
downloadPath: string,
},
};
let workerPath = 'webworkers/worker-bundle.js';
let workerUrl = 'webworkers/worker-bundle.js';
if (process.env.NODE_ENV !== 'production') {
// 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> {
render() {
const { downloadPath } = this.props.source || {};
// Archive source
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
import React, { Suspense } from 'react';
import React from 'react';
import LoadingScreen from 'component/common/loading-screen';
import MarkdownPreview from 'component/common/markdown-preview';
import CodeViewer from 'component/viewers/codeViewer';
import * as https from 'https';
type Props = {
theme: string,
source: {
stream: string => any,
file: (?string) => any,
stream: string,
fileType: string,
contentType: string,
},
@ -32,9 +34,9 @@ class DocumentViewer extends React.PureComponent<Props, State> {
componentDidMount() {
const { source } = this.props;
if (source && source.stream) {
const stream = source.stream('utf8');
// @if TARGET='app'
if (source && source.file) {
const stream = source.file('utf8');
let data = '';
@ -50,6 +52,30 @@ class DocumentViewer extends React.PureComponent<Props, State> {
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() {
@ -58,10 +84,9 @@ class DocumentViewer extends React.PureComponent<Props, State> {
const { source, theme } = this.props;
const { fileType, contentType } = source;
const markdownType = ['md', 'markdown'];
if (markdownType.includes(fileType)) {
if (markdownType.includes(fileType) || contentType === 'text/markdown' || contentType === 'text/md') {
// Render markdown
viewer = <MarkdownPreview content={content} promptLinks />;
viewer = <MarkdownPreview content={content} />;
} else {
// Render plain text
viewer = <CodeViewer value={content} contentType={contentType} theme={theme} />;

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { makeSelectFileInfoForUri } from 'lbry-redux';
import { makeSelectFileInfoForUri, makeSelectThumbnailForUri } from 'lbry-redux';
import { doChangeVolume, doChangeMute } from 'redux/actions/app';
import { selectVolume, selectMute } from 'redux/selectors/app';
import { savePosition, doSetPlayingUri } from 'redux/actions/content';
@ -11,6 +11,7 @@ const select = (state, props) => ({
position: makeSelectContentPositionForUri(props.uri)(state),
muted: selectMute(state),
hasFileInfo: Boolean(makeSelectFileInfoForUri(props.uri)(state)),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
});
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 VIDEO_JS_OPTIONS = {
autoplay: true,
const VIDEO_JS_OPTIONS: { poster?: string } = {
controls: true,
preload: 'auto',
playbackRates: [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 2],
responsive: true,
};
type Props = {
@ -38,13 +38,25 @@ type Props = {
setPlayingUri: (string | null) => void,
source: string,
contentType: string,
thumbnail: string,
hasFileInfo: boolean,
onEndedCB: any,
};
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 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);
let player;
@ -88,16 +100,25 @@ function VideoViewer(props: Props) {
sources: [
{
src: source,
type: contentType,
type: forceMp4 ? 'video/mp4' : contentType,
},
],
};
if (isAudio) {
videoJsOptions.poster = thumbnail;
}
if (!requireRedraw) {
player = videojs(videoNode, videoJsOptions, function() {
const self = this;
self.volume(volume);
self.muted(muted);
player.volume(volume);
player.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}>
{!requireRedraw && (
<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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -370,11 +370,10 @@ export default class Autocomplete extends React.Component {
const matchedItem = this.getFilteredItems(props)[index];
if (value !== '' && matchedItem) {
const itemValue = getItemValue(matchedItem);
const itemValueDoesMatch =
itemValue.toLowerCase().includes(
value.toLowerCase()
// below line is the the only thing that is changed from the real component
);
const itemValueDoesMatch = itemValue.toLowerCase().includes(
value.toLowerCase()
// below line is the the only thing that is changed from the real component
);
if (itemValueDoesMatch) {
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}`} />
{title && subtitle && (
<div className="yrbl__content">
<h2 className="card__title">{title}</h2>
<h2 className="card__title card__title--deprecated">{title}</h2>
<p>{subtitle}</p>
</div>
)}

View file

@ -29,3 +29,5 @@ export const WALLET_DECRYPT = 'wallet_decrypt';
export const WALLET_UNLOCK = 'wallet_unlock';
export const WALLET_SYNC = 'wallet_sync';
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';
export const AUTH_VERIFY = 'verify';
export const BACKUP = 'backup';
export const CHANNEL = 'channel';
export const DISCOVER = 'discover';
export const DOWNLOADED = 'downloaded';
export const HELP = 'help';
export const LIBRARY = 'library';
export const INVITE = 'invite';
export const PUBLISH = 'publish';
export const PUBLISHED = 'published';
export const GET_CREDITS = 'getcredits';
export const REPORT = 'report';
export const REWARDS = 'rewards';
export const SEND = 'send';
export const SETTINGS = 'settings';
export const SHOW = 'show';
export const ACCOUNT = 'account';
export const FOLLOWING = 'following';
export const SEARCH = 'search';
export const TRANSACTIONS = 'transactions';
export const TAGS = 'tags';
export const WALLET = 'wallet';
export const WALLET_SEND = 'wallet/send';
export const WALLET_RECEIVE = 'wallet/receive';
export const BLOCKED = 'blocked';
export const CHANNELS = 'channels';
exports.AUTH = 'signin';
exports.AUTH_VERIFY = 'verify';
exports.BACKUP = 'backup';
exports.CHANNEL = 'channel';
exports.DISCOVER = 'discover';
exports.DOWNLOADED = 'downloaded';
exports.HELP = 'help';
exports.LIBRARY = 'library';
exports.INVITE = 'invite';
exports.PUBLISH = 'publish';
exports.PUBLISHED = 'published';
exports.GET_CREDITS = 'getcredits';
exports.REPORT = 'report';
exports.REWARDS = 'rewards';
exports.SEND = 'send';
exports.SETTINGS = 'settings';
exports.SHOW = 'show';
exports.ACCOUNT = 'account';
exports.FOLLOWING = 'following';
exports.SEARCH = 'search';
exports.TRANSACTIONS = 'transactions';
exports.TAGS = 'tags';
exports.WALLET = 'wallet';
exports.BLOCKED = 'blocked';
exports.CHANNELS = 'channels';

View file

@ -1,3 +1,5 @@
import 'babel-polyfill';
import ErrorBoundary from 'component/errorBoundary';
import App from 'component/app';
import SnackBar from 'component/snackBar';
@ -11,7 +13,7 @@ import * as MODALS from 'constants/modal_types';
import React, { Fragment, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
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 { doSetLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
import {
@ -26,7 +28,7 @@ import pjson from 'package.json';
import app from './app';
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
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 analytics from 'analytics';
import { getAuthToken, setAuthToken } from 'util/saved-passwords';
@ -68,7 +70,6 @@ Lbry.setOverride(
const startTime = Date.now();
analytics.startupEvent();
const APPPAGEURL = 'lbry://?';
// @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater');
autoUpdater.logger = remote.require('electron-log');
@ -155,25 +156,29 @@ rewards.setCallback('claimRewardSuccess', () => {
});
// @if TARGET='app'
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
if (uri && uri.startsWith('lbry://')) {
if (uri.startsWith('lbry://?verify')) {
app.store.dispatch(doConditionalAuthNavigate(newSession));
} else if (uri.startsWith(APPPAGEURL)) {
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'),
})
);
}
ipcRenderer.on('open-uri-requested', (event, url, newSession) => {
function handleError() {
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) => {

View file

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

View file

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

View file

@ -1,7 +1,7 @@
// @flow
import React, { useRef } from 'react';
import { Modal } from 'modal/modal';
import { formatPathForWeb } from 'util/uri';
import { formatFileSystemPath } from 'util/url';
type Props = {
upload: WebFile => void,
@ -15,15 +15,18 @@ function ModalAutoGenerateThumbnail(props: Props) {
const playerRef = useRef();
let videoSrc;
if (typeof filePath === 'string') {
videoSrc = formatPathForWeb(filePath);
videoSrc = formatFileSystemPath(filePath);
} else {
videoSrc = URL.createObjectURL(filePath);
}
function uploadImage() {
const imageBuffer = captureSnapshot();
// $FlowFixMe
const file = new File([imageBuffer], 'thumbnail.png', { type: 'image/png' });
if (imageBuffer) {
// $FlowFixMe
upload(file);
closeModal();
} else {
@ -75,7 +78,7 @@ function ModalAutoGenerateThumbnail(props: Props) {
onConfirmed={uploadImage}
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 />
</Modal>
);

View file

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

View file

@ -36,7 +36,7 @@ class ModalPublishSuccess extends React.PureComponent<Props> {
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.`, {
publishMessage,
})}

View file

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

View file

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

View file

@ -150,7 +150,7 @@ class ModalWalletEncrypt extends React.PureComponent<Props, State> {
/>
</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.'
)}

View file

@ -1,9 +1,12 @@
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import WalletReceive from './view';
const select = state => ({});
const perform = dispatch => ({});
const perform = {
doHideModal,
};
export default connect(
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 { doHideModal } from 'redux/actions/app';
import WalletSend from './view';
const select = state => ({});
const perform = dispatch => ({});
const perform = {
doHideModal,
};
export default connect(
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 WalletAddress from 'component/walletAddress';
import Page from 'component/page';
import WalletAddress from './node_modules/component/walletAddress';
import Page from './node_modules/component/page';
const WalletAddressPage = () => (
<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 { withRouter } from 'react-router';
import Button from 'component/button';
import { formatLbryUriForWeb } from 'util/uri';
import { formatLbryUrlForWeb } from 'util/url';
import ChannelContent from 'component/channelContent';
import ChannelAbout from 'component/channelAbout';
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;
function onTabChange(newTabIndex) {
let url = formatLbryUriForWeb(uri);
let url = formatLbryUrlForWeb(uri);
let search = '?';
if (newTabIndex === 0) {
@ -196,7 +196,7 @@ function ChannelPage(props: Props) {
)}
{editing && <img className="channel-cover__custom" src={coverPreview} />}
{/* component that offers select/upload */}
<div className="channel__primary-info ">
<div className="channel__primary-info">
{!editing && (
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} obscure={channelIsBlocked} />
)}
@ -207,14 +207,12 @@ function ChannelPage(props: Props) {
thumbnailPreview={thumbPreview}
/>
)}
<h1 className="channel__title">
{title || '@' + channelName}
{channelIsMine && !editing && (
<Button title={__('Edit')} onClick={() => setEditing(!editing)} icon={ICONS.EDIT} iconSize={49} />
)}
</h1>
<h1 className="channel__title">{title || '@' + channelName}</h1>
{channelIsMine && !editing && (
<Button button="alt" title={__('Edit')} onClick={() => setEditing(!editing)} icon={ICONS.EDIT} />
)}
<div className="channel__meta">
<ClaimUri uri={uri} />
<ClaimUri uri={uri} inline />
<span>
{subCount} {subCount !== 1 ? __('Subscribers') : __('Subscriber')}
<HelpLink href="https://lbry.com/faq/views" />
@ -226,7 +224,7 @@ function ChannelPage(props: Props) {
<TabList className="tabs__list--channel-page">
<Tab disabled={editing}>{__('Content')}</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) */}
{tabIndex === 0 ? (
<Form onSubmit={handleSearch} className="wunderbar--inline">

View file

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

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