change: less intrusive first run experience

This commit is contained in:
Sean Yesmunt 2019-01-22 13:29:45 -05:00
parent 1f74ff3c63
commit defc08eccb
24 changed files with 449 additions and 191 deletions

View file

@ -67,6 +67,7 @@
"react-feather": "^1.0.8",
"react-modal": "^3.1.7",
"react-paginate": "^5.2.1",
"react-pose": "^4.0.5",
"react-redux": "^5.0.3",
"react-simplemde-editor": "^3.6.16",
"react-toggle": "^4.0.2",

View file

@ -0,0 +1,29 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectEmailToVerify, selectUser } from 'lbryinc';
import FirstRunEmailCollection from './view';
const select = state => ({
emailCollectionAcknowledged: makeSelectClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED)(
state
),
email: selectEmailToVerify(state),
user: selectUser(state),
});
const perform = dispatch => () => ({
completeFirstRun: () => {
dispatch(doSetClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, true));
dispatch(doSetClientSetting(SETTINGS.FIRST_RUN_COMPLETED, true));
},
acknowledgeEmail: () => {
dispatch(doSetClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, true));
},
});
export default connect(
select,
perform
)(FirstRunEmailCollection);

View file

@ -0,0 +1,46 @@
// @flow
import React from 'react';
import Button from 'component/button';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
type Props = {
email: string,
emailCollectionAcknowledged: boolean,
user: ?{ has_verified_email: boolean },
completeFirstRun: () => void,
acknowledgeEmail: () => void,
};
class FirstRunEmailCollection extends React.PureComponent<Props> {
render() {
const {
completeFirstRun,
email,
user,
emailCollectionAcknowledged,
acknowledgeEmail,
} = this.props;
// this shouldn't happen
if (!user) {
return null;
}
const cancelButton = <Button button="link" onClick={completeFirstRun} label={__('Not Now')} />;
if (user && !user.has_verified_email && !email) {
return <UserEmailNew cancelButton={cancelButton} />;
} else if (user && !user.has_verified_email) {
return <UserEmailVerify cancelButton={cancelButton} />;
}
// Try to acknowledge here so users don't see an empty email screen in the first run banner
if (!emailCollectionAcknowledged) {
acknowledgeEmail();
}
return null;
}
}
export default FirstRunEmailCollection;

View file

@ -0,0 +1,29 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux';
import { selectUser } from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetClientSetting } from 'redux/actions/settings';
import FirstRun from './view';
const select = state => ({
emailCollectionAcknowledged: makeSelectClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED)(
state
),
welcomeAcknowledged: makeSelectClientSetting(SETTINGS.NEW_USER_ACKNOWLEDGED)(state),
firstRunComplete: makeSelectClientSetting(SETTINGS.FIRST_RUN_COMPLETED)(state),
user: selectUser(state),
});
const perform = dispatch => ({
acknowledgeWelcome: () => {
dispatch(doSetClientSetting(SETTINGS.NEW_USER_ACKNOWLEDGED, true));
},
completeFirstRun: () => {
dispatch(doSetClientSetting(SETTINGS.FIRST_RUN_COMPLETED, true));
},
});
export default connect(
select,
perform
)(FirstRun);

View file

@ -0,0 +1,115 @@
// @flow
import React, { PureComponent } from 'react';
import posed from 'react-pose';
import Button from 'component/button';
import EmailCollection from 'component/emailCollection';
import Native from 'native';
//
// Animation for items inside banner
// The height for items must be static (in banner.scss) so that we can reliably animate into the banner and be vertically centered
//
const spring = {
type: 'spring',
stiffness: 100,
damping: 10,
mass: 1,
};
const Welcome = posed.div({
hide: { opacity: 0, y: '310px', ...spring },
show: { opacity: 1, ...spring },
});
const Email = posed.div({
hide: { opacity: 0, y: '0', ...spring },
show: { opacity: 1, y: '-310px', ...spring, delay: 175 },
});
const Help = posed.div({
hide: { opacity: 0, y: '0', ...spring },
show: { opacity: 1, y: '-620px', ...spring, delay: 175 },
});
type Props = {
welcomeAcknowledged: boolean,
emailCollectionAcknowledged: boolean,
firstRunComplete: boolean,
acknowledgeWelcome: () => void,
completeFirstRun: () => void,
};
export default class FirstRun extends PureComponent<Props> {
render() {
const {
welcomeAcknowledged,
emailCollectionAcknowledged,
firstRunComplete,
acknowledgeWelcome,
completeFirstRun,
} = this.props;
if (firstRunComplete) {
return null;
}
const showWelcome = !welcomeAcknowledged;
const showEmail = !emailCollectionAcknowledged && welcomeAcknowledged;
const showHelp = !showWelcome && !showEmail;
return (
<div className="banner banner--first-run">
<img
alt="Friendly gerbil"
className="yrbl--first-run banner__item"
src={Native.imagePath('gerbil-happy.png')}
/>
<div className="banner__item">
<div className="banner__item--static-for-animation">
<Welcome className="banner__content" pose={showWelcome ? 'show' : 'hide'}>
<div>
<header className="card__header">
<h1 className="card__title">{__('Hi There')}</h1>
</header>
<div className="card__content">
<p>
{__('Using LBRY is like dating a centaur. Totally normal up top, and')}{' '}
<em>{__('way different')}</em> {__('underneath.')}
</p>
<p>{__('Up top, LBRY is similar to popular media sites.')}</p>
<p>
{__(
'Below, LBRY is controlled by users -- you -- via blockchain and decentralization.'
)}
</p>
<div className="card__actions">
<Button button="primary" onClick={acknowledgeWelcome} label={__("I'm In")} />
</div>
</div>
</div>
</Welcome>
</div>
<div className="banner__item--static-for-animation">
<Email pose={showEmail ? 'show' : 'hide'}>
<EmailCollection />
</Email>
</div>
<div className="banner__item--static-for-animation">
<Help pose={showHelp ? 'show' : 'hide'}>
<header className="card__header">
<h1 className="card__title">{__('You Are Awesome!')}</h1>
</header>
<div className="card__content">
<p>{__("Check out some of the neat files below me. I'll see you around!")}</p>
<div className="card__actions">
<Button button="primary" onClick={completeFirstRun} label={__('See You Later')} />
</div>
</div>
</Help>
</div>
</div>
</div>
);
}
}

View file

@ -61,7 +61,7 @@ class SocialShare extends React.PureComponent<Props> {
<div className="card__content">
<label className="card__subtitle">{__('Web link')}</label>
<CopyableText copyable={speechURL} />
<div className="card__actions card__actions--center card__actions--top-space">
<div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}>
<Button
iconColor="blue"
@ -95,7 +95,7 @@ class SocialShare extends React.PureComponent<Props> {
<div className="card__content">
<label className="card__subtitle">{__('LBRY App link')}</label>
<CopyableText copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center card__actions--top-space">
<div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}>
<Button
iconColor="blue"

View file

@ -42,16 +42,14 @@ class UserEmailNew extends React.PureComponent<Props, State> {
return (
<React.Fragment>
<p>
{__("We'll let you know about LBRY updates, security issues, and great new content.")}
</p>
<header className="card__header">
<h2 className="card__title">{__("Don't Miss Out")}</h2>
<p className="card__subtitle">
{__("We'll let you know about LBRY updates, security issues, and great new content.")}
</p>
</header>
<p>
{__(
'In addition, your email address will never be sold and you can unsubscribe at any time.'
)}
</p>
<Form onSubmit={this.handleSubmit}>
<Form className="card__content" onSubmit={this.handleSubmit}>
<FormRow>
<FormField
stretch
@ -66,10 +64,13 @@ class UserEmailNew extends React.PureComponent<Props, State> {
</FormRow>
<div className="card__actions">
<Submit label="Submit" disabled={isPending} />
<Submit label="Submit" disabled={isPending || !this.state.email} />
{cancelButton}
</div>
</Form>
<p className="help">
{__('Your email address will never be sold and you can unsubscribe at any time.')}
</p>
</React.Fragment>
);
}

View file

@ -50,27 +50,33 @@ class UserEmailVerify extends React.PureComponent<Props> {
const { cancelButton, email } = this.props;
return (
<div>
<p>
{__('An email was sent to')} {email}.{' '}
{__('Follow the link and you will be good to go. This will update automatically.')}
</p>
<React.Fragment>
<header className="card__header">
<h2 className="card__title">{__('Waiting For Verification')}</h2>
</header>
<div className="card__actions">
<Button
button="primary"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
{cancelButton}
<div className="card__content">
<p>
{__('An email was sent to')} {email}.{' '}
{__('Follow the link and you will be good to go. This will update automatically.')}
</p>
<div className="card__actions">
<Button
button="primary"
label={__('Resend verification email')}
onClick={this.handleResendVerificationEmail}
/>
{cancelButton}
</div>
<p className="help">
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" />{' '}
or join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble verifying.')}
</p>
</div>
<p className="help">
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" /> or
join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
{__('if you encounter any trouble verifying.')}
</p>
</div>
</React.Fragment>
);
}
}

View file

@ -4,6 +4,7 @@
export const CREDIT_REQUIRED_ACKNOWLEDGED = 'credit_required_acknowledged';
export const NEW_USER_ACKNOWLEDGED = 'welcome_acknowledged';
export const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged';
export const FIRST_RUN_COMPLETED = 'first_run_completed';
export const LANGUAGE = 'language';
export const SHOW_NSFW = 'showNsfw';
export const SHOW_UNAVAILABLE = 'showUnavailable';

View file

@ -1,23 +0,0 @@
import * as settings from 'constants/settings';
import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import { doSetClientSetting } from 'redux/actions/settings';
import { selectEmailToVerify, selectUser } from 'lbryinc';
import ModalEmailCollection from './view';
const select = state => ({
email: selectEmailToVerify(state),
user: selectUser(state),
});
const perform = dispatch => () => ({
closeModal: () => {
dispatch(doSetClientSetting(settings.EMAIL_COLLECTION_ACKNOWLEDGED, true));
dispatch(doHideModal());
},
});
export default connect(
select,
perform
)(ModalEmailCollection);

View file

@ -1,54 +0,0 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import Button from 'component/button';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
type Props = {
closeModal: () => void,
email: string,
user: ?{ has_verified_email: boolean },
};
class ModalEmailCollection extends React.PureComponent<Props> {
getTitle() {
// const { user } = this.props;
//
// if (user && user.email && !user.has_verified_email) {
// return __('Awaiting Confirmation');
// }
return __('Can We Stay In Touch?');
}
renderInner() {
const { closeModal, email, user } = this.props;
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
if (user && !user.has_verified_email && !email) {
return <UserEmailNew cancelButton={cancelButton} />;
} else if (user && !user.has_verified_email) {
return <UserEmailVerify onModal cancelButton={cancelButton} />;
}
return closeModal();
}
render() {
const { user } = this.props;
// this shouldn't happen
if (!user) {
return null;
}
return (
<Modal type="custom" isOpen contentLabel="Email" title={this.getTitle()}>
<section className="card__content">{this.renderInner()}</section>
</Modal>
);
}
}
export default ModalEmailCollection;

View file

@ -16,7 +16,6 @@ import ModalTransactionFailed from 'modal/modalTransactionFailed';
import ModalFileTimeout from 'modal/modalFileTimeout';
import ModalAffirmPurchase from 'modal/modalAffirmPurchase';
import ModalRevokeClaim from 'modal/modalRevokeClaim';
import ModalEmailCollection from 'modal/modalEmailCollection';
import ModalPhoneCollection from 'modal/modalPhoneCollection';
import ModalFirstSubscription from 'modal/modalFirstSubscription';
import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
@ -78,11 +77,10 @@ class ModalRouter extends React.PureComponent<Props, State> {
return;
}
const transitionModal = [
this.checkShowWelcome,
this.checkShowEmail,
this.checkShowCreditIntro,
].reduce((acc, func) => (!acc ? func.bind(this)(props) : acc), false);
const transitionModal = [this.checkShowCreditIntro].reduce(
(acc, func) => (!acc ? func.bind(this)(props) : acc),
false
);
if (
transitionModal &&
@ -96,30 +94,6 @@ class ModalRouter extends React.PureComponent<Props, State> {
}
}
checkShowWelcome(props: Props) {
const { isWelcomeAcknowledged, user } = props;
if (!isWelcomeAcknowledged && user && !user.is_reward_approved && !user.is_identity_verified) {
return MODALS.WELCOME;
}
return undefined;
}
checkShowEmail(props: Props) {
const { isEmailCollectionAcknowledged, isVerificationCandidate, user } = props;
if (
!isEmailCollectionAcknowledged &&
isVerificationCandidate &&
user &&
!user.has_verified_email
) {
return MODALS.EMAIL_COLLECTION;
}
return undefined;
}
checkShowCreditIntro(props: Props) {
const { balance, page, isCreditIntroAcknowledged } = props;
@ -185,8 +159,6 @@ class ModalRouter extends React.PureComponent<Props, State> {
return <ModalRevokeClaim {...modalProps} />;
case MODALS.PHONE_COLLECTION:
return <ModalPhoneCollection {...modalProps} />;
case MODALS.EMAIL_COLLECTION:
return <ModalEmailCollection {...modalProps} />;
case MODALS.FIRST_SUBSCRIPTION:
return <ModalFirstSubscription {...modalProps} />;
case MODALS.SEND_TIP:

View file

@ -30,19 +30,6 @@ class AuthPage extends React.PureComponent<Props> {
this.navigateIfAuthenticated(nextProps);
}
getTitle() {
const { email, isPending, user } = this.props;
if (isPending || (user && !user.has_verified_email && !email)) {
return __('Human Proofing');
} else if (user && !user.has_verified_email) {
return __('Awaiting Confirmation');
} else if (user && !user.is_identity_verified && !user.is_reward_approved) {
return __('Final Verification');
}
return __('Welcome to LBRY');
}
navigateIfAuthenticated = (props: Props) => {
const { isPending, user, pathAfterAuth, navigate } = props;
if (
@ -78,24 +65,18 @@ class AuthPage extends React.PureComponent<Props> {
<Page>
{useTemplate ? (
<section className="card card--section">
<header className="card__header card__header--flat">
<h2 className="card__title">{this.getTitle()}</h2>
</header>
{innerContent}
<div className="card__content">
{innerContent}
<p className="help">
{`${__(
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices.'
)} `}
<Button
button="link"
onClick={() => navigate('/discover')}
label={__('Return home.')}
/>
</p>
</div>
<p className="help">
{`${__(
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices.'
)} `}
<Button
button="link"
onClick={() => navigate('/discover')}
label={__('Return home.')}
/>
</p>
</section>
) : (
innerContent

View file

@ -2,10 +2,12 @@
import React from 'react';
import Page from 'component/page';
import CategoryList from 'component/categoryList';
import FirstRun from 'component/firstRun';
type Props = {
fetchFeaturedUris: () => void,
fetchRewardedContent: () => void,
fetchRewards: () => void,
fetchingFeaturedUris: boolean,
featuredUris: {},
};
@ -59,6 +61,7 @@ class DiscoverPage extends React.PureComponent<Props> {
const failedToLoad = !fetchingFeaturedUris && !hasContent;
return (
<Page noPadding isLoading={!hasContent && fetchingFeaturedUris}>
<FirstRun />
{hasContent &&
Object.keys(featuredUris).map(
category =>

View file

@ -10,24 +10,34 @@ function getLocalStorageSetting(setting, fallback) {
const reducers = {};
const defaultState = {
clientSettings: {
instantPurchaseEnabled: getLocalStorageSetting(SETTINGS.INSTANT_PURCHASE_ENABLED, false),
instantPurchaseMax: getLocalStorageSetting(SETTINGS.INSTANT_PURCHASE_MAX, {
[SETTINGS.INSTANT_PURCHASE_ENABLED]: getLocalStorageSetting(
SETTINGS.INSTANT_PURCHASE_ENABLED,
false
),
[SETTINGS.INSTANT_PURCHASE_MAX]: getLocalStorageSetting(SETTINGS.INSTANT_PURCHASE_MAX, {
currency: 'LBC',
amount: 0.1,
}),
showNsfw: getLocalStorageSetting(SETTINGS.SHOW_NSFW, false),
showUnavailable: getLocalStorageSetting(SETTINGS.SHOW_UNAVAILABLE, true),
welcome_acknowledged: getLocalStorageSetting(SETTINGS.NEW_USER_ACKNOWLEDGED, false),
email_collection_acknowledged: getLocalStorageSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED),
credit_required_acknowledged: false, // this needs to be re-acknowledged every run
language: getLocalStorageSetting(SETTINGS.LANGUAGE, 'en'),
theme: getLocalStorageSetting(SETTINGS.THEME, 'light'),
themes: getLocalStorageSetting(SETTINGS.THEMES, []),
automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
resultCount: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
autoDownload: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
osNotificationsEnabled: Boolean(
[SETTINGS.SHOW_NSFW]: getLocalStorageSetting(SETTINGS.SHOW_NSFW, false),
[SETTINGS.SHOW_UNAVAILABLE]: getLocalStorageSetting(SETTINGS.SHOW_UNAVAILABLE, true),
[SETTINGS.NEW_USER_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.NEW_USER_ACKNOWLEDGED, false),
[SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED]: getLocalStorageSetting(
SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED,
false
),
[SETTINGS.FIRST_RUN_COMPLETED]: getLocalStorageSetting(SETTINGS.FIRST_RUN_COMPLETED, false),
[SETTINGS.CREDIT_REQUIRED_ACKNOWLEDGED]: false, // this needs to be re-acknowledged every run
[SETTINGS.LANGUAGE]: getLocalStorageSetting(SETTINGS.LANGUAGE, 'en'),
[SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'dark'),
[SETTINGS.THEMES]: getLocalStorageSetting(SETTINGS.THEMES, []),
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: getLocalStorageSetting(
SETTINGS.AUTOMATIC_DARK_MODE_ENABLED,
false
),
[SETTINGS.AUTOPLAY]: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
[SETTINGS.RESULT_COUNT]: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
[SETTINGS.AUTO_DOWNLOAD]: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
[SETTINGS.OS_NOTIFICATIONS_ENABLED]: Boolean(
getLocalStorageSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, true)
),
},

View file

@ -9,6 +9,7 @@
@import 'init/gui';
@import 'component/animation';
@import 'component/badge';
@import 'component/banner';
@import 'component/button';
@import 'component/card';
@import 'component/channel';
@ -45,3 +46,4 @@
@import 'component/toggle';
@import 'component/tooltip';
@import 'component/wunderbar';
@import 'component/yrbl';

View file

@ -21,6 +21,7 @@
.badge--cost:not(.badge--large) {
background-color: $lbry-yellow-2;
color: $lbry-black;
html[data-theme='dark'] & {
background-color: $lbry-yellow-3;

View file

@ -0,0 +1,30 @@
.banner {
display: flex;
overflow: hidden;
background-color: $lbry-black;
color: $lbry-white;
}
.banner--first-run {
height: 310px;
// Adjust this class inside other `.banner--xxx` styles for control over animation
.banner__item--static-for-animation {
height: 310px;
display: flex;
flex-direction: column;
justify-content: center;
}
}
.banner__item {
&:not(:first-child) {
margin-left: var(--spacing-vertical-large);
}
}
.banner__content {
display: flex;
align-items: center;
height: 100%;
}

View file

@ -81,7 +81,7 @@
// C O N T E N T
.card__content {
font-size: 1.15rem;
font-size: 1.25rem;
p:not(:last-child) {
margin-bottom: var(--spacing-vertical-medium);
@ -174,9 +174,13 @@
// S U B T I T L E
.card__subtitle {
font-size: 1.25rem;
font-size: 1.15rem;
margin-bottom: var(--spacing-vertical-small);
p {
margin-bottom: var(--spacing-vertical-small);
}
.badge {
bottom: -0.12rem;
position: relative;

View file

@ -613,10 +613,10 @@
.media-group__header-navigation {
display: flex;
padding-right: var(--spacing-vertical-medium);
padding-right: var(--spacing-vertical-large);
button:not(:last-of-type) {
margin-right: var(--spacing-vertical-tiny);
margin-right: var(--spacing-vertical-medium);
}
svg {

View file

@ -0,0 +1,10 @@
.yrbl {
height: 300px;
}
.yrbl--first-run {
align-self: center;
height: 200px;
width: auto;
margin: 0 var(--spacing-vertical-large);
}

View file

@ -150,7 +150,6 @@ input {
.card {
.help {
margin-bottom: 0;
margin-top: var(--spacing-vertical-large);
}
}
@ -160,6 +159,7 @@ input {
color: $lbry-gray-5;
display: block;
padding: 1rem;
margin-top: var(--spacing-vertical-large);
margin-bottom: var(--spacing-vertical-large);
html[data-theme='dark'] & {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 265 KiB

View file

@ -101,6 +101,16 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@emotion/is-prop-valid@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz#a6bf4fa5387cbba59d44e698a4680f481a8da6cc"
dependencies:
"@emotion/memoize" "0.7.1"
"@emotion/memoize@0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
"@lbry/color@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@lbry/color/-/color-1.0.3.tgz#ec22b2c48b0e358759528fb3bbe7ba468d4e41ca"
@ -128,12 +138,33 @@
node-fetch "^2.1.1"
url-template "^2.0.8"
"@popmotion/easing@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@popmotion/easing/-/easing-1.0.1.tgz#48336aea29542113df10aeb81b4fd10f0b95f937"
"@popmotion/popcorn@^0.3.0":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@popmotion/popcorn/-/popcorn-0.3.1.tgz#f0f33fbf7ff66f2cd0bf28be24bee0b1d46b65e8"
dependencies:
"@popmotion/easing" "^1.0.1"
framesync "^4.0.1"
hey-listen "^1.0.5"
style-value-types "^3.0.7"
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
dependencies:
any-observable "^0.3.0"
"@types/invariant@^2.2.29":
version "2.2.29"
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.29.tgz#aa845204cd0a289f65d47e0de63a6a815e30cc66"
"@types/node@^10.0.5":
version "10.12.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
"@types/node@^8.0.24":
version "8.10.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285"
@ -4222,6 +4253,12 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
framesync@^4.0.0, framesync@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.0.1.tgz#ed7791baf0d266f58ab02000456f82cb384815bf"
dependencies:
hey-listen "^1.0.5"
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@ -4667,6 +4704,10 @@ he@1.1.x:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
hey-listen@^1.0.4, hey-listen@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.5.tgz#6d0a3a2f60177f65bc4404d571a00025bf5dc20e"
highlight.js@^9.3.0:
version "9.12.0"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
@ -7322,6 +7363,29 @@ pluralize@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
popmotion-pose@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/popmotion-pose/-/popmotion-pose-3.4.1.tgz#e13e52ba9fe02051926ca6313a7615876f459990"
dependencies:
"@popmotion/easing" "^1.0.1"
hey-listen "^1.0.5"
popmotion "^8.5.0"
pose-core "^2.0.0"
style-value-types "^3.0.6"
tslib "^1.9.1"
popmotion@^8.5.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-8.6.0.tgz#14edf50376fc2d656b59e0d46390896577818abb"
dependencies:
"@popmotion/easing" "^1.0.1"
"@popmotion/popcorn" "^0.3.0"
framesync "^4.0.0"
hey-listen "^1.0.5"
style-value-types "^3.0.6"
stylefire "^2.3.4"
tslib "^1.9.1"
portfinder@^1.0.9:
version "1.0.13"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
@ -7330,6 +7394,15 @@ portfinder@^1.0.9:
debug "^2.2.0"
mkdirp "0.5.x"
pose-core@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/pose-core/-/pose-core-2.0.2.tgz#6a0bd1e7218e4bf30be9c26a30be8eeb5b8695df"
dependencies:
"@types/invariant" "^2.2.29"
"@types/node" "^10.0.5"
hey-listen "^1.0.5"
tslib "^1.9.1"
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@ -7907,6 +7980,15 @@ react-paginate@^5.2.1:
dependencies:
prop-types "^15.6.1"
react-pose@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.5.tgz#056309b62e32ce0190f90ef35d7ade1b4c463d85"
dependencies:
"@emotion/is-prop-valid" "^0.7.3"
hey-listen "^1.0.5"
popmotion-pose "^3.4.0"
tslib "^1.9.1"
react-redux@^5.0.3:
version "5.0.7"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
@ -9260,6 +9342,18 @@ style-loader@^0.20.2:
loader-utils "^1.1.0"
schema-utils "^0.4.5"
style-value-types@^3.0.6, style-value-types@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-3.0.7.tgz#6e7a22cc8b1a4465193268ed66ad5f2a82579054"
stylefire@^2.3.4:
version "2.3.7"
resolved "https://registry.yarnpkg.com/stylefire/-/stylefire-2.3.7.tgz#285b86a48f6c25bbdcccb5dafa26b9f578235bb5"
dependencies:
framesync "^4.0.0"
hey-listen "^1.0.4"
style-value-types "^3.0.6"
sumchecker@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-1.3.1.tgz#79bb3b4456dd04f18ebdbc0d703a1d1daec5105d"
@ -9592,7 +9686,7 @@ truncate-utf8-bytes@^1.0.0:
dependencies:
utf8-byte-length "^1.0.1"
tslib@^1.9.0:
tslib@^1.9.0, tslib@^1.9.1:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"