Merge pull request #2599 from lbryio/discovery-fixes

~Final~ Discovery fixes/cleanup
This commit is contained in:
Sean Yesmunt 2019-07-05 14:03:12 -04:00 committed by GitHub
commit 0e1a501ba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 157 additions and 98 deletions

View file

@ -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"

View file

@ -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 = {

View file

@ -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>

View file

@ -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;
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();

View file

@ -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>
); );

View file

@ -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;

View file

@ -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>
),
}; };

View file

@ -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
try {
const uriLessChannel = buildURI({ contentName: name }); const uriLessChannel = buildURI({ contentName: name });
resolveUri(uriLessChannel); 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] })}

View file

@ -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;

View file

@ -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);

View file

@ -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}

View file

@ -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}

View file

@ -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"

View file

@ -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>
)} )}

View file

@ -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 && (

View file

@ -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'];

View file

@ -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>

View file

@ -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>
); );

View file

@ -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,
}); });

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
} }

View file

@ -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 {

View file

@ -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;
} }

View file

@ -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
View file

@ -0,0 +1,23 @@
import { useEffect, useState } from 'react';
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;
}

View file

@ -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",