respond to ux feedback

This commit is contained in:
Sean Yesmunt 2019-06-28 03:33:07 -04:00
parent c6412eebef
commit fa24060b3f
38 changed files with 228 additions and 200 deletions

View file

@ -23,6 +23,7 @@
"WEBPACK_PORT": true "WEBPACK_PORT": true
}, },
"rules": { "rules": {
"brace-style": 0,
"comma-dangle": ["error", "always-multiline"], "comma-dangle": ["error", "always-multiline"],
"handle-callback-err": 0, "handle-callback-err": 0,
"indent": 0, "indent": 0,

View file

@ -35,7 +35,7 @@ function ChannelContent(props: Props) {
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />} {!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
{hasContent && <ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url).reverse()} />} {hasContent && <ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url)} />}
<Paginate <Paginate
onPageChange={page => fetchClaims(uri, page)} onPageChange={page => fetchClaims(uri, page)}

View file

@ -19,7 +19,7 @@ type Props = {
loading: boolean, loading: boolean,
type: string, type: string,
empty?: string, empty?: string,
meta?: Node, defaultSort?: boolean,
onScrollBottom?: any => void, onScrollBottom?: any => void,
// If using the default header, this is a unique ID needed to persist the state of the filter setting // If using the default header, this is a unique ID needed to persist the state of the filter setting
persistedStorageKey?: string, persistedStorageKey?: string,
@ -33,7 +33,7 @@ export default function ClaimList(props: Props) {
loading, loading,
persistedStorageKey, persistedStorageKey,
empty, empty,
meta, defaultSort,
type, type,
header, header,
onScrollBottom, onScrollBottom,
@ -46,7 +46,21 @@ export default function ClaimList(props: Props) {
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
} }
const urisLength = uris && uris.length;
useEffect(() => { useEffect(() => {
function handleScroll(e) {
if (onScrollBottom) {
const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
if (x && window.scrollY + window.innerHeight >= x.offsetHeight) {
// fix this
if (!loading && urisLength > 19) {
onScrollBottom();
}
}
}
}
if (onScrollBottom) { if (onScrollBottom) {
window.addEventListener('scroll', handleScroll); window.addEventListener('scroll', handleScroll);
@ -54,42 +68,35 @@ export default function ClaimList(props: Props) {
window.removeEventListener('scroll', handleScroll); window.removeEventListener('scroll', handleScroll);
}; };
} }
}, [loading, handleScroll]); }, [loading, onScrollBottom, urisLength]);
function handleScroll(e) {
if (onScrollBottom) {
const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
if (x && window.scrollY + window.innerHeight >= x.offsetHeight) {
// fix this
if (!loading && uris.length > 19) {
onScrollBottom();
}
}
}
}
return ( return (
<section className={classnames('claim-list')}> <section
className={classnames('claim-list', {
'claim-list--small': type === 'small',
})}
>
{header !== false && ( {header !== false && (
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}> <div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
{header || ( {header}
<FormField
className="claim-list__dropdown"
type="select"
name="file_sort"
value={currentSort}
onChange={handleSortChange}
>
<option value={SORT_NEW}>{__('Newest First')}</option>
<option value={SORT_OLD}>{__('Oldest First')}</option>
</FormField>
)}
{loading && <Spinner light type="small" />} {loading && <Spinner light type="small" />}
<div className="claim-list__alt-controls">{headerAltControls}</div> <div className="claim-list__alt-controls">
{headerAltControls}
{defaultSort && (
<FormField
className="claim-list__dropdown"
type="select"
name="file_sort"
value={currentSort}
onChange={handleSortChange}
>
<option value={SORT_NEW}>{__('Newest First')}</option>
<option value={SORT_OLD}>{__('Oldest First')}</option>
</FormField>
)}
</div>
</div> </div>
)} )}
{meta && <div className="claim-list__meta">{meta}</div>}
{hasUris && ( {hasUris && (
<ul> <ul>
{sortedUris.map((uri, index) => ( {sortedUris.map((uri, index) => (

View file

@ -116,6 +116,7 @@ function ClaimListDiscover(props: Props) {
const headerAltControls = ( const headerAltControls = (
<React.Fragment> <React.Fragment>
{meta}
{typeSort === 'top' && ( {typeSort === 'top' && (
<FormField <FormField
className="claim-list__dropdown" className="claim-list__dropdown"
@ -137,7 +138,6 @@ function ClaimListDiscover(props: Props) {
return ( return (
<div className="card"> <div className="card">
<ClaimList <ClaimList
meta={meta}
loading={loading} loading={loading}
uris={uris} uris={uris}
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem} injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}

View file

@ -68,7 +68,7 @@ export const icons = {
<path d="M3, 10 C3, 10 3, 10.4453982 3, 10.9968336 L3, 20.0170446 C3, 20.5675806 3.43788135, 21.0138782 4.00292933, 21.0138781 L8.99707067, 21.0138779 C9.55097324, 21.0138779 10, 20.5751284 10, 20.0089602 L10, 15.0049177 C10, 14.449917 10.4433532, 14 11.0093689, 14 L12.9906311, 14 C13.5480902, 14 14, 14.4387495 14, 15.0049177 L14, 20.0089602 C14, 20.5639609 14.4378817, 21.0138779 15.0029302, 21.0138779 L19.9970758, 21.0138781 C20.5509789, 21.0138782 21.000006, 20.56848 21.000006, 20.0170446 L21.0000057, 10" /> <path d="M3, 10 C3, 10 3, 10.4453982 3, 10.9968336 L3, 20.0170446 C3, 20.5675806 3.43788135, 21.0138782 4.00292933, 21.0138781 L8.99707067, 21.0138779 C9.55097324, 21.0138779 10, 20.5751284 10, 20.0089602 L10, 15.0049177 C10, 14.449917 10.4433532, 14 11.0093689, 14 L12.9906311, 14 C13.5480902, 14 14, 14.4387495 14, 15.0049177 L14, 20.0089602 C14, 20.5639609 14.4378817, 21.0138779 15.0029302, 21.0138779 L19.9970758, 21.0138781 C20.5509789, 21.0138782 21.000006, 20.56848 21.000006, 20.0170446 L21.0000057, 10" />
</g> </g>
), ),
[ICONS.UPLOAD]: buildIcon( [ICONS.PUBLISH]: buildIcon(
<g fill="none" fillRule="evenodd" strokeLinecap="round"> <g fill="none" fillRule="evenodd" strokeLinecap="round">
<path <path
d="M8, 18 L5, 18 L5, 18 C2.790861, 18 1, 16.209139 1, 14 C1, 11.790861 2.790861, 10 5, 10 C5.35840468, 10 5.70579988, 10.0471371 6.03632437, 10.1355501 C6.01233106, 9.92702603 6, 9.71495305 6, 9.5 C6, 6.46243388 8.46243388, 4 11.5, 4 C14.0673313, 4 16.2238156, 5.7590449 16.8299648, 8.1376465 C17.2052921, 8.04765874 17.5970804, 8 18, 8 C20.7614237, 8 23, 10.2385763 23, 13 C23, 15.7614237 20.7614237, 18 18, 18 L16, 18" d="M8, 18 L5, 18 L5, 18 C2.790861, 18 1, 16.209139 1, 14 C1, 11.790861 2.790861, 10 5, 10 C5.35840468, 10 5.70579988, 10.0471371 6.03632437, 10.1355501 C6.01233106, 9.92702603 6, 9.71495305 6, 9.5 C6, 6.46243388 8.46243388, 4 11.5, 4 C14.0673313, 4 16.2238156, 5.7590449 16.8299648, 8.1376465 C17.2052921, 8.04765874 17.5970804, 8 18, 8 C20.7614237, 8 23, 10.2385763 23, 13 C23, 15.7614237 20.7614237, 18 18, 18 L16, 18"
@ -82,18 +82,12 @@ export const icons = {
/> />
</g> </g>
), ),
[ICONS.PUBLISHED]: buildIcon( [ICONS.SUBSCRIBE]: buildIcon(
<g fill="none" fillRule="evenodd" strokeLinecap="round">
<path
d="M8, 18 L5, 18 L5, 18 C2.790861, 18 1, 16.209139 1, 14 C1, 11.790861 2.790861, 10 5, 10 C5.35840468, 10 5.70579988, 10.0471371 6.03632437, 10.1355501 C6.01233106, 9.92702603 6, 9.71495305 6, 9.5 C6, 6.46243388 8.46243388, 4 11.5, 4 C14.0673313, 4 16.2238156, 5.7590449 16.8299648, 8.1376465 C17.2052921, 8.04765874 17.5970804, 8 18, 8 C20.7614237, 8 23, 10.2385763 23, 13 C23, 15.7614237 20.7614237, 18 18, 18 L16, 18, L8, 18"
strokeLinejoin="round"
transform="scale(1, 1.2) translate(0, -2)"
/>
</g>
),
[ICONS.SUBSCRIPTION]: buildIcon(
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
), ),
[ICONS.UNSUBSCRIBE]: buildIcon(
<path d="M 12,5.67 10.94,4.61 C 5.7533356,-0.57666427 -2.0266644,7.2033357 3.16,12.39 l 1.06,1.06 7.78,7.78 7.78,-7.78 1.06,-1.06 c 2.149101,-2.148092 2.149101,-5.6319078 0,-7.78 -2.148092,-2.1491008 -5.631908,-2.1491008 -7.78,0 L 9.4481298,8.2303201 15.320603,9.2419066 11.772427,13.723825" />
),
[ICONS.SETTINGS]: buildIcon( [ICONS.SETTINGS]: buildIcon(
<g> <g>
<circle cx="12" cy="12" r="3" /> <circle cx="12" cy="12" r="3" />
@ -109,12 +103,8 @@ export const icons = {
[ICONS.OVERVIEW]: buildIcon(<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />), [ICONS.OVERVIEW]: buildIcon(<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />),
[ICONS.WALLET]: buildIcon( [ICONS.WALLET]: buildIcon(
<g> <g>
<line x1="8" y1="6" x2="21" y2="6" /> <rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
<line x1="8" y1="12" x2="21" y2="12" /> <line x1="1" y1="10" x2="23" y2="10" />
<line x1="8" y1="18" x2="21" y2="18" />
<line x1="3" y1="6" x2="3" y2="6" />
<line x1="3" y1="12" x2="3" y2="12" />
<line x1="3" y1="18" x2="3" y2="18" />
</g> </g>
), ),
[ICONS.LIBRARY]: buildIcon(<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />), [ICONS.LIBRARY]: buildIcon(<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />),
@ -224,4 +214,30 @@ export const icons = {
), ),
[ICONS.UP]: buildIcon(<polyline points="18 15 12 9 6 15" />), [ICONS.UP]: buildIcon(<polyline points="18 15 12 9 6 15" />),
[ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />), [ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />),
[ICONS.FULLSCREEN]: buildIcon(
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
),
[ICONS.FILE]: buildIcon(
<g>
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" />
<polyline points="13 2 13 9 20 9" />
</g>
),
[ICONS.CHANNEL]: buildIcon(
<g>
<circle cx="12" cy="12" r="4" />
<path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94" />
</g>
),
[ICONS.TWITTER]: buildIcon(
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" />
),
[ICONS.FACEBOOK]: buildIcon(<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />),
[ICONS.WEB]: buildIcon(
<g>
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</g>
),
}; };

View file

@ -26,7 +26,7 @@ class IconComponent extends React.PureComponent<Props> {
return __('Featured content. Earn rewards for watching.'); return __('Featured content. Earn rewards for watching.');
case ICONS.DOWNLOAD: case ICONS.DOWNLOAD:
return __('This file is downloaded.'); return __('This file is downloaded.');
case ICONS.SUBSCRIPTION: case ICONS.SUBSCRIBE:
return __('You are subscribed to this channel.'); return __('You are subscribed to this channel.');
case ICONS.SETTINGS: case ICONS.SETTINGS:
return __('Your settings.'); return __('Your settings.');

View file

@ -1,5 +1,5 @@
// @flow // @flow
import type { Node } from 'react'; import type { ElementRef } from 'react';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
@ -17,7 +17,7 @@ type Props = {
openModal: (id: string, { uri: string }) => void, openModal: (id: string, { uri: string }) => void,
claimIsMine: boolean, claimIsMine: boolean,
fileInfo: FileInfo, fileInfo: FileInfo,
viewerContainer: ?{ current: Node }, viewerContainer: { current: ElementRef<any> },
showFullscreen: boolean, showFullscreen: boolean,
}; };

View file

@ -22,7 +22,7 @@ export default function FileProperties(props: Props) {
return ( return (
<div className="file-properties"> <div className="file-properties">
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />} {isSubscribed && <Icon tooltip icon={icons.SUBSCRIBE} />}
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />} {!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />} {isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
<FilePrice hideFree uri={uri} /> <FilePrice hideFree uri={uri} />

View file

@ -1,4 +1,5 @@
// @flow // @flow
import type { ElementRef } from 'react';
import '@babel/polyfill'; import '@babel/polyfill';
import * as React from 'react'; import * as React from 'react';
@ -29,7 +30,7 @@ type Props = {
onFinishCb: ?() => void, onFinishCb: ?() => void,
savePosition: number => void, savePosition: number => void,
changeVolume: number => void, changeVolume: number => void,
viewerContainer: React.Ref, viewerContainer: { current: ElementRef<any> },
searchBarFocused: boolean, searchBarFocused: boolean,
}; };
@ -114,7 +115,9 @@ class MediaPlayer extends React.PureComponent<Props, State> {
componentWillUnmount() { componentWillUnmount() {
const mediaElement = this.mediaContainer.current.children[0]; const mediaElement = this.mediaContainer.current.children[0];
document.removeEventListener('keydown', this.handleKeyDown); // Temorarily removing for comments the keydown handler needs to know
// if a user is typing
// document.removeEventListener('keydown', this.handleKeyDown);
if (mediaElement) { if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlay); mediaElement.removeEventListener('click', this.togglePlay);
@ -128,11 +131,11 @@ class MediaPlayer extends React.PureComponent<Props, State> {
if (!searchBarFocused) { if (!searchBarFocused) {
// Handle fullscreen shortcut key (f) // Handle fullscreen shortcut key (f)
if (event.keyCode === F_KEYCODE) { if (event.keyCode === F_KEYCODE) {
this.toggleFullscreen(); // this.toggleFullscreen();
} }
// Handle toggle play // Handle toggle play
// @if TARGET='app' // @if TARGET='app'
this.togglePlay(event); // this.togglePlay(event);
// @endif // @endif
} }
}; };
@ -263,9 +266,6 @@ class MediaPlayer extends React.PureComponent<Props, State> {
this.renderFile(); this.renderFile();
} }
// @endif // @endif
// Fullscreen event for web and app
document.addEventListener('keydown', this.handleKeyDown);
} }
// @if TARGET='app' // @if TARGET='app'

View file

@ -1,4 +1,5 @@
// @flow // @flow
import type { ElementRef } from 'react';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
@ -52,7 +53,7 @@ type Props = {
nsfw: boolean, nsfw: boolean,
thumbnail: ?string, thumbnail: ?string,
isPlayableType: boolean, isPlayableType: boolean,
viewerContainer: React.Ref, viewerContainer: { current: ElementRef<any> },
}; };
class FileViewer extends React.PureComponent<Props> { class FileViewer extends React.PureComponent<Props> {
@ -126,7 +127,7 @@ class FileViewer extends React.PureComponent<Props> {
} }
this.props.cancelPlay(); this.props.cancelPlay();
window.removeEventListener('keydown', this.handleKeyDown); // window.removeEventListener('keydown', this.handleKeyDown);
} }
handleKeyDown(event: SyntheticKeyboardEvent<*>) { handleKeyDown(event: SyntheticKeyboardEvent<*>) {

View file

@ -91,11 +91,12 @@ const Header = (props: Props) => {
{__('Wallet')} {__('Wallet')}
</MenuItem> </MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}> <MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
<Icon aria-hidden icon={ICONS.UPLOAD} /> <Icon aria-hidden icon={ICONS.PUBLISH} />
{__('Publish')} {__('Publish')}
</MenuItem> </MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
<Menu> <Menu>
<MenuButton className="header__navigation-item menu__title"> <MenuButton className="header__navigation-item menu__title">
<Icon size={18} icon={ICONS.SETTINGS} /> <Icon size={18} icon={ICONS.SETTINGS} />

View file

@ -16,7 +16,7 @@ import AuthPage from 'page/auth';
import InvitePage from 'page/invite'; import InvitePage from 'page/invite';
import SubscriptionsPage from 'page/subscriptions'; import SubscriptionsPage from 'page/subscriptions';
import SearchPage from 'page/search'; import SearchPage from 'page/search';
import UserHistoryPage from 'page/userHistory'; import LibraryPage from 'page/library';
import WalletPage from 'page/wallet'; import WalletPage from 'page/wallet';
import NavigationHistory from 'page/navigationHistory'; import NavigationHistory from 'page/navigationHistory';
import TagsPage from 'page/tags'; import TagsPage from 'page/tags';
@ -24,6 +24,7 @@ import FollowingPage from 'page/following';
const Scroll = withRouter(function ScrollWrapper(props) { const Scroll = withRouter(function ScrollWrapper(props) {
const { pathname } = props.location; const { pathname } = props.location;
useEffect(() => { useEffect(() => {
// Auto scroll to the top of a window for new pages // Auto scroll to the top of a window for new pages
// The browser will handle scrolling if it needs to, but // The browser will handle scrolling if it needs to, but
@ -51,12 +52,12 @@ export default function AppRouter() {
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} /> <Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} /> <Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} /> <Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} /> <Route path={`/$/${PAGES.LIBRARY}`} exact component={LibraryPage} />
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} /> <Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} /> <Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} /> <Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={SubscriptionsPage} /> <Route path={`/$/${PAGES.FOLLOWING}`} exact component={SubscriptionsPage} />
<Route path={`/$/${PAGES.FOLLOWING}/edit`} exact component={FollowingPage} /> <Route path={`/$/${PAGES.FOLLOWING}/customize`} exact component={FollowingPage} />
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} /> <Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */} {/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} /> <Route path="/:claimName" exact component={ShowPage} />

View file

@ -26,7 +26,7 @@ const SearchOptions = (props: Props) => {
<div> <div>
<Button <Button
button="alt" button="alt"
label={__('FILTER')} label={__('Filter')}
iconRight={expanded ? ICONS.UP : ICONS.DOWN} iconRight={expanded ? ICONS.UP : ICONS.DOWN}
onClick={toggleSearchExpanded} onClick={toggleSearchExpanded}
/> />

View file

@ -34,23 +34,23 @@ function SideBar(props: Props) {
...buildLink(null, __('Home'), ICONS.HOME), ...buildLink(null, __('Home'), ICONS.HOME),
}, },
{ {
...buildLink(PAGES.FOLLOWING, __('Following'), ICONS.SUBSCRIPTION), ...buildLink(PAGES.FOLLOWING, __('Following'), ICONS.SUBSCRIBE),
}, },
{ {
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY), ...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
}, },
{ {
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISHED), ...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
}, },
].map(renderLink)} ].map(renderLink)}
<li> <li>
<Button <Button
navigate="/$/following/edit" navigate="/$/following/customize"
icon={ICONS.EDIT} icon={ICONS.EDIT}
className="navigation__link" className="navigation__link"
activeClass="navigation__link--active" activeClass="navigation__link--active"
label={__('Edit')} label={__('Customize')}
/> />
</li> </li>
</ul> </ul>

View file

@ -3,10 +3,9 @@ import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
import Tooltip from 'component/common/tooltip';
type Props = { type Props = {
claim: StreamClaim, claim: Claim,
onDone: () => void, onDone: () => void,
speechShareable: boolean, speechShareable: boolean,
isChannel: boolean, isChannel: boolean,
@ -27,21 +26,15 @@ class SocialShare extends React.PureComponent<Props> {
render() { render() {
const { claim, isChannel } = this.props; const { claim, isChannel } = this.props;
const { claim_id: claimId, name: claimName, channel_name: channelName } = claim; const { claim_id: claimId, name: claimName } = claim;
const { speechShareable, onDone } = this.props; const { speechShareable, onDone } = this.props;
const channelClaimId = claim.signing_channel && claim.signing_channel.claim_id; const signingChannel = claim.signing_channel;
const channelClaimId = signingChannel && signingChannel.claim_id;
const channelName = signingChannel && signingChannel.name;
const getSpeechUri = (): string => { const getLbryTvUri = (): string => {
if (isChannel) { return `${claimName}/${claimId}`;
// For channel claims, the channel name (@something) is in `claim.name`
return `${claimName}:${claimId}`;
} else {
// If it's for a regular claim, check if it has an associated channel
return channelName && channelClaimId
? `${channelName}:${channelClaimId}/${claimName}`
: `${claimId}/${claimName}`;
}
}; };
const getLbryUri = (): string => { const getLbryUri = (): string => {
@ -56,69 +49,57 @@ class SocialShare extends React.PureComponent<Props> {
} }
}; };
const speechPrefix = 'https://spee.ch/'; const lbryTvPrefix = 'https://beta.lbry.tv/';
const lbryPrefix = 'https://open.lbry.com/'; const lbryPrefix = 'https://open.lbry.com/';
const lbryUri = getLbryUri(); const lbryUri = getLbryUri();
const speechUri = getSpeechUri(); const lbryTvUri = getLbryTvUri();
const encodedLbryURL: string = `${lbryPrefix}${encodeURIComponent(lbryUri)}`; const encodedLbryURL: string = `${lbryPrefix}${encodeURIComponent(lbryUri)}`;
const lbryURL: string = `${lbryPrefix}${getLbryUri()}`; const lbryURL: string = `${lbryPrefix}${getLbryUri()}`;
const encodedLbryTvUrl = `${lbryTvPrefix}${encodeURIComponent(lbryTvUri)}`;
const lbryTvUrl = `${lbryTvPrefix}${lbryTvUri}`;
const encodedSpeechURL = `${speechPrefix}${encodeURIComponent(speechUri)}`; const shareOnFb = __('Share on Facebook');
const speechURL = `${speechPrefix}${speechUri}`; const shareOnTwitter = __('Share On Twitter');
return ( return (
<React.Fragment> <React.Fragment>
{speechShareable && ( {speechShareable && (
<div className="card__content"> <div className="card__content">
<label className="help">{__('Web link')}</label> <label className="card__subtitle">{__('Web link')}</label>
<CopyableText copyable={speechURL} /> <CopyableText copyable={lbryTvUrl} />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Tooltip label={__('Facebook')}> <Button
<Button icon={ICONS.FACEBOOK}
iconColor="blue" button="link"
icon={ICONS.FACEBOOK} description={shareOnFb}
button="alt" href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryTvUrl}`}
label={__('')} />
href={`https://facebook.com/sharer/sharer.php?u=${encodedSpeechURL}`} <Button
/> icon={ICONS.TWITTER}
</Tooltip> button="link"
<Tooltip label={__('Twitter')}> description={shareOnTwitter}
<Button href={`https://twitter.com/home?status=${encodedLbryTvUrl}`}
iconColor="blue" />
icon={ICONS.TWITTER} <Button icon={ICONS.WEB} button="link" description={__('View on lbry.tv')} href={`${lbryTvUrl}`} />
button="alt"
label={__('')}
href={`https://twitter.com/home?status=${encodedSpeechURL}`}
/>
</Tooltip>
<Tooltip label={__('View on Spee.ch')}>
<Button icon={ICONS.WEB} iconColor="blue" button="alt" label={__('')} href={`${speechURL}`} />
</Tooltip>
</div> </div>
</div> </div>
)} )}
<div className="card__content"> <div className="card__content">
<label className="help">{__('LBRY App link')}</label> <label className="card__subtitle">{__('LBRY App link')}</label>
<CopyableText copyable={lbryURL} noSnackbar /> <CopyableText copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Tooltip label={__('Facebook')}> <Button
<Button icon={ICONS.FACEBOOK}
iconColor="blue" button="link"
icon={ICONS.FACEBOOK} description={shareOnFb}
button="alt" href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`}
label={__('')} />
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`} <Button
/> icon={ICONS.TWITTER}
</Tooltip> button="link"
<Tooltip label={__('Twitter')}> description={shareOnTwitter}
<Button href={`https://twitter.com/home?status=${encodedLbryURL}`}
iconColor="blue" />
icon={ICONS.TWITTER}
button="alt"
label={__('')}
href={`https://twitter.com/home?status=${encodedLbryURL}`}
/>
</Tooltip>
</div> </div>
</div> </div>
<div className="card__actions"> <div className="card__actions">

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import Button from 'component/button'; import Button from 'component/button';
@ -32,18 +32,36 @@ export default function SubscribeButton(props: Props) {
showSnackBarOnSubscribe, showSnackBarOnSubscribe,
doToast, doToast,
} = props; } = props;
const buttonRef = useRef();
const [isHovering, setIsHovering] = useState(false);
const { claimName } = parseURI(uri);
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe; const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const subscriptionLabel = isSubscribed ? __('Following') : __('Follow'); const subscriptionLabel = isSubscribed ? __('Following') : __('Follow');
const unfollowOverride = isSubscribed && isHovering && __('Unfollow');
const { claimName } = parseURI(uri); useEffect(() => {
function handleHover() {
setIsHovering(!isHovering);
}
const button = buttonRef.current;
if (button) {
button.addEventListener('mouseover', handleHover);
button.addEventListener('mouseleave', handleHover);
return () => {
button.removeEventListener('mouseover', handleHover);
button.removeEventListener('mouseleave', handleHover);
};
}
}, [buttonRef, isHovering]);
return ( return (
<Button <Button
ref={buttonRef}
iconColor="red" iconColor="red"
icon={ICONS.SUBSCRIPTION} icon={unfollowOverride ? ICONS.UNSUBSCRIBE : ICONS.SUBSCRIBE}
button={'alt'} button={'alt'}
label={subscriptionLabel} label={unfollowOverride || subscriptionLabel}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();

View file

@ -10,8 +10,7 @@ export const COPY = 'Clipboard';
export const ARROW_LEFT = 'ChevronLeft'; export const ARROW_LEFT = 'ChevronLeft';
export const ARROW_RIGHT = 'ChevronRight'; export const ARROW_RIGHT = 'ChevronRight';
export const DOWNLOAD = 'Download'; export const DOWNLOAD = 'Download';
export const UPLOAD = 'UploadCloud'; export const PUBLISH = 'UploadCloud';
export const PUBLISHED = 'Cloud';
export const REMOVE = 'X'; export const REMOVE = 'X';
export const ADD = 'Plus'; export const ADD = 'Plus';
export const EDIT = 'Edit'; export const EDIT = 'Edit';
@ -29,7 +28,7 @@ export const WALLET = 'List';
export const PHONE = 'Phone'; export const PHONE = 'Phone';
export const COMPLETE = 'Check'; export const COMPLETE = 'Check';
export const COMPLETED = 'CheckCircle'; export const COMPLETED = 'CheckCircle';
export const SUBSCRIPTION = 'Heart'; export const SUBSCRIBE = 'Heart';
export const UNSUBSCRIBE = 'BrokenHeart'; export const UNSUBSCRIBE = 'BrokenHeart';
export const UNLOCK = 'Unlock'; export const UNLOCK = 'Unlock';
export const WEB = 'Globe'; export const WEB = 'Globe';

View file

@ -77,7 +77,7 @@ function ChannelPage(props: Props) {
</div> </div>
</TabList> </TabList>
<TabPanels className="channel__data"> <TabPanels>
<TabPanel> <TabPanel>
<ChannelContent uri={uri} /> <ChannelContent uri={uri} />
</TabPanel> </TabPanel>

View file

@ -10,6 +10,7 @@ type Props = {
function DiscoverPage(props: Props) { function DiscoverPage(props: Props) {
const { followedTags } = props; const { followedTags } = props;
return ( return (
<Page> <Page>
<ClaimListDiscover <ClaimListDiscover

View file

@ -18,7 +18,13 @@ function FileListDownloaded(props: Props) {
<React.Fragment> <React.Fragment>
{hasDownloads ? ( {hasDownloads ? (
<div className="card"> <div className="card">
<ClaimList persistedStorageKey="claim-list-downloaded" uris={downloadedUris} loading={fetching} /> <ClaimList
header={<h1>{__('Your Library')}</h1>}
defaultSort
persistedStorageKey="claim-list-downloaded"
uris={downloadedUris}
loading={fetching}
/>
</div> </div>
) : ( ) : (
<div className="main--empty"> <div className="main--empty">

View file

@ -21,7 +21,14 @@ function FileListPublished(props: Props) {
<Page notContained> <Page notContained>
{uris && uris.length ? ( {uris && uris.length ? (
<div className="card"> <div className="card">
<ClaimList loading={fetching} persistedStorageKey="claim-list-published" uris={uris} /> <ClaimList
header={<h1>{__('Your Publishes')}</h1>}
loading={fetching}
persistedStorageKey="claim-list-published"
uris={uris}
defaultSort
headerAltControls={<Button button="link" label={__('New Publish')} navigate="/$/publish" />}
/>
</div> </div>
) : ( ) : (
<div className="main--empty"> <div className="main--empty">

View file

@ -14,10 +14,10 @@ function FollowingEditPage(props: Props) {
return ( return (
<Page> <Page>
<div className="card"> <div className="card">
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} /> <TagsSelect showClose={false} title={__('Customize Your Tags')} />
</div> </div>
<div className="card"> <div className="card">
<ClaimList uris={channelUris} /> <ClaimList header={<h1>{__('Channels You Follow')}</h1>} uris={channelUris} />
</div> </div>
</Page> </Page>
); );

View file

@ -0,0 +1,3 @@
import LibraryPage from './view';
export default LibraryPage;

View file

@ -0,0 +1,14 @@
// @flow
import React from 'react';
import Page from 'component/page';
import DownloadList from 'page/fileListDownloaded';
function LibraryPage() {
return (
<Page>
<DownloadList />
</Page>
);
}
export default LibraryPage;

View file

@ -3,6 +3,7 @@ import * as PAGES from 'constants/pages';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import Page from 'component/page'; import Page from 'component/page';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import ClaimPreview from 'component/claimPreview';
import Button from 'component/button'; import Button from 'component/button';
type Props = { type Props = {
@ -79,6 +80,7 @@ export default function SubscriptionsPage(props: Props) {
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris} uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
onScrollBottom={() => console.log('scroll bottom') || setPage(page + 1)} onScrollBottom={() => console.log('scroll bottom') || setPage(page + 1)}
/> />
{loading && page > 1 && new Array(20).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
</div> </div>
</Page> </Page>
); );

View file

@ -32,9 +32,9 @@ function TagsPage(props: Props) {
tags={tags} tags={tags}
meta={ meta={
<Button <Button
button="alt" button="link"
onClick={() => doToggleTagFollow(tag)} onClick={() => doToggleTagFollow(tag)}
label={isFollowing ? __('Unfollow this tag') : __('Follow this tag')} label={isFollowing ? __('Following') : __('Follow')}
/> />
} }
/> />

View file

@ -1,3 +0,0 @@
import UserHistoryPage from './view';
export default UserHistoryPage;

View file

@ -1,17 +0,0 @@
// @flow
import React from 'react';
import Page from 'component/page';
import DownloadList from 'page/fileListDownloaded';
type Props = {};
class UserHistoryPage extends React.PureComponent<Props> {
render() {
return (
<Page>
<DownloadList {...this.props} />
</Page>
);
}
}
export default UserHistoryPage;

View file

@ -257,6 +257,7 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
tags: Array<string>, tags: Array<string>,
locations?: Array<Location>, locations?: Array<Location>,
license_url?: string, license_url?: string,
license?: string,
thumbnail_url?: string, thumbnail_url?: string,
release_time?: number, release_time?: number,
fee_currency?: string, fee_currency?: string,
@ -267,13 +268,19 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
description, description,
locations, locations,
bid: creditsToString(bid), bid: creditsToString(bid),
license: publishingLicense,
languages: [language], languages: [language],
tags: tags && tags.map(tag => tag.name), tags: tags && tags.map(tag => tag.name),
license_url: licenseType === COPYRIGHT ? '' : licenseUrl,
thumbnail_url: thumbnail, thumbnail_url: thumbnail,
}; };
if (publishingLicense) {
publishPayload.license = publishingLicense;
}
if (licenseUrl) {
publishPayload.license_url = licenseUrl;
}
if (myClaimForUri && myClaimForUri.value.release_time) { if (myClaimForUri && myClaimForUri.value.release_time) {
publishPayload.release_time = Number(myClaimForUri.value.release_time); publishPayload.release_time = Number(myClaimForUri.value.release_time);
} }

View file

@ -93,8 +93,3 @@ $metadata-z-index: 1;
margin-top: -0.25rem; margin-top: -0.25rem;
color: rgba($lbry-white, 0.75); color: rgba($lbry-white, 0.75);
} }
// TODO: rename
.channel__data {
min-height: 10rem;
}

View file

@ -65,7 +65,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: auto; margin-left: auto;
font-size: 1.4em; font-size: 1.1em;
& > * { & > * {
margin-left: var(--spacing-small); margin-left: var(--spacing-small);
@ -96,7 +96,7 @@
} }
.claim-preview--injected, .claim-preview--injected,
.claim-preview + .claim-preview { .claim-preview {
border-bottom: 1px solid rgba($lbry-teal-5, 0.1); border-bottom: 1px solid rgba($lbry-teal-5, 0.1);
} }
@ -158,8 +158,3 @@
.claim-preview-tags { .claim-preview-tags {
margin-left: 0; margin-left: 0;
} }
.claim-list__meta {
padding: var(--spacing-medium);
background-color: lighten($lbry-teal-5, 55%);
}

View file

@ -8,12 +8,17 @@
box-shadow: var(--card-box-shadow) $lbry-gray-1; box-shadow: var(--card-box-shadow) $lbry-gray-1;
padding-left: var(--spacing-large); padding-left: var(--spacing-large);
padding-right: var(--spacing-large); padding-right: var(--spacing-large);
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: mix($lbry-black, $lbry-gray-3, 90%); background-color: mix($lbry-black, $lbry-gray-3, 90%);
color: $lbry-white; color: $lbry-white;
border-bottom: none; border-bottom: none;
box-shadow: var(--card-box-shadow) $lbry-black; box-shadow: var(--card-box-shadow) $lbry-black;
} }
& > * {
user-select: none;
}
} }
.header__contents { .header__contents {
@ -49,10 +54,6 @@
align-items: center; align-items: center;
border-radius: 0; border-radius: 0;
svg {
stroke: $lbry-gray-5;
}
&:hover { &:hover {
color: $lbry-teal-5; color: $lbry-teal-5;

View file

@ -6,7 +6,6 @@
padding-left: var(--spacing-large); padding-left: var(--spacing-large);
padding-right: var(--spacing-large); padding-right: var(--spacing-large);
padding-bottom: var(--spacing-large); padding-bottom: var(--spacing-large);
background-color: mix($lbry-white, $lbry-gray-1, 70%);
display: flex; display: flex;
[data-mode='dark'] & { [data-mode='dark'] & {

View file

@ -7,6 +7,7 @@
.navigation { .navigation {
width: var(--side-nav-width); width: var(--side-nav-width);
padding-bottom: var(--spacing-main-padding);
font-size: 1.4rem; font-size: 1.4rem;
@media (max-width: 600px) { @media (max-width: 600px) {

View file

@ -13,6 +13,10 @@
.search__options { .search__options {
margin-top: var(--spacing-large); margin-top: var(--spacing-large);
.button {
font-size: 2rem;
}
legend { legend {
&.search__legend--1 { &.search__legend--1 {
background-color: $lbry-teal-4; background-color: $lbry-teal-4;

View file

@ -57,10 +57,6 @@ $main: $lbry-teal-5;
&:active { &:active {
background-color: $main; background-color: $main;
} }
&:focus {
@include focus;
}
} }
.tag--remove { .tag--remove {

View file

@ -1,15 +1,9 @@
// Generic html styles used accross the App // Generic html styles used accross the App
// component specific styling should go in the component scss file // component specific styling should go in the component scss file
*,
*::before,
*::after {
-webkit-user-select: text;
}
html { html {
@include font-sans; @include font-sans;
background-color: $lbry-white;
font-size: 12px; font-size: 12px;
height: 100%; height: 100%;
overflow-x: hidden; overflow-x: hidden;
@ -23,6 +17,7 @@ body {
height: 100%; height: 100%;
line-height: 1.5; line-height: 1.5;
overflow: hidden; overflow: hidden;
background-color: mix($lbry-white, $lbry-gray-1, 70%);
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: $lbry-black; background-color: $lbry-black;

View file

@ -20,10 +20,7 @@ html {
user-select: none; user-select: none;
} }
a,
area, area,
button,
[role='button'],
input, input,
label, label,
select, select,