collapsable sidebar initial commit
This commit is contained in:
parent
f88b98ea62
commit
692862c769
45 changed files with 776 additions and 512 deletions
|
@ -1275,5 +1275,6 @@
|
|||
"URL": "URL",
|
||||
"Total Uploads": "Total Uploads",
|
||||
"Are you sure you want to purchase %claim_title%?": "Are you sure you want to purchase %claim_title%?",
|
||||
"Discover": "Discover",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ type Props = {
|
|||
isBodyList?: boolean,
|
||||
defaultExpand?: boolean,
|
||||
nag?: Node,
|
||||
smallTitle?: boolean,
|
||||
};
|
||||
|
||||
export default function Card(props: Props) {
|
||||
|
@ -33,6 +34,7 @@ export default function Card(props: Props) {
|
|||
isPageTitle = false,
|
||||
isBodyList = false,
|
||||
noTitleWrap = false,
|
||||
smallTitle = false,
|
||||
defaultExpand,
|
||||
nag,
|
||||
} = props;
|
||||
|
@ -47,11 +49,18 @@ export default function Card(props: Props) {
|
|||
'card__header--nowrap': noTitleWrap,
|
||||
})}
|
||||
>
|
||||
<div className={classnames('card__title-section', { 'card__title-section--body-list': isBodyList })}>
|
||||
<div
|
||||
className={classnames('card__title-section', {
|
||||
'card__title-section--body-list': isBodyList,
|
||||
'card__title-section--small': smallTitle,
|
||||
})}
|
||||
>
|
||||
{icon && <Icon sectionIcon icon={icon} />}
|
||||
<div>
|
||||
{isPageTitle && <h1 className="card__title">{title}</h1>}
|
||||
{!isPageTitle && <h2 className="card__title">{title}</h2>}
|
||||
{!isPageTitle && (
|
||||
<h2 className={classnames('card__title', { 'card__title--small': smallTitle })}>{title}</h2>
|
||||
)}
|
||||
{subtitle && <div className="card__subtitle">{subtitle}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { withRouter } from 'react-router';
|
||||
import { Form, FormField } from 'component/common/form';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
const PAGINATE_PARAM = 'page';
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
|
|||
import Button from 'component/button';
|
||||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -10,7 +10,7 @@ import Button from 'component/button';
|
|||
import FileDownloadLink from 'component/fileDownloadLink';
|
||||
import { buildURI } from 'lbry-redux';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import ClaimSupportButton from 'component/claimSupportButton';
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import ClaimTags from 'component/claimTags';
|
||||
import Card from 'component/common/card';
|
||||
|
@ -23,23 +23,22 @@ class FileDescription extends PureComponent<Props> {
|
|||
const { description } = metadata;
|
||||
|
||||
if (!description && !(tags && tags.length)) return null;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={__('Description')}
|
||||
defaultExpand
|
||||
actions={
|
||||
<>
|
||||
{description && (
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
)}
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
<Card
|
||||
title={__('Description')}
|
||||
defaultExpand
|
||||
actions={
|
||||
<>
|
||||
{description && (
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
)}
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +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';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
type Props = {
|
||||
isFloating: boolean,
|
||||
|
|
|
@ -28,7 +28,6 @@ const perform = dispatch => ({
|
|||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
syncSettings: () => dispatch(doSyncClientSettings()),
|
||||
signOut: () => dispatch(doSignOut()),
|
||||
openMobileNavigation: () => dispatch(doOpenModal(MODALS.MOBILE_NAVIGATION)),
|
||||
openChannelCreate: () => dispatch(doOpenModal(MODALS.CREATE_CHANNEL)),
|
||||
openSignOutModal: () => dispatch(doOpenModal(MODALS.SIGN_OUT)),
|
||||
clearEmailEntry: () => dispatch(doClearEmailEntry()),
|
||||
|
|
|
@ -12,9 +12,11 @@ import Icon from 'component/common/icon';
|
|||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
import NavigationButton from 'component/navigationButton';
|
||||
import NotificationHeaderButton from 'component/notificationHeaderButton';
|
||||
import { LOGO_TITLE } from 'config';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import NotificationBubble from 'component/notificationBubble';
|
||||
import NotificationHeaderButton from 'component/notificationHeaderButton';
|
||||
|
||||
// @if TARGET='app'
|
||||
import { remote } from 'electron';
|
||||
import { IS_MAC } from 'component/app/view';
|
||||
|
@ -49,13 +51,15 @@ type Props = {
|
|||
syncError: ?string,
|
||||
emailToVerify?: string,
|
||||
signOut: () => void,
|
||||
openMobileNavigation: () => void,
|
||||
openChannelCreate: () => void,
|
||||
openSignOutModal: () => void,
|
||||
clearEmailEntry: () => void,
|
||||
clearPasswordEntry: () => void,
|
||||
hasNavigated: boolean,
|
||||
syncSettings: () => void,
|
||||
sidebarOpen: boolean,
|
||||
setSidebarOpen: boolean => void,
|
||||
isAbsoluteSideNavHidden: boolean,
|
||||
};
|
||||
|
||||
const Header = (props: Props) => {
|
||||
|
@ -71,13 +75,15 @@ const Header = (props: Props) => {
|
|||
authHeader,
|
||||
signOut,
|
||||
syncError,
|
||||
openMobileNavigation,
|
||||
openSignOutModal,
|
||||
clearEmailEntry,
|
||||
clearPasswordEntry,
|
||||
emailToVerify,
|
||||
backout,
|
||||
syncSettings,
|
||||
sidebarOpen,
|
||||
setSidebarOpen,
|
||||
isAbsoluteSideNavHidden,
|
||||
} = props;
|
||||
const isMobile = useIsMobile();
|
||||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
||||
|
@ -164,9 +170,6 @@ 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
|
||||
|
@ -203,6 +206,14 @@ const Header = (props: Props) => {
|
|||
) : (
|
||||
<>
|
||||
<div className="header__navigation">
|
||||
<span style={{ position: 'relative' }}>
|
||||
<Button
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
icon={ICONS.MENU}
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
/>
|
||||
{isAbsoluteSideNavHidden && <NotificationBubble />}
|
||||
</span>
|
||||
<Button
|
||||
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
|
||||
// @if TARGET='app'
|
||||
|
@ -223,33 +234,20 @@ const Header = (props: Props) => {
|
|||
{...homeButtonNavigationProps}
|
||||
/>
|
||||
|
||||
{/* @if TARGET='app' */}
|
||||
{!authHeader && (
|
||||
<div className="header__navigation-arrows">
|
||||
<NavigationButton isBackward history={history} />
|
||||
<NavigationButton isBackward={false} history={history} />
|
||||
</div>
|
||||
)}
|
||||
{/* @endif */}
|
||||
<div className="header__center">
|
||||
{/* @if TARGET='app' */}
|
||||
{!authHeader && (
|
||||
<div className="header__buttons">
|
||||
<NavigationButton isBackward history={history} />
|
||||
<NavigationButton isBackward={false} history={history} />
|
||||
</div>
|
||||
)}
|
||||
{/* @endif */}
|
||||
|
||||
{!authHeader && <WunderBar />}
|
||||
</div>
|
||||
{!authHeader && <WunderBar />}
|
||||
|
||||
{!authHeader ? (
|
||||
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
|
||||
{(!IS_WEB || authenticated) && (
|
||||
<Fragment>
|
||||
<Button
|
||||
aria-label={__('Your wallet')}
|
||||
navigate={`/$/${PAGES.WALLET}`}
|
||||
className="header__navigation-item menu__title header__navigation-item--balance"
|
||||
label={getWalletTitle()}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
<div className="header__buttons mobile-hidden">
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Publish a file, or create a channel')}
|
||||
|
@ -263,6 +261,7 @@ const Header = (props: Props) => {
|
|||
>
|
||||
<Icon size={18} icon={ICONS.PUBLISH} aria-hidden />
|
||||
</MenuButton>
|
||||
<NotificationHeaderButton />
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOAD}`)}>
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
|
@ -275,8 +274,6 @@ const Header = (props: Props) => {
|
|||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
<NotificationHeaderButton />
|
||||
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Your account')}
|
||||
|
@ -334,37 +331,57 @@ const Header = (props: Props) => {
|
|||
)}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Settings')}
|
||||
title={__('Settings')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!authHeader ? (
|
||||
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
|
||||
{(!IS_WEB || authenticated) && (
|
||||
<Fragment>
|
||||
<Button
|
||||
aria-label={__('Your wallet')}
|
||||
navigate={`/$/${PAGES.WALLET}`}
|
||||
className="header__navigation-item menu__title header__navigation-item--balance"
|
||||
label={getWalletTitle()}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<Menu>
|
||||
<MenuButton
|
||||
aria-label={__('Settings')}
|
||||
title={__('Settings')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
>
|
||||
<Icon size={18} icon={ICONS.SETTINGS} aria-hidden />
|
||||
</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 && (
|
||||
<div className="header__auth-buttons">
|
||||
<Button navigate={`/$/${PAGES.AUTH_SIGNIN}`} button="link" label={__('Sign In')} />
|
||||
|
@ -394,17 +411,6 @@ const Header = (props: Props) => {
|
|||
</div>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
onClick={openMobileNavigation}
|
||||
icon={ICONS.MENU}
|
||||
iconSize={24}
|
||||
className="header__menu--mobile"
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
// @endif
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
9
ui/component/notificationBubble/index.js
Normal file
9
ui/component/notificationBubble/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectUnreadNotificationCount } from 'redux/selectors/notifications';
|
||||
import NotificationHeaderButton from './view';
|
||||
|
||||
const select = state => ({
|
||||
unreadCount: selectUnreadNotificationCount(state),
|
||||
});
|
||||
|
||||
export default connect(select)(NotificationHeaderButton);
|
20
ui/component/notificationBubble/view.jsx
Normal file
20
ui/component/notificationBubble/view.jsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
unreadCount: number,
|
||||
};
|
||||
|
||||
export default function NotificationHeaderButton(props: Props) {
|
||||
const { unreadCount } = props;
|
||||
|
||||
if (unreadCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="notification__bubble">
|
||||
<span className="notification__count">{unreadCount}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
|
@ -8,6 +8,10 @@ import Footer from 'web/component/footer';
|
|||
/* @if TARGET='app' */
|
||||
import StatusBar from 'component/common/status-bar';
|
||||
/* @endif */
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
|
||||
export const MAIN_CLASS = 'main';
|
||||
type Props = {
|
||||
|
@ -16,9 +20,11 @@ type Props = {
|
|||
autoUpdateDownloaded: boolean,
|
||||
isUpgradeAvailable: boolean,
|
||||
authPage: boolean,
|
||||
filePage: boolean,
|
||||
noHeader: boolean,
|
||||
noFooter: boolean,
|
||||
noSideNavigation: boolean,
|
||||
fullWidth: boolean,
|
||||
backout: {
|
||||
backLabel?: string,
|
||||
backNavDefault?: string,
|
||||
|
@ -32,18 +38,61 @@ function Page(props: Props) {
|
|||
children,
|
||||
className,
|
||||
authPage = false,
|
||||
filePage = false,
|
||||
noHeader = false,
|
||||
noFooter = false,
|
||||
noSideNavigation = false,
|
||||
|
||||
backout,
|
||||
} = props;
|
||||
const {
|
||||
location: { pathname },
|
||||
} = useHistory();
|
||||
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', true);
|
||||
const isMediumScreen = useIsMediumScreen();
|
||||
const isMobile = useIsMobile();
|
||||
let isOnFilePage = false;
|
||||
try {
|
||||
const url = pathname.slice(1).replace(/:/g, '#');
|
||||
const { isChannel } = parseURI(url);
|
||||
if (!isChannel) {
|
||||
isOnFilePage = true;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
const isAbsoluteSideNavHidden = (isOnFilePage || isMobile) && !sidebarOpen;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOnFilePage || isMediumScreen) {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
}, [isOnFilePage, isMediumScreen]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!noHeader && <Header authHeader={authPage} backout={backout} />}
|
||||
<div className={classnames('main-wrapper__inner')}>
|
||||
<main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main>
|
||||
{!authPage && !noSideNavigation && <SideNavigation />}
|
||||
{!noHeader && (
|
||||
<Header
|
||||
authHeader={authPage}
|
||||
backout={backout}
|
||||
sidebarOpen={sidebarOpen}
|
||||
isAbsoluteSideNavHidden={isAbsoluteSideNavHidden}
|
||||
setSidebarOpen={setSidebarOpen}
|
||||
/>
|
||||
)}
|
||||
<div className={classnames('main-wrapper__inner', { 'main-wrapper__inner--filepage': isOnFilePage })}>
|
||||
{!authPage && !noSideNavigation && (
|
||||
<SideNavigation
|
||||
sidebarOpen={sidebarOpen}
|
||||
setSidebarOpen={setSidebarOpen}
|
||||
isMediumScreen={isMediumScreen}
|
||||
isOnFilePage={isOnFilePage}
|
||||
/>
|
||||
)}
|
||||
<main
|
||||
className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage, 'main--file-page': filePage })}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
{/* @if TARGET='app' */}
|
||||
<StatusBar />
|
||||
{/* @endif */}
|
||||
|
|
|
@ -71,6 +71,8 @@ export default class RecommendedContent extends React.PureComponent<Props> {
|
|||
return (
|
||||
<Card
|
||||
isBodyList
|
||||
smallTitle
|
||||
className="file-page__recommended"
|
||||
title={__('Related')}
|
||||
body={
|
||||
<WaitUntilOnPage lastUpdateDate={this.lastReset}>
|
||||
|
|
|
@ -212,13 +212,7 @@ function AppRouter(props: Props) {
|
|||
<Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} />
|
||||
<Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} />
|
||||
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />
|
||||
<Route path={`/$/${PAGES.TAGS_FOLLOWING}`} exact component={TagsFollowingPage} />
|
||||
<Route
|
||||
path={`/$/${PAGES.CHANNELS_FOLLOWING}`}
|
||||
exact
|
||||
component={isAuthenticated || !IS_WEB ? ChannelsFollowingPage : DiscoverPage}
|
||||
/>
|
||||
<Route path={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} exact component={ChannelsFollowingDiscoverPage} />
|
||||
|
||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||
{/* @if TARGET='app' */}
|
||||
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
|
||||
|
@ -235,11 +229,17 @@ function AppRouter(props: Props) {
|
|||
<Route path={`/$/${PAGES.INVITE}/:referrer`} exact component={InvitedPage} />
|
||||
<Route path={`/$/${PAGES.CHECKOUT}`} exact component={CheckoutPage} />
|
||||
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING}`} component={TagsFollowingPage} />
|
||||
<PrivateRoute
|
||||
{...props}
|
||||
path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`}
|
||||
exact
|
||||
component={SettingsNotificationsPage}
|
||||
path={`/$/${PAGES.CHANNELS_FOLLOWING}`}
|
||||
component={isAuthenticated || !IS_WEB ? ChannelsFollowingPage : DiscoverPage}
|
||||
/>
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_NOTIFICATIONS}`} component={SettingsNotificationsPage} />
|
||||
<PrivateRoute
|
||||
{...props}
|
||||
path={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`}
|
||||
component={ChannelsFollowingDiscoverPage}
|
||||
/>
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.INVITE}`} component={InvitePage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNEL_NEW}`} component={ChannelNew} />
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { selectFollowedTags, selectPurchaseUriSuccess, doClearPurchasedUriSuccess, SETTINGS } from 'lbry-redux';
|
||||
import { selectUploadCount } from 'lbryinc';
|
||||
import { selectPurchaseUriSuccess, doClearPurchasedUriSuccess, SETTINGS } from 'lbry-redux';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSignOut } from 'redux/actions/app';
|
||||
import { selectUnreadNotificationCount } from 'redux/selectors/notifications';
|
||||
|
||||
import SideNavigation from './view';
|
||||
|
||||
const select = state => ({
|
||||
subscriptions: selectSubscriptions(state),
|
||||
followedTags: selectFollowedTags(state),
|
||||
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change
|
||||
uploadCount: selectUploadCount(state),
|
||||
email: selectUserVerifiedEmail(state),
|
||||
purchaseSuccess: selectPurchaseUriSuccess(state),
|
||||
unreadCount: selectUnreadNotificationCount(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -1,31 +1,129 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import Button from 'component/button';
|
||||
import Tag from 'component/tag';
|
||||
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||
import Spinner from 'component/spinner';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import classnames from 'classnames';
|
||||
import { PINNED_LABEL_1, PINNED_URI_1, PINNED_URI_2, PINNED_LABEL_2 } from 'config';
|
||||
// @if TARGET='web'
|
||||
// import Ads from 'web/component/ads';
|
||||
// @endif
|
||||
import NotificationBubble from 'component/notificationBubble';
|
||||
|
||||
const SHOW_CHANNELS = 'SHOW_CHANNELS';
|
||||
const SHOW_TAGS = 'SHOW_TAGS';
|
||||
const ESCAPE_KEY_CODE = 27;
|
||||
const BACKSLASH_KEY_CODE = 220;
|
||||
const TOP_LEVEL_LINKS: Array<{
|
||||
label: string,
|
||||
navigate: string,
|
||||
icon: string,
|
||||
extra?: Node,
|
||||
hideForUnauth?: boolean,
|
||||
}> = [
|
||||
{
|
||||
label: __('Home'),
|
||||
navigate: `/`,
|
||||
icon: ICONS.HOME,
|
||||
},
|
||||
{
|
||||
label: __('Following'),
|
||||
navigate: `/$/${PAGES.CHANNELS_FOLLOWING}`,
|
||||
icon: ICONS.SUBSCRIBE,
|
||||
},
|
||||
{
|
||||
label: __('Your Tags'),
|
||||
navigate: `/$/${PAGES.TAGS_FOLLOWING}`,
|
||||
icon: ICONS.TAG,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
{
|
||||
label: __('Discover'),
|
||||
navigate: `/$/${PAGES.DISCOVER}`,
|
||||
icon: ICONS.DISCOVER,
|
||||
},
|
||||
{
|
||||
label: __('Purchased'),
|
||||
navigate: `/$/${PAGES.LIBRARY}`,
|
||||
icon: ICONS.PURCHASED,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
];
|
||||
|
||||
const ABSOLUTE_LINKS: Array<{
|
||||
label: string,
|
||||
navigate: string,
|
||||
icon: string,
|
||||
extra?: Node,
|
||||
hideForUnauth?: boolean,
|
||||
}> = [
|
||||
{
|
||||
label: __('Upload'),
|
||||
navigate: `/$/${PAGES.UPLOAD}`,
|
||||
icon: ICONS.PUBLISH,
|
||||
},
|
||||
{
|
||||
label: __('New Channel'),
|
||||
navigate: `/$/${PAGES.CHANNEL_NEW}`,
|
||||
icon: ICONS.CHANNEL,
|
||||
},
|
||||
{
|
||||
label: __('Uploads'),
|
||||
navigate: `/$/${PAGES.UPLOADS}`,
|
||||
icon: ICONS.PUBLISH,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
|
||||
{
|
||||
label: __('Channels'),
|
||||
navigate: `/$/${PAGES.CHANNELS}`,
|
||||
icon: ICONS.CHANNEL,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
{
|
||||
label: __('Creator Analytics'),
|
||||
navigate: `/$/${PAGES.CREATOR_ANALYTICS}`,
|
||||
icon: ICONS.ANALYTICS,
|
||||
},
|
||||
{
|
||||
label: __('Notifications'),
|
||||
navigate: `/$/${PAGES.NOTIFICATIONS}`,
|
||||
icon: ICONS.NOTIFICATION,
|
||||
extra: <NotificationBubble />,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
{
|
||||
label: __('Rewards'),
|
||||
navigate: `/$/${PAGES.REWARDS}`,
|
||||
icon: ICONS.REWARDS,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
{
|
||||
label: __('Invites'),
|
||||
navigate: `/$/${PAGES.INVITES}`,
|
||||
icon: ICONS.INVITE,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
{
|
||||
label: __('Settings'),
|
||||
navigate: `/$/${PAGES.SETTINGS}`,
|
||||
icon: ICONS.SETTINGS,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
{
|
||||
label: __('Help'),
|
||||
navigate: `/$/${PAGES.HELP}`,
|
||||
icon: ICONS.HELP,
|
||||
hideForUnauth: true,
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
subscriptions: Array<Subscription>,
|
||||
followedTags: Array<Tag>,
|
||||
email: ?string,
|
||||
uploadCount: number,
|
||||
sticky: boolean,
|
||||
expanded: boolean,
|
||||
doSignOut: () => void,
|
||||
location: { pathname: string },
|
||||
sidebarOpen: boolean,
|
||||
setSidebarOpen: boolean => void,
|
||||
isMediumScreen: boolean,
|
||||
isOnFilePage: boolean,
|
||||
unreadCount: number,
|
||||
purchaseSuccess: boolean,
|
||||
doClearPurchasedUriSuccess: () => void,
|
||||
};
|
||||
|
@ -33,44 +131,20 @@ type Props = {
|
|||
function SideNavigation(props: Props) {
|
||||
const {
|
||||
subscriptions,
|
||||
followedTags,
|
||||
uploadCount,
|
||||
doSignOut,
|
||||
// doSignOut,
|
||||
email,
|
||||
sticky = true,
|
||||
expanded = false,
|
||||
location,
|
||||
purchaseSuccess,
|
||||
doClearPurchasedUriSuccess,
|
||||
sidebarOpen,
|
||||
setSidebarOpen,
|
||||
isMediumScreen,
|
||||
isOnFilePage,
|
||||
unreadCount,
|
||||
} = props;
|
||||
const { pathname } = location;
|
||||
const isAuthenticated = Boolean(email);
|
||||
const [pulseLibrary, setPulseLibrary] = React.useState(false);
|
||||
const [sideInformation, setSideInformation] = usePersistedState(
|
||||
'side-navigation:information',
|
||||
getSideInformation(pathname)
|
||||
);
|
||||
|
||||
const isPersonalized = !IS_WEB || isAuthenticated;
|
||||
const requireAuthOnPersonalizedActions = IS_WEB;
|
||||
|
||||
function getSideInformation(path) {
|
||||
switch (path) {
|
||||
case `/$/${PAGES.CHANNELS_FOLLOWING}`:
|
||||
return SHOW_CHANNELS;
|
||||
|
||||
case `/$/${PAGES.TAGS_FOLLOWING}`:
|
||||
return SHOW_TAGS;
|
||||
|
||||
default:
|
||||
return sideInformation;
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const sideInfo = getSideInformation(pathname);
|
||||
setSideInformation(sideInfo);
|
||||
}, [pathname, setSideInformation]);
|
||||
const isAbsolute = isOnFilePage || isMediumScreen;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (purchaseSuccess) {
|
||||
|
@ -85,172 +159,119 @@ function SideNavigation(props: Props) {
|
|||
}
|
||||
}, [setPulseLibrary, purchaseSuccess, doClearPurchasedUriSuccess]);
|
||||
|
||||
function buildLink(path, label, icon, onClick, requiresAuth = false, isLiteral = false) {
|
||||
return {
|
||||
navigate: !isLiteral ? `$/${path}` : `${path}`,
|
||||
label,
|
||||
icon,
|
||||
onClick,
|
||||
requiresAuth,
|
||||
};
|
||||
}
|
||||
React.useEffect(() => {
|
||||
function handleKeydown(e: SyntheticKeyboardEvent<*>) {
|
||||
if (e.keyCode === ESCAPE_KEY_CODE && isAbsolute) {
|
||||
setSidebarOpen(false);
|
||||
} else if (e.keyCode === BACKSLASH_KEY_CODE) {
|
||||
const hasActiveInput = document.querySelector('input:focus');
|
||||
if (!hasActiveInput) {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
|
||||
return () => window.removeEventListener('keydown', handleKeydown);
|
||||
}, [sidebarOpen, setSidebarOpen, isAbsolute]);
|
||||
|
||||
const Wrapper = ({ children }: any) =>
|
||||
sticky ? (
|
||||
!isOnFilePage && !isMediumScreen ? (
|
||||
<StickyBox offsetTop={100} offsetBottom={20}>
|
||||
{children}
|
||||
</StickyBox>
|
||||
) : (
|
||||
<div>{children}</div>
|
||||
children
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<nav className="navigation">
|
||||
<ul className="navigation-links">
|
||||
{[
|
||||
{
|
||||
...(expanded && !isAuthenticated ? { ...buildLink(PAGES.AUTH, __('Sign Up'), ICONS.SIGN_UP) } : {}),
|
||||
},
|
||||
{
|
||||
...(expanded && !isAuthenticated
|
||||
? { ...buildLink(PAGES.AUTH_SIGNIN, __('Sign In'), ICONS.SIGN_IN) }
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
...buildLink('/', __('Home'), ICONS.HOME, null, null, true),
|
||||
},
|
||||
{
|
||||
...buildLink(
|
||||
PAGES.CHANNELS_FOLLOWING,
|
||||
__('Following'),
|
||||
ICONS.SUBSCRIBE,
|
||||
null,
|
||||
requireAuthOnPersonalizedActions
|
||||
),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.TAGS_FOLLOWING, __('Your Tags'), ICONS.TAG, null, requireAuthOnPersonalizedActions),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.DISCOVER, __('All Content'), ICONS.DISCOVER),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.LIBRARY, IS_WEB ? __('Purchased') : __('Library'), ICONS.LIBRARY),
|
||||
},
|
||||
// @if TARGET='web'
|
||||
{
|
||||
...(PINNED_URI_1
|
||||
? { ...buildLink(`${PINNED_URI_1}`, `${PINNED_LABEL_1}`, ICONS.PINNED, null, null, true) }
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
...(PINNED_URI_2
|
||||
? { ...buildLink(`${PINNED_URI_2}`, `${PINNED_LABEL_2}`, ICONS.PINNED, null, null, true) }
|
||||
: {}),
|
||||
},
|
||||
// @endif
|
||||
{
|
||||
...(expanded ? { ...buildLink(PAGES.SETTINGS, __('Settings'), ICONS.SETTINGS) } : {}),
|
||||
},
|
||||
].map(
|
||||
linkProps =>
|
||||
linkProps.navigate && (
|
||||
{!isOnFilePage && (
|
||||
<nav className={classnames('navigation', { 'navigation--micro': !sidebarOpen || isMediumScreen })}>
|
||||
<ul className={classnames('navigation-links--relative', { 'navigation-links--micro': !sidebarOpen })}>
|
||||
{TOP_LEVEL_LINKS.map(linkProps =>
|
||||
!email && linkProps.hideForUnauth && IS_WEB ? null : (
|
||||
<li key={linkProps.navigate}>
|
||||
<Button
|
||||
{...linkProps}
|
||||
icon={pulseLibrary && linkProps.icon === ICONS.LIBRARY ? ICONS.PURCHASED : linkProps.icon}
|
||||
className={classnames('navigation-link', {
|
||||
'navigation-link--pulse': linkProps.icon === ICONS.LIBRARY && pulseLibrary,
|
||||
'navigation-link--extra': linkProps.icon === ICONS.NOTIFICATION && unreadCount > 0,
|
||||
})}
|
||||
activeClass="navigation-link--active"
|
||||
/>
|
||||
{linkProps.extra}
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
|
||||
{expanded &&
|
||||
isPersonalized &&
|
||||
[
|
||||
{
|
||||
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
|
||||
},
|
||||
{
|
||||
...buildLink(
|
||||
PAGES.UPLOADS,
|
||||
uploadCount ? (
|
||||
<span>
|
||||
{__('Uploads')}
|
||||
<Spinner type="small" />
|
||||
</span>
|
||||
) : (
|
||||
__('Uploads')
|
||||
),
|
||||
ICONS.PUBLISH
|
||||
),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.CREATOR_DASHBOARD, __('Creator Analytics'), ICONS.ANALYTICS),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.WALLET, __('Wallet'), ICONS.WALLET),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.REWARDS, __('Rewards'), ICONS.REWARDS),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.INVITE, __('Invites'), ICONS.INVITE),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.UPLOAD, __('Upload'), ICONS.PUBLISH),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.HELP, __('Help'), ICONS.HELP),
|
||||
},
|
||||
{
|
||||
...(isAuthenticated ? { ...buildLink(PAGES.AUTH, __('Sign Out'), ICONS.SIGN_OUT, doSignOut) } : {}),
|
||||
},
|
||||
].map(
|
||||
linkProps =>
|
||||
Object.keys(linkProps).length > 0 &&
|
||||
linkProps && (
|
||||
<li key={linkProps.navigate}>
|
||||
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
|
||||
{sideInformation === SHOW_TAGS && !expanded && isPersonalized && (
|
||||
<ul className="navigation__secondary navigation-links--small tags--vertical">
|
||||
{followedTags.map(({ name }, key) => (
|
||||
<li className="navigation-link__wrapper" key={name}>
|
||||
<Tag navigate={`/$/tags?t${name}`} name={name} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{sideInformation === SHOW_CHANNELS && !expanded && isPersonalized && (
|
||||
<ul className="navigation__secondary navigation-links--small">
|
||||
{subscriptions.map(({ uri, channelName }, index) => (
|
||||
<li key={uri} className="navigation-link__wrapper">
|
||||
<Button
|
||||
navigate={uri}
|
||||
label={channelName}
|
||||
className="navigation-link"
|
||||
activeClass="navigation-link--active"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</nav>
|
||||
// @if TARGET='web'
|
||||
{/* {!isAuthenticated && !expanded && <Ads />} commenting out sidebar ads for test */}
|
||||
// @endif
|
||||
{sidebarOpen && isPersonalized && subscriptions && subscriptions.length > 0 && (
|
||||
<ul className="navigation__secondary navigation-links--relative navigation-links--small">
|
||||
{subscriptions.map(({ uri, channelName }, index) => (
|
||||
<li key={uri} className="navigation-link__wrapper">
|
||||
<Button
|
||||
navigate={uri}
|
||||
label={channelName}
|
||||
className="navigation-link"
|
||||
activeClass="navigation-link--active"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{(isOnFilePage || isMediumScreen) && sidebarOpen && (
|
||||
<>
|
||||
<nav className={classnames('navigation--filepage')}>
|
||||
<ul className="navigation-links--absolute">
|
||||
{TOP_LEVEL_LINKS.map(linkProps => (
|
||||
<li key={linkProps.navigate}>
|
||||
<Button
|
||||
{...linkProps}
|
||||
icon={pulseLibrary && linkProps.icon === ICONS.LIBRARY ? ICONS.PURCHASED : linkProps.icon}
|
||||
className={classnames('navigation-link', {
|
||||
'navigation-link--pulse': linkProps.icon === ICONS.LIBRARY && pulseLibrary,
|
||||
'navigation-link--extra': linkProps.icon === ICONS.NOTIFICATION && unreadCount > 0,
|
||||
})}
|
||||
activeClass="navigation-link--active"
|
||||
/>
|
||||
{linkProps.extra}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="navigation-links--absolute">
|
||||
{ABSOLUTE_LINKS.map(linkProps => (
|
||||
<li key={linkProps.navigate}>
|
||||
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
||||
{linkProps.extra}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{isPersonalized && subscriptions && subscriptions.length > 0 && (
|
||||
<ul className="navigation__secondary navigation-links--small">
|
||||
{subscriptions.map(({ uri, channelName }, index) => (
|
||||
<li key={uri} className="navigation-link__wrapper">
|
||||
<Button
|
||||
navigate={uri}
|
||||
label={channelName}
|
||||
className="navigation-link"
|
||||
activeClass="navigation-link--active"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</nav>
|
||||
<div className="navigation__overlay" onClick={() => setSidebarOpen(false)} />
|
||||
</>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(SideNavigation);
|
||||
export default SideNavigation;
|
||||
|
|
|
@ -5,7 +5,7 @@ import Button from 'component/button';
|
|||
import CopyableText from 'component/copyableText';
|
||||
import EmbedTextArea from 'component/embedTextArea';
|
||||
import { generateDownloadUrl } from 'util/web';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import { FormField } from 'component/common/form';
|
||||
import { hmsToSeconds, secondsToHms } from 'util/time';
|
||||
import {
|
||||
|
|
|
@ -5,7 +5,7 @@ import React, { useRef } from 'react';
|
|||
import { parseURI } from 'lbry-redux';
|
||||
import Button from 'component/button';
|
||||
import useHover from 'effects/use-hover';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
type SubscriptionArgs = {
|
||||
channelName: string,
|
||||
|
|
|
@ -34,7 +34,6 @@ 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';
|
||||
export const SET_REFERRER = 'set_referrer';
|
||||
export const REPOST = 'repost';
|
||||
export const SIGN_OUT = 'sign_out';
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import useMedia from './use-media';
|
||||
|
||||
export default function useIsMobile() {
|
||||
const isMobile = useMedia(['(min-width: 901px)'], [false], true);
|
||||
return isMobile;
|
||||
}
|
11
ui/effects/use-screensize.js
Normal file
11
ui/effects/use-screensize.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import useMedia from './use-media';
|
||||
|
||||
export function useIsMobile() {
|
||||
const isMobile = useMedia(['(min-width: 901px)'], [false], true);
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
export function useIsMediumScreen() {
|
||||
const isMobile = useMedia(['(min-width: 1151px)'], [false], true);
|
||||
return isMobile;
|
||||
}
|
|
@ -5,7 +5,7 @@ import * as React from 'react';
|
|||
import ReactModal from 'react-modal';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
type ModalProps = {
|
||||
type?: string,
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal, doSignOut } from 'redux/actions/app';
|
||||
import ModalMobileNavigation from './view';
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
doHideModal,
|
||||
doSignOut,
|
||||
}
|
||||
)(ModalMobileNavigation);
|
|
@ -1,18 +0,0 @@
|
|||
// @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} expanded />
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -32,7 +32,6 @@ import ModalWalletSend from 'modal/modalWalletSend';
|
|||
import ModalWalletReceive from 'modal/modalWalletReceive';
|
||||
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
||||
import ModalMobileNavigation from 'modal/modalMobileNavigation';
|
||||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||
import ModalRepost from 'modal/modalRepost';
|
||||
import ModalSignOut from 'modal/modalSignOut';
|
||||
|
@ -125,8 +124,6 @@ function ModalRouter(props: Props) {
|
|||
return <ModalYoutubeWelcome />;
|
||||
case MODALS.CREATE_CHANNEL:
|
||||
return <ModalCreateChannel {...modalProps} />;
|
||||
case MODALS.MOBILE_NAVIGATION:
|
||||
return <ModalMobileNavigation {...modalProps} />;
|
||||
case MODALS.SET_REFERRER:
|
||||
return <ModalSetReferrer {...modalProps} />;
|
||||
case MODALS.REPOST:
|
||||
|
|
|
@ -6,7 +6,7 @@ import Page from 'component/page';
|
|||
import ClaimListDiscover from 'component/claimListDiscover';
|
||||
import Button from 'component/button';
|
||||
import useHover from 'effects/use-hover';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import analytics from 'analytics';
|
||||
import HiddenNsfw from 'component/common/hidden-nsfw';
|
||||
import Icon from 'component/common/icon';
|
||||
|
|
|
@ -4,7 +4,6 @@ import { doSetContentHistoryItem } from 'redux/actions/content';
|
|||
import {
|
||||
doFetchFileInfo,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectChannelForClaimUri,
|
||||
makeSelectClaimIsNsfw,
|
||||
|
@ -16,7 +15,6 @@ import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
|
|||
import FilePage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
obscureNsfw: !selectShowMatureContent(state),
|
||||
|
|
|
@ -15,12 +15,10 @@ import WaitUntilOnPage from 'component/common/wait-until-on-page';
|
|||
import RecommendedContent from 'component/recommendedContent';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import YoutubeBadge from 'component/youtubeBadge';
|
||||
|
||||
export const FILE_WRAPPER_CLASS = 'file-page__video-container';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
costInfo: ?{ includesData: boolean, cost: number },
|
||||
fileInfo: FileListItem,
|
||||
uri: string,
|
||||
|
@ -86,13 +84,9 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
|
||||
renderFilePageLayout(uri: string, mode: string, cost: ?number) {
|
||||
const { claim } = this.props;
|
||||
const channelClaimId = claim.signing_channel ? claim.signing_channel.claim_id : null;
|
||||
|
||||
if (RENDER_MODES.FLOATING_MODES.includes(mode)) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
|
||||
<div className={FILE_WRAPPER_CLASS}>
|
||||
<FileRenderInitiator uri={uri} />
|
||||
</div>
|
||||
|
@ -105,7 +99,6 @@ class FilePage extends React.Component<Props> {
|
|||
if (RENDER_MODES.UNRENDERABLE_MODES.includes(mode)) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
|
||||
<FileTitle uri={uri} />
|
||||
<FileRenderDownload uri={uri} isFree={cost === 0} />
|
||||
</React.Fragment>
|
||||
|
@ -115,7 +108,6 @@ class FilePage extends React.Component<Props> {
|
|||
if (RENDER_MODES.TEXT_MODES.includes(mode)) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
|
||||
<FileTitle uri={uri} />
|
||||
<FileRenderInitiator uri={uri} />
|
||||
<FileRenderInline uri={uri} />
|
||||
|
@ -125,7 +117,6 @@ class FilePage extends React.Component<Props> {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<YoutubeBadge channelClaimId={channelClaimId} includeSyncDate={false} />
|
||||
<FileRenderInitiator uri={uri} />
|
||||
<FileRenderInline uri={uri} />
|
||||
<FileTitle uri={uri} />
|
||||
|
@ -152,29 +143,26 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
|
||||
return (
|
||||
<Page className="file-page">
|
||||
<Page className="file-page" filePage>
|
||||
<div className={classnames('section card-stack', `file-page__${renderMode}`)}>
|
||||
{this.renderFilePageLayout(uri, renderMode, costInfo ? costInfo.cost : null)}
|
||||
<FileDescription uri={uri} />
|
||||
<FileValues uri={uri} />
|
||||
<FileDetails uri={uri} />
|
||||
<Card
|
||||
title={__('Leave a Comment')}
|
||||
actions={
|
||||
<div>
|
||||
<CommentCreate uri={uri} />
|
||||
<WaitUntilOnPage lastUpdateDate={this.lastReset}>
|
||||
<CommentsList uri={uri} />
|
||||
</WaitUntilOnPage>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="section columns">
|
||||
<div className="card-stack">
|
||||
<FileDescription uri={uri} />
|
||||
<FileValues uri={uri} />
|
||||
<FileDetails uri={uri} />
|
||||
<Card
|
||||
title={__('Leave a Comment')}
|
||||
actions={
|
||||
<div>
|
||||
<CommentCreate uri={uri} />
|
||||
<WaitUntilOnPage lastUpdateDate={this.lastReset}>
|
||||
<CommentsList uri={uri} />
|
||||
</WaitUntilOnPage>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<RecommendedContent uri={uri} />
|
||||
</div>
|
||||
|
||||
<RecommendedContent uri={uri} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
selectIsFetchingNotifications,
|
||||
selectUnreadNotificationCount,
|
||||
} from 'redux/selectors/notifications';
|
||||
import { doReadNotifications } from 'redux/actions/notifications';
|
||||
import NotificationsPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -12,4 +13,6 @@ const select = state => ({
|
|||
unreadCount: selectUnreadNotificationCount(state),
|
||||
});
|
||||
|
||||
export default connect(select)(NotificationsPage);
|
||||
export default connect(select, {
|
||||
doReadNotifications,
|
||||
})(NotificationsPage);
|
||||
|
|
|
@ -9,10 +9,12 @@ import Notification from 'component/notification';
|
|||
type Props = {
|
||||
notifications: ?Array<Notification>,
|
||||
fetching: boolean,
|
||||
unreadCount: number,
|
||||
doReadNotifications: () => void,
|
||||
};
|
||||
|
||||
export default function NotificationsPage(props: Props) {
|
||||
const { notifications, fetching } = props;
|
||||
const { notifications, fetching, unreadCount, doReadNotifications } = props;
|
||||
|
||||
// Group sequential comment notifications if they are by the same author
|
||||
let groupedCount = 1;
|
||||
|
@ -59,6 +61,12 @@ export default function NotificationsPage(props: Props) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (unreadCount > 0) {
|
||||
doReadNotifications();
|
||||
}
|
||||
}, [unreadCount, doReadNotifications]);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{fetching && (
|
||||
|
|
|
@ -78,3 +78,14 @@
|
|||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shadow-pulse {
|
||||
0% {
|
||||
background-color: rgba(37, 119, 97, 0.2);
|
||||
box-shadow: 0 0 0 0px rgba(37, 119, 97, 0.2);
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(37, 119, 97, 0);
|
||||
box-shadow: 0 0 0 35px rgba(37, 119, 97, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.card__title-section--small {
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
}
|
||||
|
||||
.card__actions--inline {
|
||||
@extend .card__actions;
|
||||
margin-top: 0;
|
||||
|
@ -181,10 +185,14 @@
|
|||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
font-size: var(--font-body);
|
||||
font-size: var(--font-large);
|
||||
}
|
||||
}
|
||||
|
||||
.card__title--small {
|
||||
font-size: var(--font-body);
|
||||
}
|
||||
|
||||
.card__title-actions {
|
||||
align-self: flex-start;
|
||||
padding: var(--spacing-m);
|
||||
|
|
|
@ -350,7 +350,7 @@
|
|||
}
|
||||
|
||||
@media (max-width: $breakpoint-medium) and (min-width: $breakpoint-small) {
|
||||
$width: calc((100vw - var(--side-nav-width) - (var(--spacing-l) * 4) - var(--spacing-m) * 2) / 3);
|
||||
$width: calc((100vw - var(--side-nav-width) - (var(--spacing-m) * 3)) / 3);
|
||||
width: $width;
|
||||
@include handleClaimTileGifThumbnail($width);
|
||||
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-medium) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.file-render {
|
||||
|
|
|
@ -24,19 +24,11 @@
|
|||
padding-top: var(--mac-titlebar-height);
|
||||
}
|
||||
|
||||
.header--noauth-web {
|
||||
.header__navigation-item--icon {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.header__contents {
|
||||
max-width: var(--page-max-width);
|
||||
height: calc(var(--header-height) - 1px);
|
||||
height: calc(var(--header-height));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
padding: 0 var(--spacing-l);
|
||||
padding: 0 var(--spacing-m);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-s);
|
||||
|
@ -47,7 +39,6 @@
|
|||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--height-input);
|
||||
}
|
||||
|
||||
.header__menu {
|
||||
|
@ -65,29 +56,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.header__menu--mobile {
|
||||
display: none;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
display: block;
|
||||
margin-left: var(--spacing-m);
|
||||
|
||||
svg {
|
||||
stroke: var(--color-text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header__menu--with-balance {
|
||||
button:first-child {
|
||||
margin-left: var(--spacing-l);
|
||||
margin-right: var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
.header__navigation-arrows {
|
||||
.header__buttons {
|
||||
display: flex;
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.header__navigation-item {
|
||||
|
@ -98,6 +68,7 @@
|
|||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
position: relative;
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
svg {
|
||||
stroke: var(--color-text);
|
||||
|
@ -118,7 +89,7 @@
|
|||
width: var(--height-button);
|
||||
background-color: var(--color-header-button);
|
||||
border-radius: 1.5rem;
|
||||
margin-left: var(--spacing-s);
|
||||
margin-right: var(--spacing-s);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-alt);
|
||||
|
@ -137,13 +108,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
.header__navigation-item--icon {
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header__navigation-item--lbry {
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-right: var(--spacing-m);
|
||||
padding: var(--spacing-s);
|
||||
|
||||
.lbry-icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
height: var(--height-button);
|
||||
width: var(--height-button);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
.button__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,11 +137,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.header__navigation-item--balance {
|
||||
margin: 0 var(--spacing-m);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.header__navigation-dropdown {
|
||||
@extend .menu__list--header;
|
||||
padding: 0;
|
||||
|
@ -195,3 +172,9 @@
|
|||
margin: 0 var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
.header__center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -13,27 +13,68 @@
|
|||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
min-height: calc(100vh - var(--header-height));
|
||||
max-width: var(--page-max-width);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: var(--header-height);
|
||||
padding: var(--spacing-l); // Unfortunately this is coupled with .claim-preview--tile width calculation
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-s);
|
||||
padding: var(--spacing-xs);
|
||||
padding-top: var(--spacing-m);
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-large) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.main-wrapper__inner--filepage {
|
||||
padding: 0;
|
||||
padding-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
width: calc(100% - var(--side-nav-width) - var(--spacing-l));
|
||||
max-width: var(--page-max-width);
|
||||
z-index: 0;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
@media (max-width: $breakpoint-medium) {
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
margin-right: var(--spacing-s);
|
||||
margin-left: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.main--file-page {
|
||||
width: 100%;
|
||||
max-width: var(--page-max-width--filepage);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
padding-left: var(--spacing-m);
|
||||
padding-right: var(--spacing-m);
|
||||
|
||||
> :first-child {
|
||||
flex: 1;
|
||||
margin-right: var(--spacing-m);
|
||||
}
|
||||
|
||||
.file-page__recommended {
|
||||
width: 25rem;
|
||||
height: 0%;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-medium) {
|
||||
flex-direction: column;
|
||||
|
||||
> :first-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,20 +46,6 @@
|
|||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: var(--spacing-l);
|
||||
|
||||
.navigation-links:not(.navigation-links--small) {
|
||||
.navigation-link {
|
||||
font-size: var(--font-large);
|
||||
border-bottom: 1px solid var(--color-gray-3);
|
||||
padding: var(--spacing-m) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,128 @@
|
|||
.navigation {
|
||||
width: var(--side-nav-width);
|
||||
font-size: var(--font-body);
|
||||
margin-left: var(--spacing-main-padding);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
display: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation + .ads-wrapper {
|
||||
margin-top: var(--spacing-l);
|
||||
.navigation--filepage {
|
||||
z-index: 4;
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
height: calc(100vh - var(--header-height));
|
||||
width: var(--side-nav-width--large);
|
||||
overflow-y: scroll;
|
||||
margin-top: 0;
|
||||
padding: var(--spacing-l);
|
||||
padding-top: var(--spacing-s);
|
||||
padding-left: 0;
|
||||
border-top: 1px solid var(--color-border);
|
||||
background-color: var(--color-card-background);
|
||||
box-shadow: var(--card-box-shadow);
|
||||
|
||||
.navigation-link {
|
||||
padding-left: var(--spacing-m);
|
||||
}
|
||||
}
|
||||
|
||||
.navigation--micro {
|
||||
width: var(--side-nav-width--micro);
|
||||
}
|
||||
|
||||
.navigation__secondary {
|
||||
margin-top: var(--spacing-l);
|
||||
}
|
||||
|
||||
.navigation-link {
|
||||
display: block;
|
||||
position: relative;
|
||||
transition: color 0.2s;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-navigation-link);
|
||||
margin-bottom: var(--spacing-s);
|
||||
padding-left: var(--spacing-s);
|
||||
|
||||
.icon {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
stroke: var(--color-gray-5);
|
||||
}
|
||||
|
||||
.button__content {
|
||||
padding: var(--spacing-s);
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button__label {
|
||||
font-size: var(--font-small);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@extend .navigation-link--extra;
|
||||
color: var(--color-gray-5);
|
||||
|
||||
.icon {
|
||||
stroke: var(--color-gray-5);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoint-medium) {
|
||||
text-align: left;
|
||||
margin-bottom: 0;
|
||||
|
||||
.icon {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.button__content {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.button__label {
|
||||
margin-top: 0;
|
||||
font-size: var(--font-body);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-link--active {
|
||||
background-color: var(--color-primary-alt);
|
||||
border-radius: var(--border-radius);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
color: var(--color-primary);
|
||||
|
||||
.icon {
|
||||
stroke: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-link--pulse {
|
||||
overflow: visible;
|
||||
|
||||
.icon {
|
||||
animation: shadow-pulse 2.5s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-link--extra {
|
||||
border-radius: var(--border-radius);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
background-color: var(--color-gray-1);
|
||||
}
|
||||
|
||||
.navigation-links {
|
||||
@extend .ul--no-style;
|
||||
flex-direction: column;
|
||||
|
@ -24,64 +130,88 @@
|
|||
list-style: none;
|
||||
}
|
||||
|
||||
.navigation-links--small {
|
||||
.navigation-links--relative {
|
||||
@extend .navigation-links;
|
||||
font-size: var(--font-small);
|
||||
margin-right: var(--spacing-m);
|
||||
|
||||
.navigation-link {
|
||||
padding-left: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-links__inline {
|
||||
margin-left: calc(var(--spacing-m) + var(--spacing-s));
|
||||
}
|
||||
|
||||
.navigation-link__wrapper {
|
||||
margin: var(--spacing-xxs) 0;
|
||||
}
|
||||
|
||||
.navigation-link {
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
transition: color 0.2s;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--color-navigation-link);
|
||||
margin-top: var(--spacing-s);
|
||||
|
||||
.navigation-links--micro {
|
||||
.icon {
|
||||
stroke: var(--color-link-icon);
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.navigation-link--active {
|
||||
color: var(--color-link-active);
|
||||
.button__content {
|
||||
padding: var(--spacing-s);
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button__label {
|
||||
font-size: var(--font-xsmall);
|
||||
margin-left: 0;
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.navigation-link {
|
||||
margin-bottom: var(--spacing-xs);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-links--absolute {
|
||||
@extend .navigation-links;
|
||||
|
||||
.navigation-link {
|
||||
margin-bottom: 0;
|
||||
|
||||
.icon {
|
||||
stroke: var(--color-link-active);
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.button__content {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.button__label {
|
||||
margin-top: 0;
|
||||
font-size: var(--font-body);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
.navigation-links--small {
|
||||
@extend .navigation-links;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
font-size: var(--font-small);
|
||||
|
||||
.button__content {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-link--pulse {
|
||||
border-radius: var(--border-radius);
|
||||
overflow: visible;
|
||||
|
||||
.icon {
|
||||
border-radius: 50%;
|
||||
animation: shadow-pulse 2.5s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shadow-pulse {
|
||||
0% {
|
||||
background-color: rgba(37, 119, 97, 0.2);
|
||||
box-shadow: 0 0 0 0px rgba(37, 119, 97, 0.2);
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(37, 119, 97, 0);
|
||||
box-shadow: 0 0 0 35px rgba(37, 119, 97, 0);
|
||||
}
|
||||
.navigation__overlay {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
opacity: 0.3;
|
||||
background-color: black;
|
||||
z-index: 3;
|
||||
left: 0;
|
||||
top: var(--header-height);
|
||||
}
|
||||
|
|
|
@ -51,3 +51,20 @@
|
|||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.notification__bubble {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-notification);
|
||||
position: absolute;
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
color: white;
|
||||
font-size: var(--font-small);
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
z-index: 1;
|
||||
font-size: var(--font-small);
|
||||
height: var(--height-input);
|
||||
max-width: 20rem;
|
||||
max-width: 30rem;
|
||||
margin-left: var(--spacing-s);
|
||||
margin-right: var(--spacing-s);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> .icon {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
--color-warning: #fff58c;
|
||||
--color-cost: #ffd580;
|
||||
--color-focus: #93cff2;
|
||||
--color-notification: #f02849;
|
||||
|
||||
--color-black: #111;
|
||||
--color-white: #fdfdfd;
|
||||
|
@ -47,7 +48,7 @@
|
|||
--color-input-color: #111111;
|
||||
--color-input-label: var(--color-gray-5);
|
||||
--color-input-placeholder: #212529;
|
||||
--color-input-bg: #f2f2f2;
|
||||
--color-input-bg: var(--color-gray-1);
|
||||
--color-input-bg-copyable: #434b53;
|
||||
--color-input-border: var(--color-border);
|
||||
--color-input-border-active: var(--color-secondary);
|
||||
|
|
|
@ -53,6 +53,7 @@ p {
|
|||
ul,
|
||||
ol {
|
||||
li {
|
||||
position: relative;
|
||||
list-style-position: outside;
|
||||
margin: var(--spacing-xs) var(--spacing-m);
|
||||
margin-bottom: 0;
|
||||
|
@ -312,16 +313,8 @@ textarea {
|
|||
}
|
||||
}
|
||||
|
||||
.notification__bubble {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
border-radius: 50%;
|
||||
background-color: #f02849;
|
||||
position: absolute;
|
||||
top: -0.5rem;
|
||||
right: -0.5rem;
|
||||
color: white;
|
||||
font-size: var(--font-small);
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
.mobile-hidden {
|
||||
@media (max-width: $breakpoint-small) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ $spacing-width: 36px;
|
|||
$breakpoint-xsmall: 600px;
|
||||
$breakpoint-small: 900px;
|
||||
$breakpoint-medium: 1150px;
|
||||
$breakpoint-large: 1600px;
|
||||
|
||||
:root {
|
||||
--border-radius: 5px;
|
||||
|
@ -44,10 +45,13 @@ $breakpoint-medium: 1150px;
|
|||
--font-heading: 2.94rem;
|
||||
|
||||
// Width & spacing
|
||||
--page-max-width: 1420px;
|
||||
--page-max-width: 1280px;
|
||||
--page-max-width--filepage: 1700px;
|
||||
--mac-titlebar-height: 1.5rem;
|
||||
--mobile: 600px;
|
||||
--side-nav-width: 170px;
|
||||
--side-nav-width: 200px;
|
||||
--side-nav-width--micro: 100px;
|
||||
--side-nav-width--large: 250px;
|
||||
|
||||
--spacing-main-padding: var(--spacing-xl);
|
||||
--floating-viewer-width: 32rem;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
--color-link-icon: var(--color-gray-4);
|
||||
--color-link-active: var(--color-primary);
|
||||
--color-navigation-link: var(--color-gray-5);
|
||||
--color-header-button: #f7f7f7;
|
||||
--color-header-button: var(--color-button-alt-bg);
|
||||
--color-button-border: var(--color-gray-3);
|
||||
|
||||
// Color
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import Nag from 'component/common/nag';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import Button from 'component/button';
|
||||
import useIsMobile from 'effects/use-is-mobile';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
|
||||
type Props = {
|
||||
onClose: () => void,
|
||||
|
@ -41,8 +41,7 @@ export default function NagDegradedPerformance(props: Props) {
|
|||
),
|
||||
}}
|
||||
>
|
||||
lbry.tv collects usage information for itself only (%more_information%). Want control over
|
||||
this and more?
|
||||
lbry.tv collects usage information for itself only (%more_information%). Want control over this and more?
|
||||
</I18nMessage>
|
||||
}
|
||||
actionText={__('Get The App')}
|
||||
|
|
Loading…
Reference in a new issue