so much discovery I can't take it #2617
47 changed files with 490 additions and 275 deletions
|
@ -29,6 +29,7 @@
|
||||||
"indent": 0,
|
"indent": 0,
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
"jsx-quotes": ["error", "prefer-double"],
|
||||||
"new-cap": 0,
|
"new-cap": 0,
|
||||||
|
"no-console": 1,
|
||||||
"no-multi-spaces": 0,
|
"no-multi-spaces": 0,
|
||||||
"no-redeclare": 0,
|
"no-redeclare": 0,
|
||||||
"no-return-await": 0,
|
"no-return-await": 0,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.34.0-rc.5",
|
"version": "0.34.0-rc.9",
|
||||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lbry"
|
"lbry"
|
||||||
|
@ -124,7 +124,7 @@
|
||||||
"jsmediatags": "^3.8.1",
|
"jsmediatags": "^3.8.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#b07dfa172a526aa5e09af57bb6cf33790c8d0c91",
|
"lbry-redux": "lbryio/lbry-redux#5080eb3ea1f09ce03c4f50d9224feddf737628d3",
|
||||||
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
@ -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",
|
||||||
|
@ -199,8 +200,8 @@
|
||||||
"yarn": "^1.3"
|
"yarn": "^1.3"
|
||||||
},
|
},
|
||||||
"lbrySettings": {
|
"lbrySettings": {
|
||||||
"lbrynetDaemonVersion": "0.38.0",
|
"lbrynetDaemonVersion": "0.38.1",
|
||||||
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
|
"lbrynetDaemonUrlTemplate": "http://build.lbry.io/daemon/build-11443_commit-eae4ed7_branch-master/lbrynet-OSNAME.zip",
|
||||||
"lbrynetDaemonDir": "static/daemon",
|
"lbrynetDaemonDir": "static/daemon",
|
||||||
"lbrynetDaemonFileName": "lbrynet"
|
"lbrynetDaemonFileName": "lbrynet"
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,14 +51,12 @@ function App(props: Props) {
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
<div className={MAIN_WRAPPER_CLASS} 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 />
|
||||||
|
|
|
@ -2,25 +2,33 @@ import { connect } from 'react-redux';
|
||||||
import { doFetchClaimsByChannel } from 'redux/actions/content';
|
import { doFetchClaimsByChannel } from 'redux/actions/content';
|
||||||
import { PAGE_SIZE } from 'constants/claim';
|
import { PAGE_SIZE } from 'constants/claim';
|
||||||
import {
|
import {
|
||||||
makeSelectClaimsInChannelForCurrentPageState,
|
makeSelectClaimsInChannelForPage,
|
||||||
makeSelectFetchingChannelClaims,
|
makeSelectFetchingChannelClaims,
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectTotalPagesForChannel,
|
makeSelectTotalPagesForChannel,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => {
|
||||||
claimsInChannel: makeSelectClaimsInChannelForCurrentPageState(props.uri)(state),
|
const { search } = props.location;
|
||||||
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
const urlParams = new URLSearchParams(search);
|
||||||
totalPages: makeSelectTotalPagesForChannel(props.uri, PAGE_SIZE)(state),
|
const page = urlParams.get('page') || 0;
|
||||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
return {
|
||||||
});
|
claimsInChannel: makeSelectClaimsInChannelForPage(props.uri, page)(state),
|
||||||
|
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
||||||
|
totalPages: makeSelectTotalPagesForChannel(props.uri, PAGE_SIZE)(state),
|
||||||
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
|
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default withRouter(
|
||||||
select,
|
connect(
|
||||||
perform
|
select,
|
||||||
)(ChannelPage);
|
perform
|
||||||
|
)(ChannelPage)
|
||||||
|
);
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -29,78 +31,122 @@ const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL];
|
||||||
type Props = {
|
type Props = {
|
||||||
uris: Array<string>,
|
uris: Array<string>,
|
||||||
subscribedChannels: Array<Subscription>,
|
subscribedChannels: Array<Subscription>,
|
||||||
doClaimSearch: (number, {}) => void,
|
doClaimSearch: ({}) => void,
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doClaimSearch(20, 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">
|
||||||
|
@ -109,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}>
|
||||||
|
@ -121,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
|
||||||
|
@ -130,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>
|
||||||
|
@ -147,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}>
|
||||||
|
@ -173,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);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
|
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||||
import ClaimPreview from './view';
|
import ClaimPreview from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -24,6 +25,7 @@ const select = (state, props) => ({
|
||||||
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
filteredOutpoints: selectFilteredOutpoints(state),
|
||||||
|
hasVisitedUri: makeSelectHasVisitedUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Props = {
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
placeholder: boolean,
|
placeholder: boolean,
|
||||||
type: string,
|
type: string,
|
||||||
|
hasVisitedUri: boolean,
|
||||||
blackListedOutpoints: Array<{
|
blackListedOutpoints: Array<{
|
||||||
txid: string,
|
txid: string,
|
||||||
nout: number,
|
nout: number,
|
||||||
|
@ -56,16 +57,23 @@ function ClaimPreview(props: Props) {
|
||||||
type,
|
type,
|
||||||
blackListedOutpoints,
|
blackListedOutpoints,
|
||||||
filteredOutpoints,
|
filteredOutpoints,
|
||||||
|
hasVisitedUri,
|
||||||
} = props;
|
} = props;
|
||||||
const haventFetched = claim === undefined;
|
const haventFetched = claim === undefined;
|
||||||
const abandoned = !isResolvingUri && !claim && !placeholder;
|
const abandoned = !isResolvingUri && !claim && !placeholder;
|
||||||
const { isChannel } = parseURI(uri);
|
|
||||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
|
let isValid;
|
||||||
|
try {
|
||||||
|
parseURI(uri);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isChannel = isValid ? parseURI(uri).isChannel : false;
|
||||||
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
|
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
|
||||||
|
|
||||||
// This will be replaced once blocking is done at the wallet server level
|
// This will be replaced once blocking is done at the wallet server level
|
||||||
|
|
||||||
if (claim && !shouldHide && blackListedOutpoints) {
|
if (claim && !shouldHide && blackListedOutpoints) {
|
||||||
shouldHide = blackListedOutpoints.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
|
shouldHide = blackListedOutpoints.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
|
||||||
}
|
}
|
||||||
|
@ -89,16 +97,16 @@ function ClaimPreview(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!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" />
|
||||||
|
@ -117,7 +125,8 @@ function ClaimPreview(props: Props) {
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
className={classnames('claim-preview', {
|
className={classnames('claim-preview', {
|
||||||
'claim-preview--large': type === 'large',
|
'claim-preview--large': type === 'large',
|
||||||
'claim-list__pending': pending,
|
'claim-preview--visited': !isChannel && hasVisitedUri,
|
||||||
|
'claim-preview--pending': pending,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
||||||
|
|
|
@ -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('');
|
||||||
|
|
|
@ -253,6 +253,12 @@ export const icons = {
|
||||||
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
|
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.TAG]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" />
|
||||||
|
<line x1="7" y1="7" x2="7" y2="7" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
[ICONS.SUPPORT]: buildIcon(
|
[ICONS.SUPPORT]: buildIcon(
|
||||||
<g>
|
<g>
|
||||||
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
|
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
|
||||||
|
|
|
@ -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,9 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectScrollStartingPosition } from 'redux/selectors/app';
|
||||||
import Router from './view';
|
import Router from './view';
|
||||||
export default Router;
|
|
||||||
|
const select = state => ({
|
||||||
|
currentScroll: selectScrollStartingPosition(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
// Tell the browser we are handling scroll restoration
|
||||||
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -221,11 +221,11 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
|
||||||
:container {
|
:container {
|
||||||
perspective: 30vmin;
|
perspective: 30vmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@place-cell: center;
|
@place-cell: center;
|
||||||
@size: 100%;
|
@size: 100%;
|
||||||
|
|
||||||
box-shadow: @m2(0 0 50px var(--color));
|
box-shadow: @m2(0 0 50px var(--color));
|
||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
animation: scale-up 12s linear infinite;
|
animation: scale-up 12s linear infinite;
|
||||||
animation-delay: calc(-12s / @size() * @i());
|
animation-delay: calc(-12s / @size() * @i());
|
||||||
|
@ -235,11 +235,11 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
|
||||||
transform: translateZ(0) rotate(0);
|
transform: translateZ(0) rotate(0);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
10% {
|
10% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
95% {
|
95% {
|
||||||
transform:
|
transform:
|
||||||
translateZ(35vmin) rotateZ(@var(--deg));
|
translateZ(35vmin) rotateZ(@var(--deg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,10 +44,12 @@ class TransactionListItem extends React.PureComponent<Props> {
|
||||||
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
||||||
|
|
||||||
// Ensure the claim name exists and is valid
|
// Ensure the claim name exists and is valid
|
||||||
|
let uri;
|
||||||
let claimName = name;
|
let claimName = name;
|
||||||
if (claimName) {
|
try {
|
||||||
({ claimName } = parseURI(name));
|
({ claimName } = parseURI(name));
|
||||||
}
|
uri = buildURI({ claimName: claimName, claimId });
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
const dateFormat = {
|
const dateFormat = {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
@ -72,9 +74,7 @@ class TransactionListItem extends React.PureComponent<Props> {
|
||||||
</td>
|
</td>
|
||||||
<td className="table__item--actionable">
|
<td className="table__item--actionable">
|
||||||
{reward && <span>{reward.reward_title}</span>}
|
{reward && <span>{reward.reward_title}</span>}
|
||||||
{claimName && claimId && (
|
{claimName && claimId ? <Button button="link" navigate={uri} label={claimName} /> : claimName}
|
||||||
<Button button="link" navigate={buildURI({ claimName: claimName, claimId })} label={claimName} />
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Button from 'component/button';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import UserEmailNew from 'component/userEmailNew';
|
import UserEmailNew from 'component/userEmailNew';
|
||||||
import UserEmailVerify from 'component/userEmailVerify';
|
import UserEmailVerify from 'component/userEmailVerify';
|
||||||
import cookie from 'cookie';
|
import UserEmailResetButton from 'component/userEmailResetButton';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cancelButton: Node,
|
cancelButton: Node,
|
||||||
|
@ -21,14 +21,6 @@ type Props = {
|
||||||
|
|
||||||
function UserEmail(props: Props) {
|
function UserEmail(props: Props) {
|
||||||
const { email, user, accessToken, fetchAccessToken } = props;
|
const { email, user, accessToken, fetchAccessToken } = props;
|
||||||
const buttonsProps = IS_WEB
|
|
||||||
? {
|
|
||||||
onClick: () => {
|
|
||||||
document.cookie = cookie.serialize('auth_token', '');
|
|
||||||
window.location.reload();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: { href: 'https://lbry.com/faq/how-to-change-email' };
|
|
||||||
|
|
||||||
let isVerified = false;
|
let isVerified = false;
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -71,7 +63,7 @@ function UserEmail(props: Props) {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
value={email}
|
value={email}
|
||||||
inputButton={<Button button="inverse" label={__('Change')} {...buttonsProps} />}
|
inputButton={<UserEmailResetButton button="inverse" />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="help">
|
<p className="help">
|
||||||
|
|
11
src/ui/component/userEmailResetButton/index.js
Normal file
11
src/ui/component/userEmailResetButton/index.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import UserEmailResetButton from './view';
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(UserEmailResetButton);
|
24
src/ui/component/userEmailResetButton/view.jsx
Normal file
24
src/ui/component/userEmailResetButton/view.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import cookie from 'cookie';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
button: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function UserEmailResetButton(props: Props) {
|
||||||
|
const { button = 'link' } = props;
|
||||||
|
const buttonsProps = IS_WEB
|
||||||
|
? {
|
||||||
|
onClick: () => {
|
||||||
|
document.cookie = cookie.serialize('auth_token', '');
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: { href: 'https://lbry.com/faq/how-to-change-email' };
|
||||||
|
|
||||||
|
return <Button button={button} label={__('Change')} {...buttonsProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserEmailResetButton;
|
|
@ -1,9 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import UserEmailResetButton from 'component/userEmailResetButton';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cancelButton: React.Node,
|
|
||||||
email: string,
|
email: string,
|
||||||
resendVerificationEmail: string => void,
|
resendVerificationEmail: string => void,
|
||||||
checkEmailVerified: () => void,
|
checkEmailVerified: () => void,
|
||||||
|
@ -47,7 +47,7 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
||||||
emailVerifyCheckInterval: ?IntervalID;
|
emailVerifyCheckInterval: ?IntervalID;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { cancelButton, email } = this.props;
|
const { email } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -67,7 +67,8 @@ class UserEmailVerify extends React.PureComponent<Props> {
|
||||||
label={__('Resend verification email')}
|
label={__('Resend verification email')}
|
||||||
onClick={this.handleResendVerificationEmail}
|
onClick={this.handleResendVerificationEmail}
|
||||||
/>
|
/>
|
||||||
{cancelButton}
|
|
||||||
|
<UserEmailResetButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="help">
|
<p className="help">
|
||||||
|
|
|
@ -3,10 +3,11 @@ 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 classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { normalizeURI, SEARCH_TYPES, isURIValid, buildURI } from 'lbry-redux';
|
import { normalizeURI, SEARCH_TYPES, isURIValid } from 'lbry-redux';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import { parseQueryParams } from 'util/query-params';
|
|
||||||
import Autocomplete from './internal/autocomplete';
|
import Autocomplete from './internal/autocomplete';
|
||||||
|
import Tag from 'component/tag';
|
||||||
|
|
||||||
const L_KEY_CODE = 76;
|
const L_KEY_CODE = 76;
|
||||||
const ESC_KEY_CODE = 27;
|
const ESC_KEY_CODE = 27;
|
||||||
|
@ -22,6 +23,7 @@ type Props = {
|
||||||
doBlur: () => void,
|
doBlur: () => void,
|
||||||
focused: boolean,
|
focused: boolean,
|
||||||
doShowSnackBar: string => void,
|
doShowSnackBar: string => void,
|
||||||
|
history: { push: string => void },
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -51,10 +53,12 @@ class WunderBar extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
getSuggestionIcon = (type: string) => {
|
getSuggestionIcon = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'file':
|
case SEARCH_TYPES.FILE:
|
||||||
return ICONS.FILE;
|
return ICONS.FILE;
|
||||||
case 'channel':
|
case SEARCH_TYPES.CHANNEL:
|
||||||
return ICONS.CHANNEL;
|
return ICONS.CHANNEL;
|
||||||
|
case SEARCH_TYPES.TAG:
|
||||||
|
return ICONS.TAG;
|
||||||
default:
|
default:
|
||||||
return ICONS.SEARCH;
|
return ICONS.SEARCH;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +94,7 @@ class WunderBar extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit(value: string, suggestion?: { value: string, type: string }) {
|
handleSubmit(value: string, suggestion?: { value: string, type: string }) {
|
||||||
const { onSubmit, onSearch, doShowSnackBar } = this.props;
|
const { onSubmit, onSearch, doShowSnackBar, history } = this.props;
|
||||||
|
|
||||||
const query = value.trim();
|
const query = value.trim();
|
||||||
const showSnackError = () => {
|
const showSnackError = () => {
|
||||||
|
@ -99,8 +103,10 @@ class WunderBar extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
// User selected a suggestion
|
// User selected a suggestion
|
||||||
if (suggestion) {
|
if (suggestion) {
|
||||||
if (suggestion.type === 'search') {
|
if (suggestion.type === SEARCH_TYPES.SEARCH) {
|
||||||
onSearch(query);
|
onSearch(query);
|
||||||
|
} else if (suggestion.type === SEARCH_TYPES.TAG) {
|
||||||
|
history.push(`/$/${PAGES.TAGS}?t=${suggestion.value}`);
|
||||||
} else if (isURIValid(query)) {
|
} else if (isURIValid(query)) {
|
||||||
const uri = normalizeURI(query);
|
const uri = normalizeURI(query);
|
||||||
onSubmit(uri);
|
onSubmit(uri);
|
||||||
|
@ -157,18 +163,22 @@ class WunderBar extends React.PureComponent<Props, State> {
|
||||||
)}
|
)}
|
||||||
renderItem={({ value, type }, isHighlighted) => (
|
renderItem={({ value, type }, isHighlighted) => (
|
||||||
<div
|
<div
|
||||||
key={value}
|
// Use value + type for key because there might be suggestions with same value but different type
|
||||||
|
key={`${value}-${type}`}
|
||||||
className={classnames('wunderbar__suggestion', {
|
className={classnames('wunderbar__suggestion', {
|
||||||
'wunderbar__active-suggestion': isHighlighted,
|
'wunderbar__active-suggestion': isHighlighted,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon icon={this.getSuggestionIcon(type)} />
|
<Icon icon={this.getSuggestionIcon(type)} />
|
||||||
<span className="wunderbar__suggestion-label">{value}</span>
|
<span className="wunderbar__suggestion-label">
|
||||||
|
{type === SEARCH_TYPES.TAG ? <Tag name={value} /> : value}
|
||||||
|
</span>
|
||||||
{isHighlighted && (
|
{isHighlighted && (
|
||||||
<span className="wunderbar__suggestion-label--action">
|
<span className="wunderbar__suggestion-label--action">
|
||||||
{type === SEARCH_TYPES.SEARCH && __('Search')}
|
{type === SEARCH_TYPES.SEARCH && __('Search')}
|
||||||
{type === SEARCH_TYPES.CHANNEL && __('View channel')}
|
{type === SEARCH_TYPES.CHANNEL && __('View channel')}
|
||||||
{type === SEARCH_TYPES.FILE && __('View file')}
|
{type === SEARCH_TYPES.FILE && __('View file')}
|
||||||
|
{type === SEARCH_TYPES.TAG && __('View Tag')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -179,4 +189,4 @@ class WunderBar extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WunderBar;
|
export default withRouter(WunderBar);
|
||||||
|
|
|
@ -69,4 +69,5 @@ export const MUSIC_EQUALIZER = 'Sliders';
|
||||||
export const LIGHT = 'Sun';
|
export const LIGHT = 'Sun';
|
||||||
export const DARK = 'Moon';
|
export const DARK = 'Moon';
|
||||||
export const LIBRARY = 'Folder';
|
export const LIBRARY = 'Folder';
|
||||||
|
export const TAG = 'Tag';
|
||||||
export const SUPPORT = 'TrendingUp';
|
export const SUPPORT = 'TrendingUp';
|
||||||
|
|
|
@ -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')} />}
|
||||||
|
|
|
@ -173,16 +173,14 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<Form>
|
<FileSelector
|
||||||
<FileSelector
|
type="openDirectory"
|
||||||
type="openDirectory"
|
currentPath={daemonSettings.download_dir}
|
||||||
currentPath={daemonSettings.download_dir}
|
onFileChosen={(newDirectory: string) => {
|
||||||
onFileChosen={(newDirectory: string) => {
|
setDaemonSetting('download_dir', newDirectory);
|
||||||
setDaemonSetting('download_dir', newDirectory);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
<p className="help">{__('LBRY downloads will be saved here.')}</p>
|
||||||
<p className="help">{__('LBRY downloads will be saved here.')}</p>
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,6 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
|
||||||
contentIsFree,
|
contentIsFree,
|
||||||
fee,
|
fee,
|
||||||
uri,
|
uri,
|
||||||
nsfw,
|
|
||||||
tags,
|
tags,
|
||||||
locations,
|
locations,
|
||||||
} = publishData;
|
} = publishData;
|
||||||
|
@ -287,17 +286,6 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
|
||||||
publishPayload.release_time = Number(myClaimForUri.value.release_time);
|
publishPayload.release_time = Number(myClaimForUri.value.release_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nsfw) {
|
|
||||||
if (!publishPayload.tags.includes('mature')) {
|
|
||||||
publishPayload.tags.push('mature');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const indexToRemove = publishPayload.tags.indexOf('mature');
|
|
||||||
if (indexToRemove > -1) {
|
|
||||||
publishPayload.tags.splice(indexToRemove, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channelId) {
|
if (channelId) {
|
||||||
publishPayload.channel_id = channelId;
|
publishPayload.channel_id = channelId;
|
||||||
}
|
}
|
||||||
|
@ -347,7 +335,7 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
|
||||||
dispatch({ type: ACTIONS.PUBLISH_FAIL });
|
dispatch({ type: ACTIONS.PUBLISH_FAIL });
|
||||||
dispatch(doError(error.message));
|
dispatch(doError(error.message));
|
||||||
};
|
};
|
||||||
console.log('PP', publishPayload);
|
|
||||||
return Lbry.publish(publishPayload).then(success, failure);
|
return Lbry.publish(publishPayload).then(success, failure);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,31 @@ const defaultState: AppState = {
|
||||||
isUpgradeSkipped: undefined,
|
isUpgradeSkipped: undefined,
|
||||||
enhancedLayout: false,
|
enhancedLayout: false,
|
||||||
searchOptionsExpanded: false,
|
searchOptionsExpanded: false,
|
||||||
|
currentScroll: 0,
|
||||||
|
scrollHistory: [0],
|
||||||
|
};
|
||||||
|
|
||||||
|
// @@router comes from react-router
|
||||||
|
// This action is dispatched any time a user navigates forward or back
|
||||||
|
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 =>
|
||||||
|
|
|
@ -123,3 +123,8 @@ export const selectSearchOptionsExpanded = createSelector(
|
||||||
selectState,
|
selectState,
|
||||||
state => state.searchOptionsExpanded
|
state => state.searchOptionsExpanded
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectScrollStartingPosition = createSelector(
|
||||||
|
selectState,
|
||||||
|
state => state.currentScroll
|
||||||
|
);
|
||||||
|
|
|
@ -58,6 +58,12 @@ export const makeSelectHistoryForUri = (uri: string) =>
|
||||||
history => history.find(i => i.uri === uri)
|
history => history.find(i => i.uri === uri)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const makeSelectHasVisitedUri = (uri: string) =>
|
||||||
|
createSelector(
|
||||||
|
makeSelectHistoryForUri(uri),
|
||||||
|
history => Boolean(history)
|
||||||
|
);
|
||||||
|
|
||||||
export const selectRecentHistory = createSelector(
|
export const selectRecentHistory = createSelector(
|
||||||
selectHistory,
|
selectHistory,
|
||||||
history => {
|
history => {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
color: var(--dm-color-01);
|
color: var(--dm-color-01);
|
||||||
background-color: rgba($lbry-teal-5, 0.3);
|
background-color: lighten(mix($lbry-black, $lbry-teal-5, 80%), 10%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,4 +31,8 @@
|
||||||
svg {
|
svg {
|
||||||
stroke: $lbry-black;
|
stroke: $lbry-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-mode='dark'] & {
|
||||||
|
background-color: darken(mix($lbry-grape-1, $lbry-gray-5, 50%), 20%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,7 @@
|
||||||
line-height: var(--button-height);
|
line-height: var(--button-height);
|
||||||
border-radius: var(--button-radius);
|
border-radius: var(--button-radius);
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
padding-top: 0;
|
padding: 0 var(--spacing-medium);
|
||||||
padding-bottom: 0;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,13 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $lbry-teal-1;
|
color: $lbry-teal-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-mode='dark'] & {
|
||||||
|
color: $lbry-teal-4;
|
||||||
|
&:hover {
|
||||||
|
color: $lbry-teal-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix this in @lbry/components, we shouldn't need to be this specific
|
// Fix this in @lbry/components, we shouldn't need to be this specific
|
||||||
|
@ -129,7 +136,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__pending {
|
.claim-preview--visited {
|
||||||
|
// Still keep the normal styles on hover regardless of if they have visited the claim
|
||||||
|
&:not(:hover) {
|
||||||
|
color: lighten($lbry-black, 35%);
|
||||||
|
|
||||||
|
[data-mode='dark'] & {
|
||||||
|
color: darken($lbry-white, 35%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-preview--pending {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
||||||
|
@ -166,8 +184,5 @@
|
||||||
.claim-preview-title {
|
.claim-preview-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
padding-right: var(--spacing-medium);
|
||||||
|
|
||||||
.claim-preview-tags {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
@import '~@lbry/components/sass/form/_index.scss';
|
@import '~@lbry/components/sass/form/_index.scss';
|
||||||
|
|
||||||
|
// Reset lbry components style that turns buttons inside of forms black
|
||||||
|
form {
|
||||||
|
.button--primary,
|
||||||
|
[type='submit'] {
|
||||||
|
&:not(:hover),
|
||||||
|
&:hover {
|
||||||
|
@extend .button--primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
|
@ -223,12 +234,16 @@ fieldset-section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button,
|
||||||
|
// specificity needed because of @lbry/component rules
|
||||||
|
// @lbry/componentsfixme
|
||||||
|
.button[type='submit']:not(:hover),
|
||||||
|
.button[type='submit']:hover {
|
||||||
|
border-color: $lbry-black;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-top-right-radius: var(--input-border-radius);
|
border-top-right-radius: var(--input-border-radius);
|
||||||
border-bottom-right-radius: var(--input-border-radius);
|
border-bottom-right-radius: var(--input-border-radius);
|
||||||
border-color: $lbry-black;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
.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-left: var(--spacing-large);
|
||||||
position: relative;
|
padding-right: var(--spacing-large);
|
||||||
|
padding-bottom: var(--spacing-main-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.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%;
|
||||||
|
@ -67,4 +62,8 @@
|
||||||
svg {
|
svg {
|
||||||
stroke: $lbry-white;
|
stroke: $lbry-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-mode='dark'] & {
|
||||||
|
background-color: $lbry-teal-5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
.navigation-wrapper {
|
|
||||||
width: var(--side-nav-width);
|
|
||||||
left: calc(100% - var(--side-nav-width));
|
|
||||||
height: 100%;
|
|
||||||
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 {
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
background-color: lighten($lbry-black, 10%);
|
background-color: var(--dm-color-05);
|
||||||
color: $lbry-white;
|
color: $lbry-white;
|
||||||
box-shadow: 0 10px 30px 2px $lbry-black;
|
box-shadow: 0 10px 30px 2px $lbry-black;
|
||||||
border: 1px solid $lbry-gray-5;
|
border: 1px solid $lbry-gray-5;
|
||||||
|
|
|
@ -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,14 @@ export function toQueryString(params) {
|
||||||
|
|
||||||
return parts.join('&');
|
return parts.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/5999118/how-can-i-add-or-update-a-query-string-parameter
|
||||||
|
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
|
||||||
|
|
|
@ -575,5 +575,8 @@
|
||||||
"Confirm Claim Revoke": "Confirm Claim Revoke",
|
"Confirm Claim Revoke": "Confirm Claim Revoke",
|
||||||
"Are you sure you want to remove this support?": "Are you sure you want to remove this support?",
|
"Are you sure you want to remove this support?": "Are you sure you want to remove this support?",
|
||||||
"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."
|
"Invalid character %s in name: %s.": "Invalid character %s in name: %s.",
|
||||||
|
"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.",
|
||||||
|
"Thumbnail (300 x 300)": "Thumbnail (300 x 300)",
|
||||||
|
"Cover (1000 x 160)": "Cover (1000 x 160)"
|
||||||
}
|
}
|
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#bb82aed61a5569e565daa784eb25fc1d639c0c22:
|
lbry-redux@lbryio/lbry-redux#5080eb3ea1f09ce03c4f50d9224feddf737628d3:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/bb82aed61a5569e565daa784eb25fc1d639c0c22"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/5080eb3ea1f09ce03c4f50d9224feddf737628d3"
|
||||||
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