From 86cfa746de8da6f97994982bbc7285a68f03c350 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Wed, 18 Dec 2019 00:27:08 -0500 Subject: [PATCH] mobile view --- static/index-electron.html | 1 + static/index-web.html | 1 + ui/component/claimPreview/view.jsx | 72 +-- ui/component/common/icon-custom.jsx | 7 + ui/component/fileViewer/view.jsx | 4 +- ui/component/header/index.js | 4 +- ui/component/header/view.jsx | 12 + ui/component/inviteList/view.jsx | 60 +- ui/component/page/view.jsx | 4 +- ui/component/rewardListClaimed/view.jsx | 40 +- .../{sideBar => sideNavigation}/index.js | 15 +- .../{sideBar => sideNavigation}/view.jsx | 83 ++- ui/component/transactionListTable/view.jsx | 4 +- ui/constants/modal_types.js | 1 + ui/effects/use-is-mobile.js | 6 + ui/effects/use-media.js | 34 + ui/modal/modal.jsx | 2 +- ui/modal/modalMobileNavigation/index.js | 11 + ui/modal/modalMobileNavigation/view.jsx | 18 + ui/modal/modalRouter/index.js | 3 +- ui/modal/modalRouter/view.jsx | 15 +- ui/page/help/view.jsx | 120 ++-- ui/scss/component/_button.scss | 4 + ui/scss/component/_card.scss | 15 +- ui/scss/component/_channel.scss | 9 + ui/scss/component/_claim-list.scss | 50 +- ui/scss/component/_file-properties.scss | 2 +- ui/scss/component/_form-field.scss | 5 + ui/scss/component/_header.scss | 44 +- ui/scss/component/_main.scss | 14 +- ui/scss/component/_media.scss | 5 + ui/scss/component/_modal.scss | 21 +- ui/scss/component/_navigation.scss | 2 +- ui/scss/component/_table.scss | 8 + ui/scss/component/_wunderbar.scss | 8 +- ui/scss/component/_yrbl.scss | 4 + ui/scss/component/section.scss | 14 + ui/scss/component/tabs.scss | 4 + ui/scss/init/_gui.scss | 13 + ui/scss/init/_vars.scss | 10 +- ui/util/enhanced-layout.js | 10 +- yarn.lock | 585 +++++++++++------- 42 files changed, 891 insertions(+), 453 deletions(-) rename ui/component/{sideBar => sideNavigation}/index.js (70%) rename ui/component/{sideBar => sideNavigation}/view.jsx (58%) create mode 100644 ui/effects/use-is-mobile.js create mode 100644 ui/effects/use-media.js create mode 100644 ui/modal/modalMobileNavigation/index.js create mode 100644 ui/modal/modalMobileNavigation/view.jsx diff --git a/static/index-electron.html b/static/index-electron.html index 3596902d8..b5d3a5736 100644 --- a/static/index-electron.html +++ b/static/index-electron.html @@ -2,6 +2,7 @@ + LBRY diff --git a/static/index-web.html b/static/index-web.html index 5c535da93..6338113ff 100644 --- a/static/index-web.html +++ b/static/index-web.html @@ -2,6 +2,7 @@ + diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index c5f62f977..99af908cf 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -202,36 +202,30 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { ) : ( )} -
-
-
- {claim ? : {__('Nothing here')}} +
+
+
+
+ {claim ? : {__('Nothing here')}} +
+ {!isChannel && claim && }
- {!pending && ( - - {hideActions ? null : actions !== undefined ? ( - actions - ) : ( -
- {isChannel && !channelIsBlocked && !claimIsMine && ( - - )} - {!hideBlock && isChannel && !isSubscribed && !claimIsMine && ( - - )} - {!isChannel && claim && } -
- )} -
- )} -
-
{!isResolvingUri && (
{claim ? ( - + + {' '} + {pending + ? __('Pending...') + : claim && + (isChannel ? ( + type !== 'inline' && `${claimsInChannel} ${__('publishes')}` + ) : ( + + ))} + ) : (
{__('Publish something and claim this spot!')}
@@ -244,21 +238,27 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
)} -
- {pending ? ( -
{__('Pending...')}
- ) : ( - claim && - (isChannel ? ( - type !== 'inline' && `${claimsInChannel} ${__('publishes')}` - ) : ( - - )) - )} -
)}
+
+
+ {!pending && ( + + {hideActions ? null : actions !== undefined ? ( + actions + ) : ( +
+ {isChannel && !channelIsBlocked && !claimIsMine && ( + + )} + {!hideBlock && isChannel && !isSubscribed && !claimIsMine && ( + + )} +
+ )} +
+ )} {properties !== undefined ? properties : }
diff --git a/ui/component/common/icon-custom.jsx b/ui/component/common/icon-custom.jsx index 8b04ec24f..51e361457 100644 --- a/ui/component/common/icon-custom.jsx +++ b/ui/component/common/icon-custom.jsx @@ -322,4 +322,11 @@ export const icons = { ), + [ICONS.MENU]: buildIcon( + + + + + + ), }; diff --git a/ui/component/fileViewer/view.jsx b/ui/component/fileViewer/view.jsx index 8574184a4..3482beea5 100644 --- a/ui/component/fileViewer/view.jsx +++ b/ui/component/fileViewer/view.jsx @@ -12,6 +12,7 @@ import { FILE_WRAPPER_CLASS } from 'page/file/view'; import Draggable from 'react-draggable'; import Tooltip from 'component/common/tooltip'; import { onFullscreenChange } from 'util/full-screen'; +import useIsMobile from 'effects/use-is-mobile'; type Props = { mediaType: string, @@ -50,6 +51,7 @@ export default function FileViewer(props: Props) { mediaType, contentType, } = props; + const isMobile = useIsMobile(); const [playTime, setPlayTime] = useState(); const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect'); const [position, setPosition] = usePersistedState('floating-file-viewer:position', { @@ -125,7 +127,7 @@ export default function FileViewer(props: Props) { } const hidePlayer = - !isPlaying || !uri || (!inline && (!floatingPlayerEnabled || !['audio', 'video'].includes(mediaType))); + !isPlaying || !uri || (!inline && (isMobile || !floatingPlayerEnabled || !['audio', 'video'].includes(mediaType))); if (hidePlayer) { return null; diff --git a/ui/component/header/index.js b/ui/component/header/index.js index e4e141955..4f1cf7edb 100644 --- a/ui/component/header/index.js +++ b/ui/component/header/index.js @@ -1,9 +1,10 @@ import * as SETTINGS from 'constants/settings'; +import * as MODALS from 'constants/modal_types'; import { connect } from 'react-redux'; import { selectBalance, formatCredits } from 'lbry-redux'; import { selectUserVerifiedEmail, selectGetSyncErrorMessage } from 'lbryinc'; 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 Header from './view'; @@ -21,6 +22,7 @@ const select = state => ({ const perform = dispatch => ({ setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), signOut: () => dispatch(doSignOut()), + openMobileNavigation: () => dispatch(doOpenModal(MODALS.MOBILE_NAVIGATION)), }); export default connect( diff --git a/ui/component/header/view.jsx b/ui/component/header/view.jsx index f9d708cae..364933b42 100644 --- a/ui/component/header/view.jsx +++ b/ui/component/header/view.jsx @@ -27,6 +27,7 @@ type Props = { authHeader: boolean, syncError: ?string, signOut: () => void, + openMobileNavigation: () => void, }; const Header = (props: Props) => { @@ -41,6 +42,7 @@ const Header = (props: Props) => { authHeader, signOut, syncError, + openMobileNavigation, } = props; const authenticated = Boolean(email); @@ -201,6 +203,16 @@ const Header = (props: Props) => {
) )} + + {!authenticated && ( +
); diff --git a/ui/component/inviteList/view.jsx b/ui/component/inviteList/view.jsx index 50610c69a..7188940b5 100644 --- a/ui/component/inviteList/view.jsx +++ b/ui/component/inviteList/view.jsx @@ -53,36 +53,38 @@ class InviteList extends React.PureComponent { - - - - - - - - - - {invitees.map(invitee => ( - - - - +
+
{__('Invitee Email')}{__('Invite Status')}{__('Reward')}
{invitee.email} - {invitee.invite_accepted ? __('Accepted') : __('Not Accepted')} - - {invitee.invite_reward_claimed && ( - - {__('Claimed')} - - - )} - - {!invitee.invite_reward_claimed && - (invitee.invite_reward_claimable ? {__('Claimable')} : __('Unclaimable'))} -
+ + + + + - ))} - -
{__('Invitee Email')}{__('Invite Status')}{__('Reward')}
+ + + {invitees.map(invitee => ( + + {invitee.email} + + {invitee.invite_accepted ? __('Accepted') : __('Not Accepted')} + + + {invitee.invite_reward_claimed && ( + + {__('Claimed')} + + + )} + + {!invitee.invite_reward_claimed && + (invitee.invite_reward_claimable ? {__('Claimable')} : __('Unclaimable'))} + + + ))} + + + ); } diff --git a/ui/component/page/view.jsx b/ui/component/page/view.jsx index ca78f6cf5..cc027ac75 100644 --- a/ui/component/page/view.jsx +++ b/ui/component/page/view.jsx @@ -2,7 +2,7 @@ import type { Node } from 'react'; import React, { Fragment } from 'react'; import classnames from 'classnames'; -import SideBar from 'component/sideBar'; +import SideNavigation from 'component/sideNavigation'; import Header from 'component/header'; export const MAIN_CLASS = 'main'; @@ -24,7 +24,7 @@ function Page(props: Props) {
{children}
- {!authPage && } + {!authPage && }
); diff --git a/ui/component/rewardListClaimed/view.jsx b/ui/component/rewardListClaimed/view.jsx index 47afd70a6..b2a4a8ba1 100644 --- a/ui/component/rewardListClaimed/view.jsx +++ b/ui/component/rewardListClaimed/view.jsx @@ -37,26 +37,28 @@ const RewardListClaimed = (props: Props) => {
- - - - - - - - - - - {rewards.reverse().map(reward => ( - - - - - +
+
{__('Title')}{__('Amount')}{__('Transaction')}{__('Date')}
{reward.reward_title}{reward.reward_amount}{reward.transaction_id && }{moment(reward.created_at).format('LLL')}
+ + + + + + - ))} - -
{__('Title')}{__('Amount')}{__('Transaction')}{__('Date')}
+ + + {rewards.reverse().map(reward => ( + + {reward.reward_title} + {reward.reward_amount} + {reward.transaction_id && } + {moment(reward.created_at).format('LLL')} + + ))} + + + ); }; diff --git a/ui/component/sideBar/index.js b/ui/component/sideNavigation/index.js similarity index 70% rename from ui/component/sideBar/index.js rename to ui/component/sideNavigation/index.js index 53b599044..c138e95e4 100644 --- a/ui/component/sideBar/index.js +++ b/ui/component/sideNavigation/index.js @@ -2,21 +2,22 @@ import * as SETTINGS from 'constants/settings'; import { connect } from 'react-redux'; import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectFollowedTags } from 'lbry-redux'; -import { selectUserEmail, selectUploadCount } from 'lbryinc'; -import SideBar from './view'; +import { selectUploadCount, selectUserVerifiedEmail } from 'lbryinc'; import { makeSelectClientSetting } from 'redux/selectors/settings'; +import { doSignOut } from 'redux/actions/app'; +import SideNavigation from './view'; const select = state => ({ subscriptions: selectSubscriptions(state), followedTags: selectFollowedTags(state), language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change - email: selectUserEmail(state), uploadCount: selectUploadCount(state), + email: selectUserVerifiedEmail(state), }); -const perform = () => ({}); - export default connect( select, - perform -)(SideBar); + { + doSignOut, + } +)(SideNavigation); diff --git a/ui/component/sideBar/view.jsx b/ui/component/sideNavigation/view.jsx similarity index 58% rename from ui/component/sideBar/view.jsx rename to ui/component/sideNavigation/view.jsx index 7fd0077ee..28739096e 100644 --- a/ui/component/sideBar/view.jsx +++ b/ui/component/sideNavigation/view.jsx @@ -13,30 +13,53 @@ type Props = { email: ?string, obscureSideBar: boolean, uploadCount: number, + sticky: boolean, + showAllLinks: boolean, + doSignOut: () => void, }; function SideBar(props: Props) { - const { subscriptions, followedTags, obscureSideBar, uploadCount } = props; - function buildLink(path, label, icon, guide) { + const { + subscriptions, + followedTags, + obscureSideBar, + uploadCount, + doSignOut, + email, + sticky = true, + showAllLinks = false, + } = props; + const isAuthenticated = Boolean(email); + + function buildLink(path, label, icon, onClick) { return { navigate: path ? `$/${path}` : '/', label, icon, - guide, + onClick, }; } + const Wrapper = ({ children }: any) => + sticky ? ( + + {children} + + ) : ( +
{children}
+ ); + return obscureSideBar ? ( - +

LBRY

{__('The best decentralized content platform on the web.')}

-
+ ) : ( - + - + ); } diff --git a/ui/component/transactionListTable/view.jsx b/ui/component/transactionListTable/view.jsx index fbbc15ccd..a3b7968f1 100644 --- a/ui/component/transactionListTable/view.jsx +++ b/ui/component/transactionListTable/view.jsx @@ -32,7 +32,7 @@ function TransactionListTable(props: Props) {

{emptyMessage || __('No transactions.')}

)} {!!transactionList.length && ( - +
@@ -55,7 +55,7 @@ function TransactionListTable(props: Props) { ))}
- +
)}
); diff --git a/ui/constants/modal_types.js b/ui/constants/modal_types.js index 430442d2d..dab0c0e13 100644 --- a/ui/constants/modal_types.js +++ b/ui/constants/modal_types.js @@ -33,3 +33,4 @@ export const WALLET_SEND = 'wallet_send'; export const WALLET_RECEIVE = 'wallet_receive'; export const CREATE_CHANNEL = 'create_channel'; export const YOUTUBE_WELCOME = 'youtube_welcome'; +export const MOBILE_NAVIGATION = 'mobile_navigation'; diff --git a/ui/effects/use-is-mobile.js b/ui/effects/use-is-mobile.js new file mode 100644 index 000000000..9ab04c35c --- /dev/null +++ b/ui/effects/use-is-mobile.js @@ -0,0 +1,6 @@ +import useMedia from './use-media'; + +export default function useIsMobile() { + const isMobile = useMedia(['(min-width: 901px)'], [false], true); + return isMobile; +} diff --git a/ui/effects/use-media.js b/ui/effects/use-media.js new file mode 100644 index 000000000..3bc5f672e --- /dev/null +++ b/ui/effects/use-media.js @@ -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; +} diff --git a/ui/modal/modal.jsx b/ui/modal/modal.jsx index c1d8a3c0f..75c4152c0 100644 --- a/ui/modal/modal.jsx +++ b/ui/modal/modal.jsx @@ -57,7 +57,7 @@ export class Modal extends React.PureComponent { overlayClassName="modal-overlay" > {title &&

{title}

} - {type === 'card' &&