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