mobile view

This commit is contained in:
Sean Yesmunt 2019-12-18 00:27:08 -05:00
parent 3709ec8fc6
commit 86cfa746de
42 changed files with 891 additions and 453 deletions

View file

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="ui.js" async></script> <script src="ui.js" async></script>
<link rel="stylesheet" href="font/font.css" /> <link rel="stylesheet" href="font/font.css" />
<title>LBRY</title> <title>LBRY</title>

View file

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="/public/ui.js" async></script> <script src="/public/ui.js" async></script>
<link rel="icon" type="image/png" href="/public/favicon.png" /> <link rel="icon" type="image/png" href="/public/favicon.png" />

View file

@ -202,36 +202,30 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
) : ( ) : (
<CardMedia thumbnail={thumbnail} /> <CardMedia thumbnail={thumbnail} />
)} )}
<div className="claim-preview-metadata"> <div className="claim-preview__text">
<div className="claim-preview-info"> <div className="claim-preview-metadata">
<div className="claim-preview-title"> <div className="claim-preview-info">
{claim ? <TruncatedText text={title || claim.name} lines={1} /> : <span>{__('Nothing here')}</span>} <div className="claim-preview-title">
{claim ? <TruncatedText text={title || claim.name} lines={2} /> : <span>{__('Nothing here')}</span>}
</div>
{!isChannel && claim && <FileProperties uri={uri} />}
</div> </div>
{!pending && (
<React.Fragment>
{hideActions ? null : actions !== undefined ? (
actions
) : (
<div className="card__actions--inline">
{isChannel && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{!hideBlock && isChannel && !isSubscribed && !claimIsMine && (
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{!isChannel && claim && <FileProperties uri={uri} />}
</div>
)}
</React.Fragment>
)}
</div>
<div className="claim-preview-properties">
<div className="media__subtitle"> <div className="media__subtitle">
{!isResolvingUri && ( {!isResolvingUri && (
<div> <div>
{claim ? ( {claim ? (
<UriIndicator uri={uri} link addTooltip={includeChannelTooltip} /> <React.Fragment>
<UriIndicator uri={uri} link addTooltip={includeChannelTooltip} />{' '}
{pending
? __('Pending...')
: claim &&
(isChannel ? (
type !== 'inline' && `${claimsInChannel} ${__('publishes')}`
) : (
<DateTime timeAgo uri={uri} />
))}
</React.Fragment>
) : ( ) : (
<Fragment> <Fragment>
<div>{__('Publish something and claim this spot!')}</div> <div>{__('Publish something and claim this spot!')}</div>
@ -244,21 +238,27 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
</div> </div>
</Fragment> </Fragment>
)} )}
<div>
{pending ? (
<div>{__('Pending...')}</div>
) : (
claim &&
(isChannel ? (
type !== 'inline' && `${claimsInChannel} ${__('publishes')}`
) : (
<DateTime timeAgo uri={uri} />
))
)}
</div>
</div> </div>
)} )}
</div> </div>
</div>
<div className="claim-preview__actions">
{!pending && (
<React.Fragment>
{hideActions ? null : actions !== undefined ? (
actions
) : (
<div className="card__actions--inline">
{isChannel && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
{!hideBlock && isChannel && !isSubscribed && !claimIsMine && (
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
)}
</div>
)}
</React.Fragment>
)}
{properties !== undefined ? properties : <ClaimTags uri={uri} type={type} />} {properties !== undefined ? properties : <ClaimTags uri={uri} type={type} />}
</div> </div>
</div> </div>

View file

@ -322,4 +322,11 @@ export const icons = {
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" /> <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" />
</g> </g>
), ),
[ICONS.MENU]: buildIcon(
<g>
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="18" x2="21" y2="18" />
</g>
),
}; };

View file

@ -12,6 +12,7 @@ import { FILE_WRAPPER_CLASS } from 'page/file/view';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
import Tooltip from 'component/common/tooltip'; import Tooltip from 'component/common/tooltip';
import { onFullscreenChange } from 'util/full-screen'; import { onFullscreenChange } from 'util/full-screen';
import useIsMobile from 'effects/use-is-mobile';
type Props = { type Props = {
mediaType: string, mediaType: string,
@ -50,6 +51,7 @@ export default function FileViewer(props: Props) {
mediaType, mediaType,
contentType, contentType,
} = props; } = props;
const isMobile = useIsMobile();
const [playTime, setPlayTime] = useState(); const [playTime, setPlayTime] = useState();
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect'); const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
const [position, setPosition] = usePersistedState('floating-file-viewer:position', { const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
@ -125,7 +127,7 @@ export default function FileViewer(props: Props) {
} }
const hidePlayer = const hidePlayer =
!isPlaying || !uri || (!inline && (!floatingPlayerEnabled || !['audio', 'video'].includes(mediaType))); !isPlaying || !uri || (!inline && (isMobile || !floatingPlayerEnabled || !['audio', 'video'].includes(mediaType)));
if (hidePlayer) { if (hidePlayer) {
return null; return null;

View file

@ -1,9 +1,10 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectBalance, formatCredits } from 'lbry-redux'; import { selectBalance, formatCredits } from 'lbry-redux';
import { selectUserVerifiedEmail, selectGetSyncErrorMessage } from 'lbryinc'; import { selectUserVerifiedEmail, selectGetSyncErrorMessage } from 'lbryinc';
import { doSetClientSetting } from 'redux/actions/settings'; import { doSetClientSetting } from 'redux/actions/settings';
import { doSignOut } from 'redux/actions/app'; import { doSignOut, doOpenModal } from 'redux/actions/app';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import Header from './view'; import Header from './view';
@ -21,6 +22,7 @@ const select = state => ({
const perform = dispatch => ({ const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
signOut: () => dispatch(doSignOut()), signOut: () => dispatch(doSignOut()),
openMobileNavigation: () => dispatch(doOpenModal(MODALS.MOBILE_NAVIGATION)),
}); });
export default connect( export default connect(

View file

@ -27,6 +27,7 @@ type Props = {
authHeader: boolean, authHeader: boolean,
syncError: ?string, syncError: ?string,
signOut: () => void, signOut: () => void,
openMobileNavigation: () => void,
}; };
const Header = (props: Props) => { const Header = (props: Props) => {
@ -41,6 +42,7 @@ const Header = (props: Props) => {
authHeader, authHeader,
signOut, signOut,
syncError, syncError,
openMobileNavigation,
} = props; } = props;
const authenticated = Boolean(email); const authenticated = Boolean(email);
@ -201,6 +203,16 @@ const Header = (props: Props) => {
</div> </div>
) )
)} )}
{!authenticated && (
<Button
button="primary"
label={__('Sign In')}
className="header__menu--mobile"
onClick={() => history.push(`/$/${PAGES.AUTH}`)}
/>
)}
<Button onClick={openMobileNavigation} icon={ICONS.MENU} iconSize={24} className="header__menu--mobile" />
</div> </div>
</header> </header>
); );

View file

@ -53,36 +53,38 @@ class InviteList extends React.PureComponent<Props> {
</div> </div>
</div> </div>
<table className="table section"> <div className="table__wrapper">
<thead> <table className="table section">
<tr> <thead>
<th>{__('Invitee Email')}</th> <tr>
<th>{__('Invite Status')}</th> <th>{__('Invitee Email')}</th>
<th>{__('Reward')}</th> <th>{__('Invite Status')}</th>
</tr> <th>{__('Reward')}</th>
</thead>
<tbody>
{invitees.map(invitee => (
<tr key={invitee.email}>
<td>{invitee.email}</td>
<td>
<span>{invitee.invite_accepted ? __('Accepted') : __('Not Accepted')}</span>
</td>
<td>
{invitee.invite_reward_claimed && (
<React.Fragment>
<span>{__('Claimed')}</span>
<Icon icon={ICONS.COMPLETE} />
</React.Fragment>
)}
{!invitee.invite_reward_claimed &&
(invitee.invite_reward_claimable ? <span>{__('Claimable')}</span> : __('Unclaimable'))}
</td>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {invitees.map(invitee => (
<tr key={invitee.email}>
<td>{invitee.email}</td>
<td>
<span>{invitee.invite_accepted ? __('Accepted') : __('Not Accepted')}</span>
</td>
<td>
{invitee.invite_reward_claimed && (
<React.Fragment>
<span>{__('Claimed')}</span>
<Icon icon={ICONS.COMPLETE} />
</React.Fragment>
)}
{!invitee.invite_reward_claimed &&
(invitee.invite_reward_claimable ? <span>{__('Claimable')}</span> : __('Unclaimable'))}
</td>
</tr>
))}
</tbody>
</table>
</div>
</section> </section>
); );
} }

View file

@ -2,7 +2,7 @@
import type { Node } from 'react'; import type { Node } from 'react';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import SideBar from 'component/sideBar'; import SideNavigation from 'component/sideNavigation';
import Header from 'component/header'; import Header from 'component/header';
export const MAIN_CLASS = 'main'; export const MAIN_CLASS = 'main';
@ -24,7 +24,7 @@ function Page(props: Props) {
<Header authHeader={authPage} /> <Header authHeader={authPage} />
<div className={classnames('main-wrapper__inner')}> <div className={classnames('main-wrapper__inner')}>
<main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main> <main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main>
{!authPage && <SideBar obscureSideBar={obscureSideBar} />} {!authPage && <SideNavigation obscureSideBar={obscureSideBar} />}
</div> </div>
</Fragment> </Fragment>
); );

View file

@ -37,26 +37,28 @@ const RewardListClaimed = (props: Props) => {
</div> </div>
</header> </header>
<table className="table table--rewards"> <div className="table__wrapper">
<thead> <table className="table table--rewards">
<tr> <thead>
<th>{__('Title')}</th> <tr>
<th>{__('Amount')}</th> <th>{__('Title')}</th>
<th>{__('Transaction')}</th> <th>{__('Amount')}</th>
<th>{__('Date')}</th> <th>{__('Transaction')}</th>
</tr> <th>{__('Date')}</th>
</thead>
<tbody>
{rewards.reverse().map(reward => (
<tr key={reward.id}>
<td>{reward.reward_title}</td>
<td>{reward.reward_amount}</td>
<td>{reward.transaction_id && <ButtonTransaction id={reward.transaction_id} />}</td>
<td>{moment(reward.created_at).format('LLL')}</td>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {rewards.reverse().map(reward => (
<tr key={reward.id}>
<td>{reward.reward_title}</td>
<td>{reward.reward_amount}</td>
<td>{reward.transaction_id && <ButtonTransaction id={reward.transaction_id} />}</td>
<td>{moment(reward.created_at).format('LLL')}</td>
</tr>
))}
</tbody>
</table>
</div>
</section> </section>
); );
}; };

View file

@ -2,21 +2,22 @@ import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectFollowedTags } from 'lbry-redux'; import { selectFollowedTags } from 'lbry-redux';
import { selectUserEmail, selectUploadCount } from 'lbryinc'; import { selectUploadCount, selectUserVerifiedEmail } from 'lbryinc';
import SideBar from './view';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSignOut } from 'redux/actions/app';
import SideNavigation from './view';
const select = state => ({ const select = state => ({
subscriptions: selectSubscriptions(state), subscriptions: selectSubscriptions(state),
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change
email: selectUserEmail(state),
uploadCount: selectUploadCount(state), uploadCount: selectUploadCount(state),
email: selectUserVerifiedEmail(state),
}); });
const perform = () => ({});
export default connect( export default connect(
select, select,
perform {
)(SideBar); doSignOut,
}
)(SideNavigation);

View file

@ -13,30 +13,53 @@ type Props = {
email: ?string, email: ?string,
obscureSideBar: boolean, obscureSideBar: boolean,
uploadCount: number, uploadCount: number,
sticky: boolean,
showAllLinks: boolean,
doSignOut: () => void,
}; };
function SideBar(props: Props) { function SideBar(props: Props) {
const { subscriptions, followedTags, obscureSideBar, uploadCount } = props; const {
function buildLink(path, label, icon, guide) { subscriptions,
followedTags,
obscureSideBar,
uploadCount,
doSignOut,
email,
sticky = true,
showAllLinks = false,
} = props;
const isAuthenticated = Boolean(email);
function buildLink(path, label, icon, onClick) {
return { return {
navigate: path ? `$/${path}` : '/', navigate: path ? `$/${path}` : '/',
label, label,
icon, icon,
guide, onClick,
}; };
} }
const Wrapper = ({ children }: any) =>
sticky ? (
<StickyBox offsetTop={100} offsetBottom={20}>
{children}
</StickyBox>
) : (
<div>{children}</div>
);
return obscureSideBar ? ( return obscureSideBar ? (
<StickyBox offsetTop={100} offsetBottom={20}> <Wrapper>
<div className="card navigation--placeholder"> <div className="card navigation--placeholder">
<div className="wrap"> <div className="wrap">
<h2>LBRY</h2> <h2>LBRY</h2>
<p>{__('The best decentralized content platform on the web.')}</p> <p>{__('The best decentralized content platform on the web.')}</p>
</div> </div>
</div> </div>
</StickyBox> </Wrapper>
) : ( ) : (
<StickyBox offsetTop={100} offsetBottom={20}> <Wrapper>
<nav className="navigation"> <nav className="navigation">
<ul className="navigation-links"> <ul className="navigation-links">
{[ {[
@ -70,15 +93,47 @@ function SideBar(props: Props) {
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" /> <Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
</li> </li>
))} ))}
{showAllLinks &&
[
{
...buildLink(PAGES.WALLET, __('Wallet'), ICONS.WALLET),
},
{
...buildLink(PAGES.REWARDS, __('Rewards'), ICONS.FEATURED),
},
{
...buildLink(PAGES.ACCOUNT, __('Overview'), ICONS.OVERVIEW),
},
{
...buildLink(PAGES.PUBLISH, __('Publish'), ICONS.PUBLISH),
},
{
...buildLink(PAGES.SETTINGS, __('Settings'), ICONS.SETTINGS),
},
{
...buildLink(PAGES.HELP, __('Help'), ICONS.HELP),
},
{
...(isAuthenticated ? { ...buildLink(PAGES.AUTH, __('Sign Out'), ICONS.SIGN_OUT, doSignOut) } : {}),
},
].map(linkProps => (
<li key={linkProps.navigate}>
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
</li>
))}
<li>
<Button
navigate={`/$/${PAGES.FOLLOWING}`}
label={__('Customize')}
icon={ICONS.EDIT}
className="navigation-link"
activeClass="navigation-link--active"
/>
</li>
</ul> </ul>
<Button
navigate={`/$/${PAGES.FOLLOWING}`}
label={__('Customize')}
icon={ICONS.EDIT}
className="navigation-link"
activeClass="navigation-link--active"
/>
<section className="navigation-links__inline"> <section className="navigation-links__inline">
<ul className="navigation-links--small tags--vertical"> <ul className="navigation-links--small tags--vertical">
{followedTags.map(({ name }, key) => ( {followedTags.map(({ name }, key) => (
@ -101,7 +156,7 @@ function SideBar(props: Props) {
</ul> </ul>
</section> </section>
</nav> </nav>
</StickyBox> </Wrapper>
); );
} }

View file

@ -32,7 +32,7 @@ function TransactionListTable(props: Props) {
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2> <h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
)} )}
{!!transactionList.length && ( {!!transactionList.length && (
<React.Fragment> <div className="table__wrapper">
<table className="table table--transactions"> <table className="table table--transactions">
<thead> <thead>
<tr> <tr>
@ -55,7 +55,7 @@ function TransactionListTable(props: Props) {
))} ))}
</tbody> </tbody>
</table> </table>
</React.Fragment> </div>
)} )}
</React.Fragment> </React.Fragment>
); );

View file

@ -33,3 +33,4 @@ export const WALLET_SEND = 'wallet_send';
export const WALLET_RECEIVE = 'wallet_receive'; export const WALLET_RECEIVE = 'wallet_receive';
export const CREATE_CHANNEL = 'create_channel'; export const CREATE_CHANNEL = 'create_channel';
export const YOUTUBE_WELCOME = 'youtube_welcome'; export const YOUTUBE_WELCOME = 'youtube_welcome';
export const MOBILE_NAVIGATION = 'mobile_navigation';

View file

@ -0,0 +1,6 @@
import useMedia from './use-media';
export default function useIsMobile() {
const isMobile = useMedia(['(min-width: 901px)'], [false], true);
return isMobile;
}

34
ui/effects/use-media.js Normal file
View file

@ -0,0 +1,34 @@
import { useState, useEffect } from 'react';
// https://usehooks.com/useMedia/
export default function useMedia(queries, values, defaultValue) {
// Array containing a media query list for each query
const mediaQueryLists = queries.map(q => window.matchMedia(q));
// Function that gets value based on matching media query
const getValue = () => {
// Get index of first media query that matches
const index = mediaQueryLists.findIndex(mql => mql.matches);
// Return related value or defaultValue if none
return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
};
// State and setter for matched value
const [value, setValue] = useState(getValue);
useEffect(
() => {
// Event listener callback
// Note: By defining getValue outside of useEffect we ensure that it has ...
// ... current values of hook args (as this hook callback is created once on mount).
const handler = () => setValue(getValue);
// Set a listener for each media query with above handler as callback.
mediaQueryLists.forEach(mql => mql.addListener(handler));
// Remove listeners on cleanup
return () => mediaQueryLists.forEach(mql => mql.removeListener(handler));
},
[] // Empty array ensures effect is only run on mount and unmount
);
return value;
}

View file

@ -57,7 +57,7 @@ export class Modal extends React.PureComponent<ModalProps> {
overlayClassName="modal-overlay" overlayClassName="modal-overlay"
> >
{title && <h1 className="card__title card__title--deprecated">{title}</h1>} {title && <h1 className="card__title card__title--deprecated">{title}</h1>}
{type === 'card' && <Button button="close" icon={ICONS.REMOVE} onClick={onAborted} />} {type === 'card' && <Button iconSize={24} button="close" icon={ICONS.REMOVE} onClick={onAborted} />}
{children} {children}
{type === 'custom' || type === 'card' ? null : ( // custom modals define their own buttons {type === 'custom' || type === 'card' ? null : ( // custom modals define their own buttons
<div className="card__actions"> <div className="card__actions">

View file

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import { doHideModal, doSignOut } from 'redux/actions/app';
import ModalMobileNavigation from './view';
export default connect(
null,
{
doHideModal,
doSignOut,
}
)(ModalMobileNavigation);

View file

@ -0,0 +1,18 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import SideNavigation from 'component/sideNavigation';
type Props = {
doHideModal: () => void,
};
export default function ModalMobileNavigation(props: Props) {
const { doHideModal } = props;
return (
<Modal type="card" isOpen contentLabel={__('Navigation')} onAborted={doHideModal}>
<SideNavigation sticky={false} showAllLinks />
</Modal>
);
}

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectModal } from 'redux/selectors/app'; import { selectModal } from 'redux/selectors/app';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal, doHideModal } from 'redux/actions/app';
import { selectError } from 'lbry-redux'; import { selectError } from 'lbry-redux';
import ModalRouter from './view'; import ModalRouter from './view';
@ -11,6 +11,7 @@ const select = (state, props) => ({
const perform = dispatch => ({ const perform = dispatch => ({
openModal: props => dispatch(doOpenModal(props)), openModal: props => dispatch(doOpenModal(props)),
hideModal: props => dispatch(doHideModal(props)),
}); });
export default connect( export default connect(

View file

@ -1,5 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { withRouter } from 'react-router';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import ModalError from 'modal/modalError'; import ModalError from 'modal/modalError';
import ModalDownloading from 'modal/modalDownloading'; import ModalDownloading from 'modal/modalDownloading';
@ -31,14 +32,22 @@ import ModalWalletSend from 'modal/modalWalletSend';
import ModalWalletReceive from 'modal/modalWalletReceive'; import ModalWalletReceive from 'modal/modalWalletReceive';
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome'; import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
import ModalCreateChannel from 'modal/modalChannelCreate'; import ModalCreateChannel from 'modal/modalChannelCreate';
import ModalMobileNavigation from 'modal/modalMobileNavigation';
type Props = { type Props = {
modal: { id: string, modalProps: {} }, modal: { id: string, modalProps: {} },
error: { message: string }, error: { message: string },
location: { pathname: string },
hideModal: () => void,
}; };
function ModalRouter(props: Props) { function ModalRouter(props: Props) {
const { modal, error } = props; const { modal, error, location, hideModal } = props;
const { pathname } = location;
React.useEffect(() => {
hideModal();
}, [pathname, hideModal]);
if (error) { if (error) {
return <ModalError {...error} />; return <ModalError {...error} />;
@ -111,9 +120,11 @@ function ModalRouter(props: Props) {
return <ModalYoutubeWelcome />; return <ModalYoutubeWelcome />;
case MODALS.CREATE_CHANNEL: case MODALS.CREATE_CHANNEL:
return <ModalCreateChannel {...modalProps} />; return <ModalCreateChannel {...modalProps} />;
case MODALS.MOBILE_NAVIGATION:
return <ModalMobileNavigation {...modalProps} />;
default: default:
return null; return null;
} }
} }
export default ModalRouter; export default withRouter(ModalRouter);

View file

@ -210,66 +210,68 @@ class HelpPage extends React.PureComponent<Props, State> {
</div> </div>
</header> </header>
<table className="table table--stretch"> <div className="table__wrapper">
<tbody> <table className="table table--stretch">
<tr> <tbody>
<td>{__('App')}</td> <tr>
<td>{this.state.uiVersion}</td> <td>{__('App')}</td>
</tr> <td>{this.state.uiVersion}</td>
<tr> </tr>
<td>{__('Daemon (lbrynet)')}</td> <tr>
<td>{ver ? ver.lbrynet_version : __('Loading...')}</td> <td>{__('Daemon (lbrynet)')}</td>
</tr> <td>{ver ? ver.lbrynet_version : __('Loading...')}</td>
<tr> </tr>
<td>{__('Connected Email')}</td> <tr>
<td> <td>{__('Connected Email')}</td>
{user && user.primary_email ? ( <td>
<React.Fragment> {user && user.primary_email ? (
{user.primary_email}{' '} <React.Fragment>
<Button {user.primary_email}{' '}
button="link" <Button
href={`https://lbry.com/list/edit/${accessToken}`} button="link"
label={__('Update mailing preferences')} href={`https://lbry.com/list/edit/${accessToken}`}
/> label={__('Update mailing preferences')}
</React.Fragment> />
) : ( </React.Fragment>
<React.Fragment> ) : (
<span className="empty">{__('none')} </span> <React.Fragment>
<Button button="link" onClick={() => doAuth()} label={__('set email')} /> <span className="empty">{__('none')} </span>
</React.Fragment> <Button button="link" onClick={() => doAuth()} label={__('set email')} />
)} </React.Fragment>
</td> )}
</tr> </td>
<tr> </tr>
<td>{__('Reward Eligible')}</td> <tr>
<td>{user && user.is_reward_approved ? __('Yes') : __('No')}</td> <td>{__('Reward Eligible')}</td>
</tr> <td>{user && user.is_reward_approved ? __('Yes') : __('No')}</td>
<tr> </tr>
<td>{__('Platform')}</td> <tr>
<td>{platform}</td> <td>{__('Platform')}</td>
</tr> <td>{platform}</td>
<tr> </tr>
<td>{__('Installation ID')}</td> <tr>
<td>{this.state.lbryId}</td> <td>{__('Installation ID')}</td>
</tr> <td>{this.state.lbryId}</td>
<tr> </tr>
<td>{__('Access Token')}</td> <tr>
<td> <td>{__('Access Token')}</td>
{this.state.accessTokenHidden && ( <td>
<Button button="link" label={__('View')} onClick={this.showAccessToken} /> {this.state.accessTokenHidden && (
)} <Button button="link" label={__('View')} onClick={this.showAccessToken} />
{!this.state.accessTokenHidden && accessToken && ( )}
<div> {!this.state.accessTokenHidden && accessToken && (
<p>{accessToken}</p> <div>
<div className="help--warning"> <p>{accessToken}</p>
{__('This is equivalent to a password. Do not post or share this.')} <div className="help--warning">
{__('This is equivalent to a password. Do not post or share this.')}
</div>
</div> </div>
</div> )}
)} </td>
</td> </tr>
</tr> </tbody>
</tbody> </table>
</table> </div>
</section> </section>
</Page> </Page>
); );

View file

@ -32,6 +32,10 @@
color: var(--color-button-primary-text); color: var(--color-button-primary-text);
background-color: var(--color-button-primary-bg); background-color: var(--color-button-primary-bg);
} }
@media (max-width: $breakpoint-small) {
padding: var(--spacing-medium) var(--spacing-small);
}
} }
.button--download-link { .button--download-link {

View file

@ -5,6 +5,10 @@
border-radius: var(--card-radius); border-radius: var(--card-radius);
box-shadow: var(--card-box-shadow) var(--color-box-shadow); box-shadow: var(--card-box-shadow) var(--color-box-shadow);
overflow: hidden; overflow: hidden;
@media (max-width: $breakpoint-small) {
margin-bottom: var(--spacing-medium);
}
} }
.card--disabled { .card--disabled {
@ -72,12 +76,19 @@
.card__list { .card__list {
column-count: 2; column-count: 2;
column-gap: var(--spacing-medium); column-gap: var(--spacing-large);
display: block; display: block;
.card { .card {
display: inline-block; display: inline-block;
margin: 0 0 var(--spacing-medium); margin: 0 0 var(--spacing-large);
}
@media (max-width: $breakpoint-small) {
column-count: 1;
.card {
display: block;
}
} }
} }

View file

@ -2,6 +2,7 @@ $cover-z-index: 0;
$metadata-z-index: 1; $metadata-z-index: 1;
.channel-cover { .channel-cover {
position: relative;
background-image: linear-gradient(to right, #637ad2, #318794 80%); background-image: linear-gradient(to right, #637ad2, #318794 80%);
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
@ -49,6 +50,10 @@ $metadata-z-index: 1;
box-shadow: 0px 8px 40px -3px #000; box-shadow: 0px 8px 40px -3px #000;
left: var(--spacing-medium); left: var(--spacing-medium);
top: 4rem; top: 4rem;
@media (max-width: $breakpoint-small) {
display: none;
}
} }
.channel-thumbnail__custom { .channel-thumbnail__custom {
@ -99,6 +104,10 @@ $metadata-z-index: 1;
padding-bottom: var(--spacing-medium); padding-bottom: var(--spacing-medium);
min-width: 0; min-width: 0;
width: 100%; width: 100%;
@media (max-width: $breakpoint-small) {
padding-left: var(--spacing-medium);
}
} }
.channel__title { .channel__title {

View file

@ -34,6 +34,12 @@
margin-bottom: 0; margin-bottom: 0;
padding: 0 var(--spacing-medium); padding: 0 var(--spacing-medium);
padding-right: var(--spacing-large); padding-right: var(--spacing-large);
@media (max-width: $breakpoint-small) {
font-size: var(--font-small);
padding: 0 var(--spacing-small);
padding-right: var(--spacing-large);
}
} }
.claim-list__conjuction { .claim-list__conjuction {
@ -49,6 +55,10 @@
& > * { & > * {
margin-left: var(--spacing-small); margin-left: var(--spacing-small);
} }
@media (max-width: $breakpoint-small) {
display: none;
}
} }
.claim-preview { .claim-preview {
@ -67,6 +77,17 @@
flex-shrink: 0; flex-shrink: 0;
margin-right: var(--spacing-medium); margin-right: var(--spacing-medium);
} }
@media (max-width: $breakpoint-small) {
.media__thumb {
position: absolute;
z-index: 0;
opacity: 0.1;
right: 0;
top: var(--spacing-small);
bottom: var(--spacing-small);
}
}
} }
.claim-preview--large { .claim-preview--large {
@ -118,6 +139,27 @@
width: var(--channel-thumbnail-width--small); width: var(--channel-thumbnail-width--small);
height: var(--channel-thumbnail-width--small); height: var(--channel-thumbnail-width--small);
} }
.claim-preview__actions {
align-self: flex-end;
margin-top: 0;
margin-bottom: auto;
justify-content: flex-end;
width: auto;
}
.claim-preview__text {
width: 100%;
flex-direction: row;
justify-content: space-between;
}
}
.claim-preview__text {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
} }
.claim-preview-title { .claim-preview-title {
@ -145,10 +187,14 @@
justify-content: space-between; justify-content: space-between;
} }
.claim-preview-actions { .claim-preview__actions {
align-self: flex-end;
display: flex; display: flex;
justify-content: space-evenly; justify-content: space-between;
margin-top: auto;
width: 100%;
} }
.claim-preview__button { .claim-preview__button {
margin-left: 2rem; margin-left: 2rem;
} }

View file

@ -13,7 +13,7 @@
margin-right: var(--spacing-small); margin-right: var(--spacing-small);
} }
@media (max-width: 600px) { @media (max-width: $breakpoint-xsmall) {
display: none; display: none;
} }
} }

View file

@ -121,6 +121,11 @@ fieldset-group {
margin-bottom: var(--spacing-small); margin-bottom: var(--spacing-small);
} }
.form-field__help + .checkbox,
.form-field__help + .radio {
margin-top: var(--spacing-large);
}
.form-field__conjuction { .form-field__conjuction {
padding-top: 1rem; padding-top: 1rem;
} }

View file

@ -34,22 +34,19 @@
max-width: var(--page-max-width); max-width: var(--page-max-width);
height: calc(var(--header-height) - 1px); height: calc(var(--header-height) - 1px);
display: flex; display: flex;
align-items: center;
margin: auto; margin: auto;
padding: 0 var(--spacing-large); padding: 0 var(--spacing-large);
@media (max-width: $breakpoint-small) {
padding: var(--spacing-small);
}
} }
.header__navigation { .header__navigation {
flex: 1; flex: 1;
display: flex; display: flex;
align-items: center; align-items: center;
&:last-of-type {
width: var(--side-nav-width);
@media (max-width: 600px) {
display: none;
}
}
} }
.header__menu { .header__menu {
@ -61,6 +58,23 @@
> .button:only-child { > .button:only-child {
margin-left: auto; margin-left: auto;
} }
@media (max-width: $breakpoint-small) {
display: none;
}
}
.header__menu--mobile {
display: none;
@media (max-width: $breakpoint-small) {
display: block;
margin-left: var(--spacing-medium);
svg {
stroke: var(--color-text);
}
}
} }
.header__menu--with-balance { .header__menu--with-balance {
@ -111,17 +125,3 @@
width: 2rem; width: 2rem;
} }
} }
.header__navigation-item--right-action {
align-self: flex-end;
margin-left: auto;
padding: 0 var(--spacing-small);
}
@media (max-width: 600px) {
.header__navigation-item--back,
.header__navigation-item--forward,
.header__navigation-item--right-action {
display: none;
}
}

View file

@ -16,6 +16,10 @@
margin-right: auto; margin-right: auto;
margin-top: var(--header-height); margin-top: var(--header-height);
padding: var(--spacing-large); padding: var(--spacing-large);
@media (max-width: $breakpoint-small) {
padding: var(--spacing-small);
}
} }
.main { .main {
@ -23,7 +27,7 @@
width: calc(100% - var(--side-nav-width) - var(--spacing-large)); width: calc(100% - var(--side-nav-width) - var(--spacing-large));
margin-right: var(--spacing-main-padding); margin-right: var(--spacing-main-padding);
@media (max-width: 600px) { @media (max-width: $breakpoint-small) {
width: 100%; width: 100%;
margin-right: 0; margin-right: 0;
margin-left: 0; margin-left: 0;
@ -45,6 +49,14 @@
.grid-area--related { .grid-area--related {
width: calc(47.5% - var(--spacing-large)); width: calc(47.5% - var(--spacing-large));
} }
@media (max-width: $breakpoint-small) {
.grid-area--related,
.grid-area--info {
margin-right: 0;
width: 100%;
}
}
} }
.main--auth-page { .main--auth-page {

View file

@ -38,6 +38,11 @@
transform: translateY(-130%); transform: translateY(-130%);
font-size: var(--font-xsmall); font-size: var(--font-xsmall);
color: var(--color-text-subtitle); color: var(--color-text-subtitle);
@media (max-width: $breakpoint-small) {
position: static;
transform: none;
}
} }
.media__uri--inline { .media__uri--inline {

View file

@ -24,8 +24,25 @@
box-shadow: none; box-shadow: none;
} }
@media (min-width: 501px) { @media (max-width: $breakpoint-small) {
min-width: 500px; width: 100%;
height: 100%;
margin-bottom: 0;
border-radius: 0;
.navigation {
width: 100%;
display: block;
padding: var(--spacing-large);
.navigation-links:not(.navigation-links--small) {
.navigation-link {
font-size: var(--font-large);
border-bottom: 1px solid var(--color-gray-3);
padding: var(--spacing-medium) 0;
}
}
}
} }
} }

View file

@ -2,7 +2,7 @@
width: var(--side-nav-width); width: var(--side-nav-width);
font-size: var(--font-body); font-size: var(--font-body);
@media (max-width: 600px) { @media (max-width: $breakpoint-small) {
display: none; display: none;
} }
} }

View file

@ -1,3 +1,7 @@
.table__wrapper {
overflow-x: auto;
}
.table__header { .table__header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -17,6 +21,10 @@
@extend .table__header-text; @extend .table__header-text;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@media (max-width: $breakpoint-small) {
flex-wrap: wrap;
}
} }
.table--help { .table--help {

View file

@ -8,10 +8,6 @@
z-index: 1; z-index: 1;
font-size: var(--font-small); font-size: var(--font-small);
@media (max-width: 600px) {
margin-right: 0;
}
> .icon { > .icon {
top: 0; top: 0;
left: var(--spacing-small); left: var(--spacing-small);
@ -30,6 +26,10 @@
fieldset-section { fieldset-section {
width: 15rem; width: 15rem;
} }
@media (max-width: $breakpoint-small) {
display: none;
}
} }
.wunderbar__active-suggestion { .wunderbar__active-suggestion {

View file

@ -10,6 +10,10 @@
.yrbl { .yrbl {
height: 20rem; height: 20rem;
margin-right: var(--spacing-large); margin-right: var(--spacing-large);
@media (max-width: $breakpoint-small) {
height: 10rem;
}
} }
.yrbl__content { .yrbl__content {

View file

@ -55,6 +55,10 @@
display: inline-block; display: inline-block;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
font-size: var(--font-heading); font-size: var(--font-heading);
@media (max-width: $breakpoint-small) {
font-size: var(--font-title);
}
} }
.section__subtitle--status { .section__subtitle--status {
@ -87,3 +91,13 @@
.section__body { .section__body {
margin-top: var(--spacing-medium); margin-top: var(--spacing-medium);
} }
@media (max-width: $breakpoint-small) {
.section__actions {
flex-wrap: wrap;
.button {
margin-bottom: var(--spacing-small);
}
}
}

View file

@ -18,6 +18,10 @@
padding-left: calc(var(--channel-thumbnail-width) + var(--spacing-large)); padding-left: calc(var(--channel-thumbnail-width) + var(--spacing-large));
padding-right: var(--spacing-medium); padding-right: var(--spacing-medium);
height: 4rem; height: 4rem;
@media (max-width: $breakpoint-small) {
padding-left: var(--spacing-medium);
}
} }
.tab { .tab {

View file

@ -133,6 +133,19 @@ a {
margin-right: 1.5rem; margin-right: 1.5rem;
} }
} }
@media (max-width: $breakpoint-small) {
flex-direction: column;
& > * {
margin: 0;
width: 100%;
&:first-child {
margin-right: 0;
}
}
}
} }
.hidden { .hidden {

View file

@ -2,8 +2,8 @@
$spacing-vertical: 2rem; $spacing-vertical: 2rem;
$spacing-width: 36px; $spacing-width: 36px;
$medium-breakpoint: 1279px; $breakpoint-xsmall: 600px;
$large-breakpoint: 1921px; $breakpoint-small: 900px;
:root { :root {
// Width & spacing // Width & spacing
@ -62,3 +62,9 @@ $large-breakpoint: 1921px;
--tag-height: 1.5rem; --tag-height: 1.5rem;
} }
@media (max-width: $breakpoint-small) {
:root {
--font-base: 16px;
}
}

View file

@ -134,11 +134,11 @@ var Konami = function(callback) {
konami.removeEvent(document, 'touchstart', this.touchstartHandler); konami.removeEvent(document, 'touchstart', this.touchstartHandler);
}, },
check_direction: function() { check_direction: function() {
x_magnitude = Math.abs(this.start_x - this.stop_x); var x_magnitude = Math.abs(this.start_x - this.stop_x);
y_magnitude = Math.abs(this.start_y - this.stop_y); var y_magnitude = Math.abs(this.start_y - this.stop_y);
x = this.start_x - this.stop_x < 0 ? 'RIGHT' : 'LEFT'; var x = this.start_x - this.stop_x < 0 ? 'RIGHT' : 'LEFT';
y = this.start_y - this.stop_y < 0 ? 'DOWN' : 'UP'; var y = this.start_y - this.stop_y < 0 ? 'DOWN' : 'UP';
result = x_magnitude > y_magnitude ? x : y; var result = x_magnitude > y_magnitude ? x : y;
result = this.tap === true ? 'TAP' : result; result = this.tap === true ? 'TAP' : result;
return result; return result;
}, },

585
yarn.lock

File diff suppressed because it is too large Load diff