so much discovery I can't take it #2617
29 changed files with 329 additions and 186 deletions
|
@ -158,6 +158,7 @@
|
||||||
"react-router-dom": "^5.0.0",
|
"react-router-dom": "^5.0.0",
|
||||||
"react-simplemde-editor": "^4.0.0",
|
"react-simplemde-editor": "^4.0.0",
|
||||||
"react-spring": "^8.0.20",
|
"react-spring": "^8.0.20",
|
||||||
|
"react-sticky-box": "^0.8.0",
|
||||||
"react-toggle": "^4.0.2",
|
"react-toggle": "^4.0.2",
|
||||||
"redux": "^3.6.0",
|
"redux": "^3.6.0",
|
||||||
"redux-persist": "^4.8.0",
|
"redux-persist": "^4.8.0",
|
||||||
|
|
|
@ -51,14 +51,12 @@ function App(props: Props) {
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
<div className="main-wrapper" ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<div className={MAIN_WRAPPER_CLASS}>
|
<div className="main-wrapper__inner">
|
||||||
<div className="main-wrapper-inner">
|
<Router />
|
||||||
<Router />
|
<SideBar />
|
||||||
<SideBar />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ModalRouter />
|
<ModalRouter />
|
||||||
|
|
|
@ -118,14 +118,14 @@ function ChannelForm(props: Props) {
|
||||||
onUpdate={v => handleThumbnailChange(v)}
|
onUpdate={v => handleThumbnailChange(v)}
|
||||||
currentValue={params.thumbnail}
|
currentValue={params.thumbnail}
|
||||||
assetName={'Thumbnail'}
|
assetName={'Thumbnail'}
|
||||||
recommended={'(400x400)'}
|
recommended={'(300 x 300)'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectAsset
|
<SelectAsset
|
||||||
onUpdate={v => handleCoverChange(v)}
|
onUpdate={v => handleCoverChange(v)}
|
||||||
currentValue={params.cover}
|
currentValue={params.cover}
|
||||||
assetName={'Cover'}
|
assetName={'Cover'}
|
||||||
recommended={'(1000x300)'}
|
recommended={'(1000 x 160)'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
@ -39,18 +39,16 @@ export default function ClaimList(props: Props) {
|
||||||
type,
|
type,
|
||||||
header,
|
header,
|
||||||
onScrollBottom,
|
onScrollBottom,
|
||||||
page,
|
|
||||||
pageSize,
|
pageSize,
|
||||||
} = props;
|
} = props;
|
||||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||||
const hasUris = uris && !!uris.length;
|
const urisLength = (uris && uris.length) || 0;
|
||||||
const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
|
const sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
|
||||||
|
|
||||||
function handleSortChange() {
|
function handleSortChange() {
|
||||||
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) {
|
function handleScroll(e) {
|
||||||
if (pageSize && onScrollBottom) {
|
if (pageSize && onScrollBottom) {
|
||||||
|
@ -71,7 +69,7 @@ export default function ClaimList(props: Props) {
|
||||||
window.removeEventListener('scroll', handleScroll);
|
window.removeEventListener('scroll', handleScroll);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [loading, onScrollBottom, urisLength]);
|
}, [loading, onScrollBottom, urisLength, pageSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
|
@ -100,17 +98,17 @@ export default function ClaimList(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasUris && (
|
{urisLength > 0 && (
|
||||||
<ul>
|
<ul>
|
||||||
{sortedUris.map((uri, index) => (
|
{sortedUris.map((uri, index) => (
|
||||||
<React.Fragment key={uri}>
|
<React.Fragment key={uri}>
|
||||||
<ClaimPreview uri={uri} type={type} placeholder={loading && (!page || page === 1)} />
|
<ClaimPreview uri={uri} type={type} />
|
||||||
{index === 4 && injectedItem && <li className="claim-preview--injected">{injectedItem}</li>}
|
{index === 4 && injectedItem && <li className="claim-preview--injected">{injectedItem}</li>}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{!hasUris && !loading && <h2 className="main--empty empty">{empty || __('No results')}</h2>}
|
{urisLength === 0 && !loading && <h2 className="main--empty empty">{empty || __('No results')}</h2>}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
|
import { doClaimSearch, selectClaimSearchByQuery, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import ClaimListDiscover from './view';
|
import ClaimListDiscover from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
uris: selectLastClaimSearchUris(state),
|
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||||
loading: selectFetchingClaimSearch(state),
|
loading: selectFetchingClaimSearch(state),
|
||||||
subscribedChannels: selectSubscriptions(state),
|
subscribedChannels: selectSubscriptions(state),
|
||||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import moment from 'moment';
|
import { withRouter } from 'react-router';
|
||||||
import usePersistedState from 'util/use-persisted-state';
|
import { buildClaimSearchCacheQuery, MATURE_TAGS } from 'lbry-redux';
|
||||||
import { MATURE_TAGS } from 'lbry-redux';
|
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
|
import moment from 'moment';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
|
import { updateQueryParam } from 'util/query-params';
|
||||||
|
import { toCapitalCase } from 'util/string';
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 20;
|
||||||
const TIME_DAY = 'day';
|
const TIME_DAY = 'day';
|
||||||
|
@ -33,75 +35,118 @@ type Props = {
|
||||||
injectedItem: any,
|
injectedItem: any,
|
||||||
tags: Array<string>,
|
tags: Array<string>,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
personal: boolean,
|
personalView: boolean,
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
showNsfw: boolean,
|
showNsfw: boolean,
|
||||||
|
history: { action: string, push: string => void, replace: string => void },
|
||||||
|
location: { search: string, pathname: string },
|
||||||
|
claimSearchByQuery: {
|
||||||
|
[string]: Array<string>,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimListDiscover(props: Props) {
|
function ClaimListDiscover(props: Props) {
|
||||||
const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta, subscribedChannels, showNsfw } = props;
|
const {
|
||||||
const [personalSort, setPersonalSort] = usePersistedState('claim-list-discover:personalSort', SEARCH_SORT_YOU);
|
doClaimSearch,
|
||||||
const [typeSort, setTypeSort] = usePersistedState('claim-list-discover:typeSort', TYPE_TRENDING);
|
claimSearchByQuery,
|
||||||
const [timeSort, setTimeSort] = usePersistedState('claim-list-discover:timeSort', TIME_WEEK);
|
tags,
|
||||||
const [page, setPage] = useState(1);
|
loading,
|
||||||
|
personalView,
|
||||||
|
injectedItem,
|
||||||
|
meta,
|
||||||
|
subscribedChannels,
|
||||||
|
showNsfw,
|
||||||
|
history,
|
||||||
|
location,
|
||||||
|
} = props;
|
||||||
|
const didNavigateForward = history.action === 'PUSH';
|
||||||
|
const { search, pathname } = location;
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const personalSort = urlParams.get('sort') || SEARCH_SORT_YOU;
|
||||||
|
const typeSort = urlParams.get('type') || TYPE_TRENDING;
|
||||||
|
const timeSort = urlParams.get('time') || TIME_WEEK;
|
||||||
|
const page = Number(urlParams.get('page')) || 1;
|
||||||
|
const tagsInUrl = urlParams.get('t') || '';
|
||||||
|
const url = `${pathname}${search}`;
|
||||||
|
const options: {
|
||||||
|
page_size: number,
|
||||||
|
page: number,
|
||||||
|
no_totals: boolean,
|
||||||
|
any_tags: Array<string>,
|
||||||
|
channel_ids: Array<string>,
|
||||||
|
not_tags: Array<string>,
|
||||||
|
order_by: Array<string>,
|
||||||
|
release_time?: string,
|
||||||
|
} = {
|
||||||
|
page_size: PAGE_SIZE,
|
||||||
|
page,
|
||||||
|
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
||||||
|
// it's faster, but we will need to remove it if we start using total_pages
|
||||||
|
no_totals: true,
|
||||||
|
any_tags: (personalView && personalSort === SEARCH_SORT_YOU) || !personalView ? tags : [],
|
||||||
|
channel_ids: personalSort === SEARCH_SORT_CHANNELS ? subscribedChannels.map(sub => sub.uri.split('#')[1]) : [],
|
||||||
|
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||||
|
order_by:
|
||||||
|
typeSort === TYPE_TRENDING
|
||||||
|
? ['trending_global', 'trending_mixed']
|
||||||
|
: typeSort === TYPE_NEW
|
||||||
|
? ['release_time']
|
||||||
|
: ['effective_amount'], // Sort by top
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeSort === TYPE_TOP && timeSort !== TIME_ALL) {
|
||||||
|
options.release_time = `>${Math.floor(
|
||||||
|
moment()
|
||||||
|
.subtract(1, timeSort)
|
||||||
|
.unix()
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimSearchCacheQuery = buildClaimSearchCacheQuery(options);
|
||||||
|
const uris = claimSearchByQuery[claimSearchCacheQuery] || [];
|
||||||
|
const shouldPerformSearch = uris.length === 0 || didNavigateForward || (!loading && uris.length < PAGE_SIZE * page);
|
||||||
|
// Don't use the query from buildClaimSearchCacheQuery for the effect since that doesn't include page & release_time
|
||||||
|
const optionsStringForEffect = JSON.stringify(options);
|
||||||
|
|
||||||
|
function getSearch() {
|
||||||
|
let search = `?`;
|
||||||
|
if (!personalView) {
|
||||||
|
search += `t=${tagsInUrl}&`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return search;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTypeSort(newTypeSort) {
|
||||||
|
let url = `${getSearch()}type=${newTypeSort}&sort=${personalSort}`;
|
||||||
|
if (newTypeSort === TYPE_TOP) {
|
||||||
|
url += `&time=${timeSort}`;
|
||||||
|
}
|
||||||
|
history.push(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePersonalSort(newPersonalSort) {
|
||||||
|
history.push(`${getSearch()}type=${typeSort}&sort=${newPersonalSort}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTimeSort(newTimeSort) {
|
||||||
|
history.push(`${getSearch()}type=${typeSort}&sort=${personalSort}&time=${newTimeSort}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScrollBottom() {
|
||||||
|
if (!loading) {
|
||||||
|
const uri = updateQueryParam(url, 'page', page + 1);
|
||||||
|
history.replace(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
const tagsString = tags.join(',');
|
|
||||||
const channelsIdString = subscribedChannels.map(channel => channel.uri.split('#')[1]).join(',');
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const options: {
|
if (shouldPerformSearch) {
|
||||||
page_size: number,
|
const searchOptions = JSON.parse(optionsStringForEffect);
|
||||||
any_tags?: Array<string>,
|
doClaimSearch(searchOptions);
|
||||||
order_by?: Array<string>,
|
|
||||||
channel_ids?: Array<string>,
|
|
||||||
release_time?: string,
|
|
||||||
not_tags?: Array<string>,
|
|
||||||
} = { page_size: PAGE_SIZE, page, no_totals: true };
|
|
||||||
const newTags = tagsString.split(',');
|
|
||||||
const newChannelIds = channelsIdString.split(',');
|
|
||||||
|
|
||||||
if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
|
|
||||||
options.any_tags = newTags;
|
|
||||||
} else if (personalSort === SEARCH_SORT_CHANNELS) {
|
|
||||||
options.channel_ids = newChannelIds;
|
|
||||||
}
|
}
|
||||||
|
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect]);
|
||||||
if (!showNsfw) {
|
|
||||||
options.not_tags = MATURE_TAGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeSort === TYPE_TRENDING) {
|
|
||||||
options.order_by = ['trending_global', 'trending_mixed'];
|
|
||||||
} else if (typeSort === TYPE_NEW) {
|
|
||||||
options.order_by = ['release_time'];
|
|
||||||
} else if (typeSort === TYPE_TOP) {
|
|
||||||
options.order_by = ['effective_amount'];
|
|
||||||
if (timeSort !== TIME_ALL) {
|
|
||||||
const time = Math.floor(
|
|
||||||
moment()
|
|
||||||
.subtract(1, timeSort)
|
|
||||||
.unix()
|
|
||||||
);
|
|
||||||
options.release_time = `>${time}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.page_size = PAGE_SIZE;
|
|
||||||
doClaimSearch(options);
|
|
||||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString, channelsIdString, showNsfw]);
|
|
||||||
|
|
||||||
function getLabel(type) {
|
|
||||||
if (type === SEARCH_SORT_ALL) {
|
|
||||||
return __('Everyone');
|
|
||||||
}
|
|
||||||
|
|
||||||
return type === SEARCH_SORT_YOU ? __('Tags You Follow') : __('Channels You Follow');
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetList() {
|
|
||||||
setPage(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<h1 className="card__title--flex">
|
<h1 className="card__title--flex">
|
||||||
|
@ -110,10 +155,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
type="select"
|
type="select"
|
||||||
name="trending_sort"
|
name="trending_sort"
|
||||||
value={typeSort}
|
value={typeSort}
|
||||||
onChange={e => {
|
onChange={e => handleTypeSort(e.target.value)}
|
||||||
resetList();
|
|
||||||
setTypeSort(e.target.value);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{SEARCH_TYPES.map(type => (
|
{SEARCH_TYPES.map(type => (
|
||||||
<option key={type} value={type}>
|
<option key={type} value={type}>
|
||||||
|
@ -122,7 +164,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
))}
|
))}
|
||||||
</FormField>
|
</FormField>
|
||||||
<span>{__('For')}</span>
|
<span>{__('For')}</span>
|
||||||
{!personal && tags && tags.length ? (
|
{!personalView && tags && tags.length ? (
|
||||||
tags.map(tag => <Tag key={tag} name={tag} disabled />)
|
tags.map(tag => <Tag key={tag} name={tag} disabled />)
|
||||||
) : (
|
) : (
|
||||||
<FormField
|
<FormField
|
||||||
|
@ -131,13 +173,16 @@ function ClaimListDiscover(props: Props) {
|
||||||
className="claim-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
value={personalSort}
|
value={personalSort}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
resetList();
|
handlePersonalSort(e.target.value);
|
||||||
setPersonalSort(e.target.value);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{SEARCH_FILTER_TYPES.map(type => (
|
{SEARCH_FILTER_TYPES.map(type => (
|
||||||
<option key={type} value={type}>
|
<option key={type} value={type}>
|
||||||
{getLabel(type)}
|
{type === SEARCH_SORT_ALL
|
||||||
|
? __('Everyone')
|
||||||
|
: type === SEARCH_SORT_YOU
|
||||||
|
? __('Tags You Follow')
|
||||||
|
: __('Channels You Follow')}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</FormField>
|
</FormField>
|
||||||
|
@ -148,10 +193,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
type="select"
|
type="select"
|
||||||
name="trending_time"
|
name="trending_time"
|
||||||
value={timeSort}
|
value={timeSort}
|
||||||
onChange={e => {
|
onChange={e => handleTimeSort(e.target.value)}
|
||||||
resetList();
|
|
||||||
setTimeSort(e.target.value);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{SEARCH_TIMES.map(time => (
|
{SEARCH_TIMES.map(time => (
|
||||||
<option key={time} value={time}>
|
<option key={time} value={time}>
|
||||||
|
@ -174,14 +216,14 @@ function ClaimListDiscover(props: Props) {
|
||||||
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
||||||
header={header}
|
header={header}
|
||||||
headerAltControls={meta}
|
headerAltControls={meta}
|
||||||
onScrollBottom={() => setPage(page + 1)}
|
onScrollBottom={handleScrollBottom}
|
||||||
page={page}
|
page={page}
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && page > 1 && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
|
{loading && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ClaimListDiscover;
|
export default withRouter(ClaimListDiscover);
|
||||||
|
|
|
@ -100,13 +100,13 @@ function ClaimPreview(props: Props) {
|
||||||
if (isValid && !isResolvingUri && haventFetched && uri) {
|
if (isValid && !isResolvingUri && haventFetched && uri) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
}, [isResolvingUri, uri, resolveUri, haventFetched]);
|
}, [isValid, isResolvingUri, uri, resolveUri, haventFetched]);
|
||||||
|
|
||||||
if (shouldHide) {
|
if (shouldHide) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (placeholder || isResolvingUri) {
|
if (placeholder || (isResolvingUri && !claim)) {
|
||||||
return (
|
return (
|
||||||
<li className="claim-preview" disabled>
|
<li className="claim-preview" disabled>
|
||||||
<div className="placeholder media__thumb" />
|
<div className="placeholder media__thumb" />
|
||||||
|
|
|
@ -30,6 +30,7 @@ export function CommentCreate(props: Props) {
|
||||||
function handleCommentAck(event) {
|
function handleCommentAck(event) {
|
||||||
setCommentAck(true);
|
setCommentAck(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
if (channel !== CHANNEL_NEW && commentValue.length) createComment(commentValue, claimId, channel);
|
if (channel !== CHANNEL_NEW && commentValue.length) createComment(commentValue, claimId, channel);
|
||||||
setCommentValue('');
|
setCommentValue('');
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Props = {
|
||||||
isUpgradeAvailable: boolean,
|
isUpgradeAvailable: boolean,
|
||||||
roundedBalance: number,
|
roundedBalance: number,
|
||||||
downloadUpgradeRequested: any => void,
|
downloadUpgradeRequested: any => void,
|
||||||
history: { push: string => void },
|
history: { push: string => void, goBack: () => void, goForward: () => void },
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
automaticDarkModeEnabled: boolean,
|
automaticDarkModeEnabled: boolean,
|
||||||
setClientSetting: (string, boolean | string) => void,
|
setClientSetting: (string, boolean | string) => void,
|
||||||
|
@ -51,7 +51,7 @@ const Header = (props: Props) => {
|
||||||
<Button
|
<Button
|
||||||
className="header__navigation-item header__navigation-item--back"
|
className="header__navigation-item header__navigation-item--back"
|
||||||
description={__('Navigate back')}
|
description={__('Navigate back')}
|
||||||
onClick={() => window.history.back()}
|
onClick={() => history.goBack()}
|
||||||
icon={ICONS.ARROW_LEFT}
|
icon={ICONS.ARROW_LEFT}
|
||||||
iconSize={18}
|
iconSize={18}
|
||||||
/>
|
/>
|
||||||
|
@ -59,7 +59,7 @@ const Header = (props: Props) => {
|
||||||
<Button
|
<Button
|
||||||
className="header__navigation-item header__navigation-item--forward"
|
className="header__navigation-item header__navigation-item--forward"
|
||||||
description={__('Navigate forward')}
|
description={__('Navigate forward')}
|
||||||
onClick={() => window.history.forward()}
|
onClick={() => history.goForward()}
|
||||||
icon={ICONS.ARROW_RIGHT}
|
icon={ICONS.ARROW_RIGHT}
|
||||||
iconSize={18}
|
iconSize={18}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,2 +1,10 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import Router from './view';
|
import Router from './view';
|
||||||
export default Router;
|
|
||||||
|
const select = state => ({
|
||||||
|
scroll: state.app.scrollHistory[state.app.scrollHistory.length - 1],
|
||||||
|
scrollHistory: state.app.scrollHistory,
|
||||||
|
currentScroll: state.app.currentScroll || 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select)(Router);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
|
import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
|
||||||
|
@ -21,49 +22,56 @@ import NavigationHistory from 'page/navigationHistory';
|
||||||
import TagsPage from 'page/tags';
|
import TagsPage from 'page/tags';
|
||||||
import FollowingPage from 'page/following';
|
import FollowingPage from 'page/following';
|
||||||
|
|
||||||
const Scroll = withRouter(function ScrollWrapper(props) {
|
// Let app handle scroll
|
||||||
const { pathname } = props.location;
|
if ('scrollRestoration' in history) {
|
||||||
|
history.scrollRestoration = 'manual';
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentScroll: number,
|
||||||
|
location: { pathname: string, search: string },
|
||||||
|
};
|
||||||
|
|
||||||
|
function AppRouter(props: Props) {
|
||||||
|
const { currentScroll, location } = props;
|
||||||
|
const { pathname, search } = location;
|
||||||
|
|
||||||
|
// Don't update the scroll position if only the `page` param changes
|
||||||
|
const url = `${pathname}${search.replace(/&?\??page=\d+/, '')}`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Auto scroll to the top of a window for new pages
|
window.scrollTo(0, currentScroll);
|
||||||
// The browser will handle scrolling if it needs to, but
|
}, [currentScroll, url]);
|
||||||
// for new pages, react-router maintains the current y scroll position
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
return props.children;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function AppRouter() {
|
|
||||||
return (
|
return (
|
||||||
<Scroll>
|
<Switch>
|
||||||
<Switch>
|
<Route path="/" exact component={DiscoverPage} />
|
||||||
<Route path="/" exact component={DiscoverPage} />
|
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
||||||
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
|
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
|
||||||
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
|
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
|
||||||
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
|
<Route path={`/$/${PAGES.PUBLISHED}`} exact component={FileListPublished} />
|
||||||
<Route path={`/$/${PAGES.PUBLISHED}`} exact component={FileListPublished} />
|
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
<Route path={`/$/${PAGES.PUBLISH}`} exact component={PublishPage} />
|
||||||
<Route path={`/$/${PAGES.PUBLISH}`} exact component={PublishPage} />
|
<Route path={`/$/${PAGES.REPORT}`} exact component={ReportPage} />
|
||||||
<Route path={`/$/${PAGES.REPORT}`} exact component={ReportPage} />
|
<Route path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
||||||
<Route path={`/$/${PAGES.REWARDS}`} exact component={RewardsPage} />
|
<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={LibraryPage} />
|
||||||
<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={FollowingPage} />
|
||||||
<Route path={`/$/${PAGES.FOLLOWING}`} 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} />
|
<Route path="/:claimName/:contentName" exact component={ShowPage} />
|
||||||
<Route path="/:claimName/:contentName" exact component={ShowPage} />
|
|
||||||
|
|
||||||
{/* Route not found. Mostly for people typing crazy urls into the url */}
|
{/* Route not found. Mostly for people typing crazy urls into the url */}
|
||||||
<Route render={() => <Redirect to="/" />} />
|
<Route render={() => <Redirect to="/" />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Scroll>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withRouter(AppRouter);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
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 * as React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
|
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
subscriptions: Array<Subscription>,
|
subscriptions: Array<Subscription>,
|
||||||
|
@ -12,21 +13,18 @@ type Props = {
|
||||||
|
|
||||||
function SideBar(props: Props) {
|
function SideBar(props: Props) {
|
||||||
const { subscriptions, followedTags } = props;
|
const { subscriptions, followedTags } = props;
|
||||||
const buildLink = (path, label, icon, guide) => ({
|
|
||||||
navigate: path ? `$/${path}` : '/',
|
|
||||||
label,
|
|
||||||
icon,
|
|
||||||
guide,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderLink = linkProps => (
|
function buildLink(path, label, icon, guide) {
|
||||||
<li key={linkProps.label}>
|
return {
|
||||||
<Button {...linkProps} className="navigation__link" activeClass="navigation__link--active" />
|
navigate: path ? `$/${path}` : '/',
|
||||||
</li>
|
label,
|
||||||
);
|
icon,
|
||||||
|
guide,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="navigation-wrapper">
|
<StickyBox offsetBottom={40} offsetTop={100}>
|
||||||
<nav className="navigation">
|
<nav className="navigation">
|
||||||
<ul className="navigation__links">
|
<ul className="navigation__links">
|
||||||
{[
|
{[
|
||||||
|
@ -39,7 +37,14 @@ function SideBar(props: Props) {
|
||||||
{
|
{
|
||||||
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
|
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
|
||||||
},
|
},
|
||||||
].map(renderLink)}
|
{
|
||||||
|
...buildLink(PAGES.FOLLOWING, __('Customize'), ICONS.EDIT),
|
||||||
|
},
|
||||||
|
].map(linkProps => (
|
||||||
|
<li key={linkProps.label}>
|
||||||
|
<Button {...linkProps} className="navigation__link" activeClass="navigation__link--active" />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="navigation__links tags--vertical">
|
<ul className="navigation__links tags--vertical">
|
||||||
{followedTags.map(({ name }, key) => (
|
{followedTags.map(({ name }, key) => (
|
||||||
|
@ -61,7 +66,7 @@ function SideBar(props: Props) {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</StickyBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,8 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.props.onReadyToLaunch();
|
||||||
|
|
||||||
const { checkDaemonVersion } = this.props;
|
const { checkDaemonVersion } = this.props;
|
||||||
this.adjustErrorTimeout();
|
this.adjustErrorTimeout();
|
||||||
Lbry.connect()
|
Lbry.connect()
|
||||||
|
|
|
@ -63,7 +63,7 @@ class UriIndicator extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className="button--uri-indicator" navigate={channelLink}>
|
<Button className="button--uri-indicator" navigate={channelLink}>
|
||||||
<Tooltip label={<ClaimPreview uri={channelLink} type="small" />}>{inner}</Tooltip>
|
<Tooltip label={'test' || <ClaimPreview uri={channelLink} type="small" />}>{inner}</Tooltip>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
makeSelectThumbnailForUri,
|
makeSelectThumbnailForUri,
|
||||||
makeSelectCoverForUri,
|
makeSelectCoverForUri,
|
||||||
selectCurrentChannelPage,
|
selectCurrentChannelPage,
|
||||||
|
makeSelectClaimForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ const select = (state, props) => ({
|
||||||
cover: makeSelectCoverForUri(props.uri)(state),
|
cover: makeSelectCoverForUri(props.uri)(state),
|
||||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
page: selectCurrentChannelPage(state),
|
page: selectCurrentChannelPage(state),
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -20,6 +20,7 @@ const ABOUT_PAGE = `about`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
claim: ChannelClaim,
|
||||||
title: ?string,
|
title: ?string,
|
||||||
cover: ?string,
|
cover: ?string,
|
||||||
thumbnail: ?string,
|
thumbnail: ?string,
|
||||||
|
@ -31,12 +32,12 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelPage(props: Props) {
|
function ChannelPage(props: Props) {
|
||||||
const { uri, title, cover, history, location, page, channelIsMine, thumbnail } = props;
|
const { uri, title, cover, history, location, page, channelIsMine, thumbnail, claim } = props;
|
||||||
const { channelName } = parseURI(uri);
|
const { channelName } = parseURI(uri);
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
|
const currentView = urlParams.get(PAGE_VIEW_QUERY) || undefined;
|
||||||
|
const { permanent_url: permanentUrl } = claim;
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [thumbPreview, setThumbPreview] = useState(thumbnail);
|
const [thumbPreview, setThumbPreview] = useState(thumbnail);
|
||||||
const [coverPreview, setCoverPreview] = useState(cover);
|
const [coverPreview, setCoverPreview] = useState(cover);
|
||||||
|
@ -90,7 +91,7 @@ function ChannelPage(props: Props) {
|
||||||
<Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab>
|
<Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<ShareButton uri={uri} />
|
<ShareButton uri={uri} />
|
||||||
<SubscribeButton uri={uri} />
|
<SubscribeButton uri={permanentUrl} />
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ function DiscoverPage(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
personal
|
personalView
|
||||||
tags={followedTags.map(tag => tag.name)}
|
tags={followedTags.map(tag => tag.name)}
|
||||||
meta={<Button button="link" label={__('Customize')} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
meta={<Button button="link" label={__('Customize')} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
||||||
injectedItem={<TagsSelect showClose title={__('Customize Your Homepage')} />}
|
injectedItem={<TagsSelect showClose title={__('Customize Your Homepage')} />}
|
||||||
|
|
|
@ -63,6 +63,29 @@ const defaultState: AppState = {
|
||||||
isUpgradeSkipped: undefined,
|
isUpgradeSkipped: undefined,
|
||||||
enhancedLayout: false,
|
enhancedLayout: false,
|
||||||
searchOptionsExpanded: false,
|
searchOptionsExpanded: false,
|
||||||
|
currentScroll: 0,
|
||||||
|
scrollHistory: [0],
|
||||||
|
};
|
||||||
|
|
||||||
|
reducers['@@router/LOCATION_CHANGE'] = (state, action) => {
|
||||||
|
const { currentScroll } = state;
|
||||||
|
const scrollHistory = state.scrollHistory.slice();
|
||||||
|
const { action: name } = action.payload;
|
||||||
|
|
||||||
|
let newCurrentScroll = currentScroll;
|
||||||
|
if (name === 'PUSH') {
|
||||||
|
scrollHistory.push(window.scrollY);
|
||||||
|
newCurrentScroll = 0;
|
||||||
|
} else if (name === 'POP') {
|
||||||
|
newCurrentScroll = scrollHistory[scrollHistory.length - 1];
|
||||||
|
scrollHistory.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
scrollHistory,
|
||||||
|
currentScroll: newCurrentScroll,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.DAEMON_READY] = state =>
|
reducers[ACTIONS.DAEMON_READY] = state =>
|
||||||
|
|
|
@ -189,4 +189,9 @@
|
||||||
|
|
||||||
.claim-preview-tags {
|
.claim-preview-tags {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
||||||
|
// change this
|
||||||
|
.tag {
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
.main-wrapper {
|
.main-wrapper {
|
||||||
position: absolute;
|
|
||||||
min-height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
padding-top: var(--header-height);
|
|
||||||
padding-left: var(--spacing-large);
|
|
||||||
padding-right: var(--spacing-large);
|
|
||||||
padding-bottom: var(--spacing-large);
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
background-color: var(--dm-color-08);
|
background-color: var(--dm-color-08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-wrapper-inner {
|
.main-wrapper__inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
min-height: 100vh;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
width: 100%;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-top: var(--spacing-large);
|
padding-bottom: var(--spacing-main-padding);
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding));
|
width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding));
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-top: calc(var(--header-height) + var(--spacing-large));
|
||||||
|
margin-right: var(--spacing-main-padding);
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
.navigation-wrapper {
|
.navigation-wrapper {
|
||||||
width: var(--side-nav-width);
|
// width: var(--side-nav-width);
|
||||||
left: calc(100% - var(--side-nav-width));
|
// left: calc(100% - var(--side-nav-width));
|
||||||
height: 100%;
|
// height: 100%;
|
||||||
position: absolute;
|
// position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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) {
|
||||||
|
|
|
@ -45,7 +45,6 @@ $main: $lbry-teal-5;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
max-width: 10rem;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -6,6 +6,7 @@ html {
|
||||||
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
&[data-mode='dark'] {
|
&[data-mode='dark'] {
|
||||||
|
@ -20,7 +21,6 @@ body {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
overflow: hidden;
|
|
||||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
|
|
|
@ -26,3 +26,13 @@ export function toQueryString(params) {
|
||||||
|
|
||||||
return parts.join('&');
|
return parts.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateQueryParam(uri, key, value) {
|
||||||
|
const re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i');
|
||||||
|
const separator = uri.indexOf('?') !== -1 ? '&' : '?';
|
||||||
|
if (uri.match(re)) {
|
||||||
|
return uri.replace(re, '$1' + key + '=' + value + '$2');
|
||||||
|
} else {
|
||||||
|
return uri + separator + key + '=' + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
5
src/ui/util/string.js
Normal file
5
src/ui/util/string.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export function toCapitalCase(string: string) {
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
|
@ -10,10 +10,10 @@ export default function useHover(ref) {
|
||||||
|
|
||||||
const refElement = ref.current;
|
const refElement = ref.current;
|
||||||
if (refElement) {
|
if (refElement) {
|
||||||
refElement.addEventListener('mouseover', handleHover);
|
refElement.addEventListener('mouseenter', handleHover);
|
||||||
refElement.addEventListener('mouseleave', handleHover);
|
refElement.addEventListener('mouseleave', handleHover);
|
||||||
return () => {
|
return () => {
|
||||||
refElement.removeEventListener('mouseover', handleHover);
|
refElement.removeEventListener('mouseenter', handleHover);
|
||||||
refElement.removeEventListener('mouseleave', handleHover);
|
refElement.removeEventListener('mouseleave', handleHover);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Primary definition for this is in webpack.web.config.js
|
Primary definition for this is in webpack.web.config.js
|
||||||
We can't access it here because webpack isn't running on this file
|
We can't access it here because webpack isn't running on this file
|
||||||
|
|
|
@ -542,6 +542,7 @@
|
||||||
"Upgrade is ready to install": "Upgrade is ready to install",
|
"Upgrade is ready to install": "Upgrade is ready to install",
|
||||||
"Upgrade is ready": "Upgrade is ready",
|
"Upgrade is ready": "Upgrade is ready",
|
||||||
"Abandon the claim for this URI": "Abandon the claim for this URI",
|
"Abandon the claim for this URI": "Abandon the claim for this URI",
|
||||||
|
<<<<<<< HEAD
|
||||||
"For video content, use MP4s in H264/AAC format for best compatibility.": "For video content, use MP4s in H264/AAC format for best compatibility.",
|
"For video content, use MP4s in H264/AAC format for best compatibility.": "For video content, use MP4s in H264/AAC format for best compatibility.",
|
||||||
"Read the App Basics FAQ": "Read the App Basics FAQ",
|
"Read the App Basics FAQ": "Read the App Basics FAQ",
|
||||||
"View all LBRY FAQs": "View all LBRY FAQs",
|
"View all LBRY FAQs": "View all LBRY FAQs",
|
||||||
|
@ -577,3 +578,23 @@
|
||||||
"These credits are permanently yours and can be removed at any time. Removing this support will reduce the claim's discoverability and return the LBC to your spendable balance.": "These credits are permanently yours and can be removed at any time. Removing this support will reduce the claim's discoverability and return the LBC to your spendable balance.",
|
"These credits are permanently yours and can be removed at any time. Removing this support will reduce the claim's discoverability and return the LBC to your spendable balance.": "These credits are permanently yours and can be removed at any time. Removing this support will reduce the claim's discoverability and return the LBC to your spendable balance.",
|
||||||
"The better your tags are, the easier it will be for people to discover your channel.": "The better your tags are, the easier it will be for people to discover your channel."
|
"The better your tags are, the easier it will be for people to discover your channel.": "The better your tags are, the easier it will be for people to discover your channel."
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
"View Tag": "View Tag",
|
||||||
|
"Invalid claim ID %s.": "Invalid claim ID %s.",
|
||||||
|
"Invalid character %s in name: %s.": "Invalid character %s in name: %s.",
|
||||||
|
"LBRY names cannot contain spaces or reserved symbols ($#@;/\"<>%{}|^~[]`)": "LBRY names cannot contain spaces or reserved symbols ($#@;/\"<>%{}|^~[]`)",
|
||||||
|
"No transactions.": "No transactions.",
|
||||||
|
"Today": "Today",
|
||||||
|
"This": "This",
|
||||||
|
"All time": "All time",
|
||||||
|
"Thumbnail (300 x 300)": "Thumbnail (300 x 300)",
|
||||||
|
"Cover (1000 x 160)": "Cover (1000 x 160)",
|
||||||
|
"No path provided after /": "No path provided after /",
|
||||||
|
"Connection Failure": "Connection Failure",
|
||||||
|
"Try closing all LBRY processes and starting again. If this still happens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.": "Try closing all LBRY processes and starting again. If this still happens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.",
|
||||||
|
"Unable to Authenticate": "Unable to Authenticate",
|
||||||
|
"Authentication Failure": "Authentication Failure",
|
||||||
|
"Reload": "Reload",
|
||||||
|
"If reloading does not fix this, or you see this at every start up, please email help@lbry.com.": "If reloading does not fix this, or you see this at every start up, please email help@lbry.com."
|
||||||
|
}
|
||||||
|
>>>>>>> restore that shit
|
||||||
|
|
25
yarn.lock
25
yarn.lock
|
@ -769,6 +769,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.1.5":
|
||||||
|
version "7.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.4.tgz#cb7d1ad7c6d65676e66b47186577930465b5271b"
|
||||||
|
integrity sha512-Na84uwyImZZc3FKf4aUF1tysApzwf3p2yuFBIyBfbzT5glzKTdvYI4KVW4kcgjrzoGUjC7w3YyCHcJKaRxsr2Q==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
"@babel/runtime@^7.4.3":
|
"@babel/runtime@^7.4.3":
|
||||||
version "7.4.4"
|
version "7.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d"
|
||||||
|
@ -6646,9 +6653,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#eb3a7afc46ef69678149ad5caff3829473d75b43:
|
lbry-redux@lbryio/lbry-redux#6406f184e8c87d79f4adb2b8792ff3643f4527ea:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/eb3a7afc46ef69678149ad5caff3829473d75b43"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6406f184e8c87d79f4adb2b8792ff3643f4527ea"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
@ -9578,6 +9585,15 @@ react-spring@^8.0.20:
|
||||||
"@babel/runtime" "^7.3.1"
|
"@babel/runtime" "^7.3.1"
|
||||||
prop-types "^15.5.8"
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
|
react-sticky-box@^0.8.0:
|
||||||
|
version "0.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-sticky-box/-/react-sticky-box-0.8.0.tgz#1c191936af8f5420087b703ec6da4ef46060076c"
|
||||||
|
integrity sha512-al7fY+VzTKBgVrn14l21jQfhuG582Z6FD8tVbWVQDDqzcjLmUrFb+ljG2phxHhRRazg64L3yH4nOKjn78PZmag==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.1.5"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
resize-observer-polyfill "^1.5.1"
|
||||||
|
|
||||||
react-toggle@^4.0.2:
|
react-toggle@^4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.0.2.tgz#77f487860efb87fafd197672a2db8c885be1440f"
|
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.0.2.tgz#77f487860efb87fafd197672a2db8c885be1440f"
|
||||||
|
@ -10032,6 +10048,11 @@ reselect@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
|
resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
|
||||||
integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
|
integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
|
||||||
|
|
||||||
|
resize-observer-polyfill@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||||
|
|
||||||
resolve-cwd@^2.0.0:
|
resolve-cwd@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||||
|
|
Loading…
Reference in a new issue