~Final~ Discovery fixes/cleanup #2599
28 changed files with 157 additions and 98 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.34.0-rc.4",
|
"version": "0.34.0-rc.5",
|
||||||
"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"
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
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, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import ClaimListDiscover from './view';
|
import ClaimListDiscover from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
uris: selectLastClaimSearchUris(state),
|
uris: selectLastClaimSearchUris(state),
|
||||||
loading: selectFetchingClaimSearch(state),
|
loading: selectFetchingClaimSearch(state),
|
||||||
subscribedChannels: selectSubscriptions(state),
|
subscribedChannels: selectSubscriptions(state),
|
||||||
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = {
|
const perform = {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { Node } from 'react';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import usePersistedState from 'util/use-persisted-state';
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
|
import { MATURE_TAGS } from 'lbry-redux';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
|
@ -22,7 +23,7 @@ const TYPE_TRENDING = 'trending';
|
||||||
const TYPE_TOP = 'top';
|
const TYPE_TOP = 'top';
|
||||||
const TYPE_NEW = 'new';
|
const TYPE_NEW = 'new';
|
||||||
const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_CHANNELS, SEARCH_SORT_ALL];
|
const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_CHANNELS, SEARCH_SORT_ALL];
|
||||||
const SEARCH_TYPES = ['trending', 'top', 'new'];
|
const SEARCH_TYPES = [TYPE_TRENDING, TYPE_TOP, TYPE_NEW];
|
||||||
const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL];
|
const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL];
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -35,10 +36,11 @@ type Props = {
|
||||||
personal: boolean,
|
personal: boolean,
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
|
showNsfw: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimListDiscover(props: Props) {
|
function ClaimListDiscover(props: Props) {
|
||||||
const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta, subscribedChannels } = props;
|
const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta, subscribedChannels, showNsfw } = props;
|
||||||
const [personalSort, setPersonalSort] = usePersistedState('claim-list-discover:personalSort', SEARCH_SORT_YOU);
|
const [personalSort, setPersonalSort] = usePersistedState('claim-list-discover:personalSort', SEARCH_SORT_YOU);
|
||||||
const [typeSort, setTypeSort] = usePersistedState('claim-list-discover:typeSort', TYPE_TRENDING);
|
const [typeSort, setTypeSort] = usePersistedState('claim-list-discover:typeSort', TYPE_TRENDING);
|
||||||
const [timeSort, setTimeSort] = usePersistedState('claim-list-discover:timeSort', TIME_WEEK);
|
const [timeSort, setTimeSort] = usePersistedState('claim-list-discover:timeSort', TIME_WEEK);
|
||||||
|
@ -54,6 +56,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
order_by?: Array<string>,
|
order_by?: Array<string>,
|
||||||
channel_ids?: Array<string>,
|
channel_ids?: Array<string>,
|
||||||
release_time?: string,
|
release_time?: string,
|
||||||
|
not_tags?: Array<string>,
|
||||||
} = { page_size: PAGE_SIZE, page };
|
} = { page_size: PAGE_SIZE, page };
|
||||||
const newTags = tagsString.split(',');
|
const newTags = tagsString.split(',');
|
||||||
const newChannelIds = channelsIdString.split(',');
|
const newChannelIds = channelsIdString.split(',');
|
||||||
|
@ -64,6 +67,10 @@ function ClaimListDiscover(props: Props) {
|
||||||
options.channel_ids = newChannelIds;
|
options.channel_ids = newChannelIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!showNsfw) {
|
||||||
|
options.not_tags = MATURE_TAGS;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeSort === TYPE_TRENDING) {
|
if (typeSort === TYPE_TRENDING) {
|
||||||
options.order_by = ['trending_global', 'trending_mixed'];
|
options.order_by = ['trending_global', 'trending_mixed'];
|
||||||
} else if (typeSort === TYPE_NEW) {
|
} else if (typeSort === TYPE_NEW) {
|
||||||
|
@ -81,7 +88,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
doClaimSearch(20, options);
|
doClaimSearch(20, options);
|
||||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString, channelsIdString]);
|
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString, channelsIdString, showNsfw]);
|
||||||
|
|
||||||
function getLabel(type) {
|
function getLabel(type) {
|
||||||
if (type === SEARCH_SORT_ALL) {
|
if (type === SEARCH_SORT_ALL) {
|
||||||
|
@ -138,7 +145,9 @@ function ClaimListDiscover(props: Props) {
|
||||||
{SEARCH_TIMES.map(time => (
|
{SEARCH_TIMES.map(time => (
|
||||||
<option key={time} value={time}>
|
<option key={time} value={time}>
|
||||||
{/* i18fixme */}
|
{/* i18fixme */}
|
||||||
{__('This')} {toCapitalCase(time)}
|
{time === TIME_DAY && __('Today')}
|
||||||
|
{time !== TIME_ALL && time !== TIME_DAY && `${__('This')} ${toCapitalCase(time)}`}
|
||||||
|
{time === TIME_ALL && __('All time')}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
|
@ -56,17 +56,14 @@ function ClaimPreview(props: Props) {
|
||||||
const abandoned = !isResolvingUri && !claim && !placeholder;
|
const abandoned = !isResolvingUri && !claim && !placeholder;
|
||||||
const { isChannel } = parseURI(uri);
|
const { isChannel } = parseURI(uri);
|
||||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
I was going to use this, but then I realized we should add a I was going to use this, but then I realized we should add a `break` statement to the for loop so we don't loop over the list when we don't need to. Now it will stop if it finds a matching item.
|
|||||||
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
|
const shouldHide =
|
||||||
|
abandoned ||
|
||||||
// This will be replaced once blocking is done at the wallet server level
|
(!claimIsMine && obscureNsfw && nsfw) ||
|
||||||
if (claim && !shouldHide) {
|
(claim &&
|
||||||
for (let i = 0; i < blackListedOutpoints.length; i += 1) {
|
blackListedOutpoints &&
|
||||||
const outpoint = blackListedOutpoints[i];
|
blackListedOutpoints.reduce((hide, outpoint) => {
|
||||||
if (outpoint.txid === claim.txid && outpoint.nout === claim.nout) {
|
return hide || (outpoint.txid === claim.txid && outpoint.nout === claim.nout);
|
||||||
shouldHide = true;
|
}));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleContextMenu(e) {
|
function handleContextMenu(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Button from 'component/button';
|
import Tag from 'component/tag';
|
||||||
|
|
||||||
const SLIM_TAGS = 1;
|
const SLIM_TAGS = 1;
|
||||||
const NORMAL_TAGS = 4;
|
const NORMAL_TAGS = 4;
|
||||||
|
@ -45,7 +45,7 @@ export default function ClaimTags(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div className={classnames('file-properties', { 'file-properties--large': type === 'large' })}>
|
<div className={classnames('file-properties', { 'file-properties--large': type === 'large' })}>
|
||||||
{tagsToDisplay.map(tag => (
|
{tagsToDisplay.map(tag => (
|
||||||
<Button key={tag} title={tag} navigate={`$/tags?t=${tag}`} className="tag" label={tag} />
|
<Tag key={tag} title={tag} name={tag} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -44,7 +44,7 @@ class CreditAmount extends React.PureComponent<Props> {
|
||||||
|
|
||||||
let amountText;
|
let amountText;
|
||||||
if (showFree && isFree) {
|
if (showFree && isFree) {
|
||||||
amountText = __('FREE');
|
amountText = __('Free');
|
||||||
} else {
|
} else {
|
||||||
amountText = formattedAmount;
|
amountText = formattedAmount;
|
||||||
|
|
||||||
|
|
|
@ -240,4 +240,17 @@ export const icons = {
|
||||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.ALERT]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
|
<line x1="12" y1="16" x2="12" y2="16" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
|
[ICONS.UNLOCK]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { useEffect, Fragment } from 'react';
|
import React, { useEffect, Fragment } from 'react';
|
||||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||||
import { buildURI, THUMBNAIL_STATUSES } from 'lbry-redux';
|
import { buildURI, isURIValid, THUMBNAIL_STATUSES } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ChannelSection from 'component/selectChannel';
|
import ChannelSection from 'component/selectChannel';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -106,11 +106,14 @@ function PublishForm(props: Props) {
|
||||||
|
|
||||||
if (channelName) {
|
if (channelName) {
|
||||||
// resolve without the channel name so we know the winning bid for it
|
// resolve without the channel name so we know the winning bid for it
|
||||||
const uriLessChannel = buildURI({ contentName: name });
|
try {
|
||||||
resolveUri(uriLessChannel);
|
const uriLessChannel = buildURI({ contentName: name });
|
||||||
|
resolveUri(uriLessChannel);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri) {
|
const isValid = isURIValid(uri);
|
||||||
|
if (uri && isValid) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
updatePublishForm({ uri });
|
updatePublishForm({ uri });
|
||||||
}
|
}
|
||||||
|
@ -130,6 +133,7 @@ function PublishForm(props: Props) {
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<TagSelect
|
<TagSelect
|
||||||
title={false}
|
title={false}
|
||||||
|
suggestMature
|
||||||
help={__('The better your tags are, the easier it will be for people to discover your content.')}
|
help={__('The better your tags are, the easier it will be for people to discover your content.')}
|
||||||
empty={__('No tags added')}
|
empty={__('No tags added')}
|
||||||
onSelect={tag => updatePublishForm({ tags: [...tags, tag] })}
|
onSelect={tag => updatePublishForm({ tags: [...tags, tag] })}
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Props = {
|
||||||
updatePublishForm: ({}) => void,
|
updatePublishForm: ({}) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishText(props: Props) {
|
function PublishName(props: Props) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
channel,
|
channel,
|
||||||
|
@ -49,7 +49,7 @@ function PublishText(props: Props) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
nameError = __('A name is required');
|
nameError = __('A name is required');
|
||||||
} else if (!isNameValid(name, false)) {
|
} else if (!isNameValid(name, false)) {
|
||||||
nameError = __('LBRY names cannot contain that symbol ($, #, @)');
|
nameError = __('LBRY names cannot contain spaces or reserved symbols ($#@;/"<>%{}|^~[]`)');
|
||||||
}
|
}
|
||||||
|
|
||||||
setNameError(nameError);
|
setNameError(nameError);
|
||||||
|
@ -120,4 +120,4 @@ function PublishText(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PublishText;
|
export default PublishName;
|
||||||
|
|
|
@ -4,7 +4,9 @@ import React, { useState } from 'react';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import FileSelector from 'component/common/file-selector';
|
import FileSelector from 'component/common/file-selector';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
// @if TARGET='app'
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
// @endif
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import uuid from 'uuid/v4';
|
import uuid from 'uuid/v4';
|
||||||
|
|
||||||
|
@ -34,6 +36,11 @@ function SelectAsset(props: Props) {
|
||||||
|
|
||||||
function doUploadAsset(filePath, thumbnailBuffer) {
|
function doUploadAsset(filePath, thumbnailBuffer) {
|
||||||
let thumbnail, fileExt, fileName, fileType;
|
let thumbnail, fileExt, fileName, fileType;
|
||||||
|
if (IS_WEB) {
|
||||||
|
console.error('no upload support for web');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
thumbnail = fs.readFileSync(filePath);
|
thumbnail = fs.readFileSync(filePath);
|
||||||
fileExt = path.extname(filePath);
|
fileExt = path.extname(filePath);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import useHover from 'util/use-hover';
|
||||||
|
|
||||||
type SubscribtionArgs = {
|
type SubscribtionArgs = {
|
||||||
channelName: string,
|
channelName: string,
|
||||||
|
@ -33,28 +34,12 @@ export default function SubscribeButton(props: Props) {
|
||||||
doToast,
|
doToast,
|
||||||
} = props;
|
} = props;
|
||||||
const buttonRef = useRef();
|
const buttonRef = useRef();
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const isHovering = useHover(buttonRef);
|
||||||
const { claimName } = parseURI(uri);
|
const { claimName } = parseURI(uri);
|
||||||
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
||||||
const subscriptionLabel = isSubscribed ? __('Following') : __('Follow');
|
const subscriptionLabel = isSubscribed ? __('Following') : __('Follow');
|
||||||
const unfollowOverride = isSubscribed && isHovering && __('Unfollow');
|
const unfollowOverride = isSubscribed && isHovering && __('Unfollow');
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function handleHover() {
|
|
||||||
setIsHovering(!isHovering);
|
|
||||||
}
|
|
||||||
|
|
||||||
const button = buttonRef.current;
|
|
||||||
if (button) {
|
|
||||||
button.addEventListener('mouseover', handleHover);
|
|
||||||
button.addEventListener('mouseleave', handleHover);
|
|
||||||
return () => {
|
|
||||||
button.removeEventListener('mouseover', handleHover);
|
|
||||||
button.removeEventListener('mouseleave', handleHover);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [buttonRef, isHovering]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
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 { MATURE_TAGS } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -13,13 +14,14 @@ type Props = {
|
||||||
|
|
||||||
export default function Tag(props: Props) {
|
export default function Tag(props: Props) {
|
||||||
const { name, onClick, type = 'link', disabled = false } = props;
|
const { name, onClick, type = 'link', disabled = false } = props;
|
||||||
|
const isMature = MATURE_TAGS.includes(name);
|
||||||
const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` };
|
const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` };
|
||||||
|
|
||||||
let title;
|
let title;
|
||||||
if (!onClick) {
|
if (!onClick) {
|
||||||
title = __('View tag');
|
title = __('View tag');
|
||||||
} else {
|
} else {
|
||||||
type === 'add' ? __('Add tag') : __('Remove tag');
|
title = type === 'add' ? __('Add tag') : __('Remove tag');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -28,8 +30,10 @@ export default function Tag(props: Props) {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={title}
|
title={title}
|
||||||
className={classnames('tag', {
|
className={classnames('tag', {
|
||||||
'tag--add': type === 'add',
|
|
||||||
'tag--remove': type === 'remove',
|
'tag--remove': type === 'remove',
|
||||||
|
// tag--add only adjusts the color, which causes issues with mature tag color clashing
|
||||||
|
'tag--add': !isMature && type === 'add',
|
||||||
|
'tag--mature': isMature,
|
||||||
})}
|
})}
|
||||||
label={name}
|
label={name}
|
||||||
iconSize={12}
|
iconSize={12}
|
||||||
|
|
|
@ -16,10 +16,11 @@ type Props = {
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
doAddTag: string => void,
|
doAddTag: string => void,
|
||||||
onSelect?: Tag => void,
|
onSelect?: Tag => void,
|
||||||
|
suggestMature?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelect(props: Props) {
|
export default function TagSelect(props: Props) {
|
||||||
const { unfollowedTags = [], followedTags = [], doToggleTagFollow, doAddTag, onSelect } = props;
|
const { unfollowedTags = [], followedTags = [], doToggleTagFollow, doAddTag, onSelect, suggestMature } = props;
|
||||||
const [newTag, setNewTag] = useState('');
|
const [newTag, setNewTag] = useState('');
|
||||||
|
|
||||||
let tags = unfollowedTags.slice();
|
let tags = unfollowedTags.slice();
|
||||||
|
@ -34,6 +35,10 @@ export default function TagSelect(props: Props) {
|
||||||
.filter(doesTagMatch)
|
.filter(doesTagMatch)
|
||||||
.slice(0, 5);
|
.slice(0, 5);
|
||||||
|
|
||||||
|
if (!newTag && suggestMature) {
|
||||||
|
suggestedTags.push('mature');
|
||||||
|
}
|
||||||
|
|
||||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag, unfollowedTagsAnimation);
|
const suggestedTransitions = useTransition(suggestedTags, tag => tag, unfollowedTagsAnimation);
|
||||||
|
|
||||||
function onChange(e) {
|
function onChange(e) {
|
||||||
|
@ -69,7 +74,7 @@ export default function TagSelect(props: Props) {
|
||||||
<div>
|
<div>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<FormField
|
<FormField
|
||||||
label={__('Tags')}
|
label={__('Tag Search')}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder={__('Search for more tags')}
|
placeholder={__('Search for more tags')}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Props = {
|
||||||
showClose: boolean,
|
showClose: boolean,
|
||||||
followedTags: Array<Tag>,
|
followedTags: Array<Tag>,
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
|
suggestMature: boolean,
|
||||||
|
|
||||||
// Ovverides
|
// Ovverides
|
||||||
// The default component is for following tags
|
// The default component is for following tags
|
||||||
|
@ -29,10 +30,22 @@ const tagsAnimation = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TagSelect(props: Props) {
|
export default function TagSelect(props: Props) {
|
||||||
const { showClose, followedTags, doToggleTagFollow, title, help, empty, tagsChosen, onSelect, onRemove } = props;
|
const {
|
||||||
|
showClose,
|
||||||
|
followedTags,
|
||||||
|
doToggleTagFollow,
|
||||||
|
title,
|
||||||
|
help,
|
||||||
|
empty,
|
||||||
|
tagsChosen,
|
||||||
|
onSelect,
|
||||||
|
onRemove,
|
||||||
|
suggestMature,
|
||||||
|
} = props;
|
||||||
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false);
|
||||||
const tagsToDisplay = tagsChosen || followedTags;
|
const tagsToDisplay = tagsChosen || followedTags;
|
||||||
const transitions = useTransition(tagsToDisplay, tag => tag.name, tagsAnimation);
|
const transitions = useTransition(tagsToDisplay, tag => tag.name, tagsAnimation);
|
||||||
|
const hasMatureTag = tagsToDisplay.map(tag => tag.name).includes('mature');
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
setHasClosed(true);
|
setHasClosed(true);
|
||||||
|
@ -73,7 +86,7 @@ export default function TagSelect(props: Props) {
|
||||||
<div className="empty">{empty || __("You aren't following any tags, try searching for one.")}</div>
|
<div className="empty">{empty || __("You aren't following any tags, try searching for one.")}</div>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
<TagsSearch onSelect={onSelect} />
|
<TagsSearch onSelect={onSelect} suggestMature={suggestMature && !hasMatureTag} />
|
||||||
{help !== false && (
|
{help !== false && (
|
||||||
<p className="help">{help || __("The tags you follow will change what's trending for you.")}</p>
|
<p className="help">{help || __("The tags you follow will change what's trending for you.")}</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -121,7 +121,7 @@ class TransactionList extends React.PureComponent<Props> {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && !transactionList.length && (
|
{!loading && !transactionList.length && (
|
||||||
<p className="main--empty empty">{emptyMessage || __('No transactions.')}</p>
|
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!!transactionList.length && (
|
{!!transactionList.length && (
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
export const defaultFollowedTags = [
|
|
||||||
'blockchain',
|
|
||||||
'news',
|
|
||||||
'learning',
|
|
||||||
'technology',
|
|
||||||
'automotive',
|
|
||||||
'economics',
|
|
||||||
'food',
|
|
||||||
'science',
|
|
||||||
'art',
|
|
||||||
'nature',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const defaultKnownTags = ['beliefs', 'funny', 'gaming', 'pop culture', 'music', 'sports', 'weapons'];
|
|
|
@ -312,7 +312,7 @@ class FilePage extends React.Component<Props> {
|
||||||
/>
|
/>
|
||||||
<div className="file-properties">
|
<div className="file-properties">
|
||||||
{isRewardContent && <Icon size={20} iconColor="red" icon={icons.FEATURED} />}
|
{isRewardContent && <Icon size={20} iconColor="red" icon={icons.FEATURED} />}
|
||||||
{nsfw && <div className="badge badge--nsfw">MATURE</div>}
|
{nsfw && <div className="badge badge--mature">{__('Mature')}</div>}
|
||||||
<FilePrice badge uri={normalizeURI(uri)} />
|
<FilePrice badge uri={normalizeURI(uri)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React, { createRef } from 'react';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import ClaimListDiscover from 'component/claimListDiscover';
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
import useHover from 'util/use-hover';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
location: { search: string },
|
location: { search: string },
|
||||||
|
@ -16,6 +17,8 @@ function TagsPage(props: Props) {
|
||||||
followedTags,
|
followedTags,
|
||||||
doToggleTagFollow,
|
doToggleTagFollow,
|
||||||
} = props;
|
} = props;
|
||||||
|
const buttonRef = createRef();
|
||||||
|
const isHovering = useHover(buttonRef);
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const tagsQuery = urlParams.get('t') || '';
|
const tagsQuery = urlParams.get('t') || '';
|
||||||
|
@ -25,18 +28,16 @@ function TagsPage(props: Props) {
|
||||||
const tag = tags[0];
|
const tag = tags[0];
|
||||||
|
|
||||||
const isFollowing = followedTags.map(({ name }) => name).includes(tag);
|
const isFollowing = followedTags.map(({ name }) => name).includes(tag);
|
||||||
|
let label = isFollowing ? __('Following') : __('Follow');
|
||||||
|
if (isHovering && isFollowing) {
|
||||||
|
label = __('Unfollow');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
tags={tags}
|
tags={tags}
|
||||||
meta={
|
meta={<Button ref={buttonRef} button="link" onClick={() => doToggleTagFollow(tag)} label={label} />}
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
onClick={() => doToggleTagFollow(tag)}
|
|
||||||
label={isFollowing ? __('Following') : __('Follow')}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
searchReducer,
|
searchReducer,
|
||||||
walletReducer,
|
walletReducer,
|
||||||
notificationsReducer,
|
notificationsReducer,
|
||||||
tagsReducerBuilder,
|
tagsReducer,
|
||||||
commentReducer,
|
commentReducer,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc';
|
import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc';
|
||||||
|
@ -16,17 +16,6 @@ import contentReducer from 'redux/reducers/content';
|
||||||
import settingsReducer from 'redux/reducers/settings';
|
import settingsReducer from 'redux/reducers/settings';
|
||||||
import subscriptionsReducer from 'redux/reducers/subscriptions';
|
import subscriptionsReducer from 'redux/reducers/subscriptions';
|
||||||
import publishReducer from 'redux/reducers/publish';
|
import publishReducer from 'redux/reducers/publish';
|
||||||
import { defaultKnownTags, defaultFollowedTags } from 'constants/tags';
|
|
||||||
|
|
||||||
function getDefaultKnownTags() {
|
|
||||||
return defaultFollowedTags.concat(defaultKnownTags).reduce(
|
|
||||||
(tagsMap, tag) => ({
|
|
||||||
...tagsMap,
|
|
||||||
[tag]: { name: tag },
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default history =>
|
export default history =>
|
||||||
combineReducers({
|
combineReducers({
|
||||||
|
@ -47,7 +36,7 @@ export default history =>
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
stats: statsReducer,
|
stats: statsReducer,
|
||||||
subscriptions: subscriptionsReducer,
|
subscriptions: subscriptionsReducer,
|
||||||
tags: tagsReducerBuilder({ followedTags: defaultFollowedTags, knownTags: getDefaultKnownTags() }),
|
tags: tagsReducer,
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
wallet: walletReducer,
|
wallet: walletReducer,
|
||||||
});
|
});
|
||||||
|
|
|
@ -155,6 +155,7 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
||||||
license_url: licenseUrl,
|
license_url: licenseUrl,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
title,
|
title,
|
||||||
|
tags,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
const publishData: UpdatePublishFormData = {
|
const publishData: UpdatePublishFormData = {
|
||||||
|
@ -171,6 +172,7 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
||||||
uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined,
|
uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined,
|
||||||
licenseUrl,
|
licenseUrl,
|
||||||
nsfw: isClaimNsfw(claim),
|
nsfw: isClaimNsfw(claim),
|
||||||
|
tags: tags ? tags.map(tag => ({ name: tag })) : [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channelName) {
|
if (channelName) {
|
||||||
|
|
|
@ -97,14 +97,14 @@ export const selectTakeOverAmount = createSelector(
|
||||||
const claimForShortUri = claimsByUri[shortUri];
|
const claimForShortUri = claimsByUri[shortUri];
|
||||||
|
|
||||||
if (!myClaimForUri && claimForShortUri) {
|
if (!myClaimForUri && claimForShortUri) {
|
||||||
return claimForShortUri.effective_amount;
|
return claimForShortUri.meta.effective_amount;
|
||||||
} else if (myClaimForUri && claimForShortUri) {
|
} else if (myClaimForUri && claimForShortUri) {
|
||||||
// https://github.com/lbryio/lbry/issues/1476
|
// https://github.com/lbryio/lbry/issues/1476
|
||||||
// We should check the current effective_amount on my claim to see how much additional lbc
|
// We should check the current effective_amount on my claim to see how much additional lbc
|
||||||
// is needed to win the claim. Currently this is not possible during a takeover.
|
// is needed to win the claim. Currently this is not possible during a takeover.
|
||||||
// With this, we could say something like, "You have x lbc in support, if you bid y additional LBC you will control the claim"
|
// With this, we could say something like, "You have x lbc in support, if you bid y additional LBC you will control the claim"
|
||||||
// For now just ignore supports. We will just show the winning claim's bid amount
|
// For now just ignore supports. We will just show the winning claim's bid amount
|
||||||
return claimForShortUri.effective_amount || claimForShortUri.amount;
|
return claimForShortUri.meta.effective_amount || claimForShortUri.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -19,3 +19,12 @@
|
||||||
background-color: $lbry-red-2;
|
background-color: $lbry-red-2;
|
||||||
color: $lbry-white;
|
color: $lbry-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge--mature {
|
||||||
|
background-color: lighten($lbry-grape-1, 10%);
|
||||||
|
color: $lbry-black;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: $lbry-black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -78,8 +78,8 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 100px;
|
margin-top: 100px;
|
||||||
padding-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ $main: $lbry-teal-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
|
@extend .badge;
|
||||||
@extend .badge--tag;
|
@extend .badge--tag;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -61,12 +62,20 @@ $main: $lbry-teal-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag--remove {
|
.tag--remove {
|
||||||
@extend .tag;
|
|
||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag--add {
|
.tag--add {
|
||||||
background-color: lighten($lbry-teal-5, 60%);
|
background-color: lighten($lbry-teal-5, 60%);
|
||||||
|
|
||||||
|
&.tag--mature {
|
||||||
|
@extend .badge--mature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag--mature {
|
||||||
|
@extend .badge--mature;
|
||||||
|
// Lighten the color a little so it doesn't stand out as much on claim previews
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag__action-label {
|
.tag__action-label {
|
||||||
|
|
|
@ -73,7 +73,7 @@ $large-breakpoint: 1921px;
|
||||||
// Image
|
// Image
|
||||||
--thumbnail-preview-height: 100px;
|
--thumbnail-preview-height: 100px;
|
||||||
--thumbnail-preview-width: 177px;
|
--thumbnail-preview-width: 177px;
|
||||||
--cover-photo-height: 300px;
|
--cover-photo-height: 160px;
|
||||||
--channel-thumbnail-width: 10rem;
|
--channel-thumbnail-width: 10rem;
|
||||||
--file-list-thumbnail-width: 10rem;
|
--file-list-thumbnail-width: 10rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ const appFilter = createFilter('app', ['hasClickedComment', 'searchOptionsExpand
|
||||||
// We only need to persist the receiveAddress for the wallet
|
// We only need to persist the receiveAddress for the wallet
|
||||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||||
const searchFilter = createFilter('search', ['options']);
|
const searchFilter = createFilter('search', ['options']);
|
||||||
|
const tagsFilter = createFilter('tags', ['followedTags']);
|
||||||
const whiteListedReducers = [
|
const whiteListedReducers = [
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
'publish',
|
'publish',
|
||||||
|
@ -86,6 +87,7 @@ const whiteListedReducers = [
|
||||||
'search',
|
'search',
|
||||||
'tags',
|
'tags',
|
||||||
];
|
];
|
||||||
|
|
||||||
const persistOptions = {
|
const persistOptions = {
|
||||||
whitelist: whiteListedReducers,
|
whitelist: whiteListedReducers,
|
||||||
// Order is important. Needs to be compressed last or other transforms can't
|
// Order is important. Needs to be compressed last or other transforms can't
|
||||||
|
@ -98,6 +100,7 @@ const persistOptions = {
|
||||||
// @endif
|
// @endif
|
||||||
appFilter,
|
appFilter,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
|
tagsFilter,
|
||||||
compressor,
|
compressor,
|
||||||
],
|
],
|
||||||
debounce: 5000,
|
debounce: 5000,
|
||||||
|
|
23
src/ui/util/use-hover.js
Normal file
23
src/ui/util/use-hover.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
No need to do this now, but I suspect these should end up with their own folder or another organizational method rather than being dumped in No need to do this now, but I suspect these should end up with their own folder or another organizational method rather than being dumped in `util`.
|
|||||||
|
|
||||||
|
export default function useHover(ref) {
|
||||||
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleHover() {
|
||||||
|
setIsHovering(!isHovering);
|
||||||
|
}
|
||||||
|
|
||||||
|
const refElement = ref.current;
|
||||||
|
if (refElement) {
|
||||||
|
refElement.addEventListener('mouseover', handleHover);
|
||||||
|
refElement.addEventListener('mouseleave', handleHover);
|
||||||
|
return () => {
|
||||||
|
refElement.removeEventListener('mouseover', handleHover);
|
||||||
|
refElement.removeEventListener('mouseleave', handleHover);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [ref, isHovering]);
|
||||||
|
|
||||||
|
return isHovering;
|
||||||
|
}
|
|
@ -368,7 +368,6 @@
|
||||||
"Add to your library": "Add to your library",
|
"Add to your library": "Add to your library",
|
||||||
"Web link": "Web link",
|
"Web link": "Web link",
|
||||||
"Facebook": "Facebook",
|
"Facebook": "Facebook",
|
||||||
"": "",
|
|
||||||
"Twitter": "Twitter",
|
"Twitter": "Twitter",
|
||||||
"View on Spee.ch": "View on Spee.ch",
|
"View on Spee.ch": "View on Spee.ch",
|
||||||
"LBRY App link": "LBRY App link",
|
"LBRY App link": "LBRY App link",
|
||||||
|
@ -473,8 +472,6 @@
|
||||||
"Editing": "Editing",
|
"Editing": "Editing",
|
||||||
"Edit Your Channel": "Edit Your Channel",
|
"Edit Your Channel": "Edit Your Channel",
|
||||||
"Editing Your Channel": "Editing Your Channel",
|
"Editing Your Channel": "Editing Your Channel",
|
||||||
"We can explain... We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use it.": "We can explain... We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use it.",
|
|
||||||
"We can explain... \n\n We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use it.": "We can explain... \n\n We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use it.",
|
|
||||||
"We can explain...": "We can explain...",
|
"We can explain...": "We can explain...",
|
||||||
"We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use": "We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use",
|
"We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use": "We know this page won't win any design awards, we have a cool idea for channel edits in the future. We just wanted to release a very very very basic version that just barely kinda works so people can use",
|
||||||
"We know this page won't win any design awards, we just wanted to release a very very very basic version that just barely kinda works so people can use": "We know this page won't win any design awards, we just wanted to release a very very very basic version that just barely kinda works so people can use",
|
"We know this page won't win any design awards, we just wanted to release a very very very basic version that just barely kinda works so people can use": "We know this page won't win any design awards, we just wanted to release a very very very basic version that just barely kinda works so people can use",
|
||||||
|
|
Loading…
Reference in a new issue
Below for loop can be functional. I admit that I may just enjoy rewriting these more than it is actually useful: