diff --git a/package.json b/package.json index 34daad47f..1ce2e38e9 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "proxy-polyfill": "0.1.6", "re-reselect": "^4.0.0", "react-beautiful-dnd": "^13.1.0", + "react-color": "^2.19.3", "react-datetime-picker": "^3.4.3", "remove-markdown": "^0.3.0", "rss": "^1.2.2", @@ -89,7 +90,7 @@ "@meetfranz/electron-cookies": "^3.0.2", "@reach/auto-id": "^0.13.0", "@reach/combobox": "^0.12.1", - "@reach/menu-button": "0.7.4", + "@reach/menu-button": "0.8.6", "@reach/rect": "^0.16.0", "@reach/tabs": "^0.1.5", "@reach/tooltip": "^0.12.1", diff --git a/static/app-strings.json b/static/app-strings.json index 649979674..2661cf9e9 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2308,5 +2308,20 @@ "Yes, share with LBRY": "Yes, share with LBRY", "Search Uploads": "Search Uploads", "This refundable boost will improve the discoverability of this %claimTypeText% while active. ": "This refundable boost will improve the discoverability of this %claimTypeText% while active. ", + "%repost_channel_link%": "%repost_channel_link%", + "Show less": "Show less", + "Channel \"realporno\" blocked.": "Channel \"realporno\" blocked.", + "Elements": "Elements", + "Icons": "Icons", + "You followed @MinutePhysics!": "You followed @MinutePhysics!", + "Unfollowed @samtime.": "Unfollowed @samtime.", + "You followed @samtime!": "You followed @samtime!", + "Unfollowed @gatogalactico.": "Unfollowed @gatogalactico.", + "You followed @gatogalactico!": "You followed @gatogalactico!", + "Unfollowed @Odysee.": "Unfollowed @Odysee.", + "Unfollowed @rossmanngroup.": "Unfollowed @rossmanngroup.", + "You followed @rossmanngroup!": "You followed @rossmanngroup!", + "%repost% %publish%": "%repost% %publish%", + "Failed to view lbry://@MicheL-PDF#7/LaDameAuPain#f, please try again. If this problem persists, visit https://lbry.com/faq/support for support.": "Failed to view lbry://@MicheL-PDF#7/LaDameAuPain#f, please try again. If this problem persists, visit https://lbry.com/faq/support for support.", "--end--": "--end--" } diff --git a/static/img/freespch.png b/static/img/freespch.png new file mode 100644 index 000000000..559067192 Binary files /dev/null and b/static/img/freespch.png differ diff --git a/ui/component/blockList/view.jsx b/ui/component/blockList/view.jsx index 4a45f8f95..5f1f48d06 100644 --- a/ui/component/blockList/view.jsx +++ b/ui/component/blockList/view.jsx @@ -118,7 +118,7 @@ export default function BlockList(props: Props) { return ( <>
{help}
-
+
{ if (success) { onDone(); @@ -269,9 +270,10 @@ function ChannelForm(props: Props) { } // TODO clear and bail after submit + //
return ( <> -
+
- + {__('General')} {__('Credit Details')} diff --git a/ui/component/channelStakedIndicator/view.jsx b/ui/component/channelStakedIndicator/view.jsx index 2103938d9..308360571 100644 --- a/ui/component/channelStakedIndicator/view.jsx +++ b/ui/component/channelStakedIndicator/view.jsx @@ -13,6 +13,7 @@ type Props = { level: number, large?: boolean, inline?: boolean, + hideTooltip?: Boolean, }; function getChannelIcon(level: number): string { @@ -27,8 +28,20 @@ function getChannelIcon(level: number): string { return icons[level] || ICONS.CHANNEL_LEVEL_1; } +// function getChannelIconB(level: number): string { +// const icons = { +// '1': ICONS.CHANNEL_LEVEL_1_B, +// '2': ICONS.CHANNEL_LEVEL_2_B, +// '3': ICONS.CHANNEL_LEVEL_3_B, +// '4': ICONS.CHANNEL_LEVEL_4_B, +// '5': ICONS.CHANNEL_LEVEL_5_B, +// }; +// +// return icons[level] || ICONS.CHANNEL_LEVEL_1_B; +// } + function ChannelStakedIndicator(props: Props) { - const { channelClaim, amount, level, large = false, inline = false } = props; + const { channelClaim, amount, level, large = false, inline = false, hideTooltip = false } = props; if (!channelClaim || !channelClaim.meta) { return null; @@ -36,24 +49,38 @@ function ChannelStakedIndicator(props: Props) { const isControlling = channelClaim && channelClaim.meta.is_controlling; const icon = getChannelIcon(level); + // const icon_b = getChannelIconB(level); - return ( - -
- -
+ if (!hideTooltip) { + return ( + +
+ +
-
- {__('Level %current_level%', { current_level: level })} -
- } size={14} /> +
+ {__('Level %current_level%', { current_level: level })} +
+ } size={14} /> +
+ } + > +
+
- } - > + + ); + } else { + return (
- - ); + ); + } } type LevelIconProps = { diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index c5bc3dd02..e24c58608 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -23,6 +23,7 @@ type Props = { showDelayedMessage?: boolean, noLazyLoad?: boolean, hideStakedIndicator?: boolean, + hideTooltip?: boolean, xsmall?: boolean, noOptimization?: boolean, setThumbUploadError: (boolean) => void, @@ -45,11 +46,12 @@ function ChannelThumbnail(props: Props) { showDelayedMessage = false, noLazyLoad, hideStakedIndicator = false, + hideTooltip, setThumbUploadError, ThumbUploadError, } = props; const [thumbLoadError, setThumbLoadError] = React.useState(ThumbUploadError); - const shouldResolve = claim === undefined; + const shouldResolve = !isResolving && claim === undefined; const thumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://'); const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://'); const defaultAvatar = AVATAR_DEFAULT || Gerbil; @@ -77,7 +79,7 @@ function ChannelThumbnail(props: Props) { if (isGif && !allowGifs) { return ( - {!hideStakedIndicator && } + {!hideStakedIndicator && } ); } @@ -91,6 +93,7 @@ function ChannelThumbnail(props: Props) { 'channel-thumbnail--resolving': isResolving, })} > + {/* show delay necessary? */} {showDelayedMessage ? (
{__('This will be visible in a few minutes.')}
) : ( diff --git a/ui/component/claimList/view.jsx b/ui/component/claimList/view.jsx index 891f926b8..01a935759 100644 --- a/ui/component/claimList/view.jsx +++ b/ui/component/claimList/view.jsx @@ -102,6 +102,7 @@ export default function ClaimList(props: Props) { let tileUris = (prefixUris || []).concat(uris || []); tileUris = tileUris.filter((uri) => !excludeUris.includes(uri)); + if (prefixUris && prefixUris.length) tileUris.splice(prefixUris.length * -1, prefixUris.length); const totalLength = tileUris.length; diff --git a/ui/component/claimMenuList/view.jsx b/ui/component/claimMenuList/view.jsx index e128c1303..f1f57787d 100644 --- a/ui/component/claimMenuList/view.jsx +++ b/ui/component/claimMenuList/view.jsx @@ -39,7 +39,7 @@ type Props = { doChannelUnmute: (string) => void, doCommentModBlock: (string) => void, doCommentModUnBlock: (string) => void, - doCommentModBlockAsAdmin: (string, string) => void, + doCommentModBlockAsAdmin: (string, ?string, ?string) => void, doCommentModUnBlockAsAdmin: (string, string) => void, doCollectionEdit: (string, any) => void, hasClaimInWatchLater: boolean, @@ -232,7 +232,7 @@ function ClaimMenuList(props: Props) { if (channelIsAdminBlocked) { doCommentModUnBlockAsAdmin(contentChannelUri, ''); } else { - doCommentModBlockAsAdmin(contentChannelUri, ''); + doCommentModBlockAsAdmin(contentChannelUri, undefined, undefined); } } diff --git a/ui/component/claimPreview/claim-preview-loading.jsx b/ui/component/claimPreview/claim-preview-loading.jsx index e7d313d7f..47c8e3665 100644 --- a/ui/component/claimPreview/claim-preview-loading.jsx +++ b/ui/component/claimPreview/claim-preview-loading.jsx @@ -11,17 +11,24 @@ function ClaimPreviewLoading(props: Props) { const { isChannel, type } = props; return (
  • -
    +
    -
    -
    +
    +
    +
    +
    +
    +
    +
    +
    +
  • diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index 67d05d74d..5664655af 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -30,6 +30,7 @@ import ClaimPreviewLoading from './claim-preview-loading'; import ClaimPreviewHidden from './claim-preview-no-mature'; import ClaimPreviewNoContent from './claim-preview-no-content'; import CollectionEditButtons from 'component/collectionEditButtons'; +import { useIsMobile } from 'effects/use-screensize'; import AbandonedChannelPreview from 'component/abandonedChannelPreview'; // preview images used on the landing page and on the channel page @@ -143,6 +144,8 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { unavailableUris, } = props; + const isMobile = useIsMobile(); + const isCollection = claim && claim.value_type === 'collection'; const collectionClaimId = isCollection && claim && claim.claim_id; const listId = collectionId || collectionClaimId; @@ -160,15 +163,18 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { return ; } const formattedSubCount = toCompactNotation(channelSubCount, lang, 10000); + const formattedSubCountLocale = Number(channelSubCount).toLocaleString(); return ( - - - {channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })} - - +
    + + + {channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })} + + +
    ); }, [channelSubCount]); - const isValid = uri && isURIValid(uri); + const isValid = uri && isURIValid(uri, false); // $FlowFixMe const isPlayable = @@ -359,7 +365,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
    {/* @endif */}
    - +
    @@ -380,9 +386,18 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { )}
    - - {(pending || !!reflectingProgress) && } - {channelSubscribers} +
    + {!isChannelUri && signingChannel && ( +
    + + + +
    + )} + + {(pending || !!reflectingProgress) && } + {channelSubscribers} +
    {type !== 'small' && (
    @@ -393,11 +408,6 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { actions ) : (
    - {!isChannelUri && signingChannel && ( -
    - -
    - )} {isChannelUri && !banState.muted && !claimIsMine && ( ((props: Props, ref: any) => { )} {claim && ( - {typeof properties === 'function' ? ( - properties(claim) - ) : properties !== undefined ? ( - properties - ) : ( - - )} + {typeof properties === 'function' + ? properties(claim) + : properties !== undefined + ? properties + : !isMobile && } )}
    diff --git a/ui/component/claimPreviewTile/view.jsx b/ui/component/claimPreviewTile/view.jsx index 52ee6d1e9..aea7bd313 100644 --- a/ui/component/claimPreviewTile/view.jsx +++ b/ui/component/claimPreviewTile/view.jsx @@ -8,7 +8,6 @@ import TruncatedText from 'component/common/truncated-text'; import DateTime from 'component/dateTime'; import ChannelThumbnail from 'component/channelThumbnail'; import FileViewCountInline from 'component/fileViewCountInline'; -import SubscribeButton from 'component/subscribeButton'; import useGetThumbnail from 'effects/use-get-thumbnail'; import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url'; import { formatClaimPreviewTitle } from 'util/formatAriaLabel'; @@ -85,7 +84,6 @@ function ClaimPreviewTile(props: Props) { const shouldFetch = claim === undefined; const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail; const canonicalUrl = claim && claim.canonical_url; - const permanentUrl = claim && claim.permanent_url; const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url); const listId = collectionId || collectionClaimId; const navigateUrl = @@ -110,7 +108,6 @@ function ClaimPreviewTile(props: Props) { const isChannel = claim && claim.value_type === 'channel'; const channelUri = !isChannel ? signingChannel && signingChannel.permanent_url : claim && claim.permanent_url; const channelTitle = signingChannel && ((signingChannel.value && signingChannel.value.title) || signingChannel.name); - const repostedChannelUri = isRepost && isChannel ? permanentUrl || canonicalUrl : undefined; // Aria-label value for claim preview let ariaLabelData = isChannel ? title : formatClaimPreviewTitle(title, channelTitle, date, mediaDuration); @@ -148,17 +145,24 @@ function ClaimPreviewTile(props: Props) { if (placeholder || (!claim && isResolvingUri)) { return ( -
  • -
    +
  • +
    Placeholder
    -
    +
    +
    + > +
    +
    +
    +
    +
    +
  • ); @@ -220,11 +224,7 @@ function ClaimPreviewTile(props: Props) { contains_view_count: shouldShowViewCount, })} > - {isChannel ? ( -
    - -
    - ) : ( + {!isChannel && ( diff --git a/ui/component/claimRepostAuthor/view.jsx b/ui/component/claimRepostAuthor/view.jsx index 4525560b0..c4daeba81 100644 --- a/ui/component/claimRepostAuthor/view.jsx +++ b/ui/component/claimRepostAuthor/view.jsx @@ -19,8 +19,11 @@ function ClaimRepostAuthor(props: Props) { if (short && repostUrl) { return ( - - {repostUrl} +
    + +
    + {repostUrl} +
    ); } @@ -47,10 +50,13 @@ function ClaimRepostAuthor(props: Props) { return (
    - - }}> - %repost_channel_link% reposted - +
    + +
    + }}> + %repost_channel_link% + +
    ); } diff --git a/ui/component/claimTilesDiscover/index.js b/ui/component/claimTilesDiscover/index.js index d8237d0c3..5aed218be 100644 --- a/ui/component/claimTilesDiscover/index.js +++ b/ui/component/claimTilesDiscover/index.js @@ -9,6 +9,7 @@ import { doToggleTagFollowDesktop } from 'redux/actions/tags'; import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings'; import { selectMutedAndBlockedChannelIds } from 'redux/selectors/blocked'; import { ENABLE_NO_SOURCE_CLAIMS } from 'config'; +import { createNormalizedClaimSearchKey } from 'util/claim'; import ClaimListDiscover from './view'; @@ -17,13 +18,22 @@ const select = (state, props) => { const hideReposts = makeSelectClientSetting(SETTINGS.HIDE_REPOSTS)(state); const mutedAndBlockedChannelIds = selectMutedAndBlockedChannelIds(state); - return { - claimSearchByQuery: selectClaimSearchByQuery(state), - claimsByUri: selectClaimsByUri(state), - fetchingClaimSearchByQuery: selectFetchingClaimSearchByQuery(state), + const options = resolveSearchOptions({ showNsfw, hideReposts, - options: resolveSearchOptions({ showNsfw, hideReposts, mutedAndBlockedChannelIds, pageSize: 8, ...props }), + mutedAndBlockedChannelIds, + pageSize: 8, + ...props, + }); + const searchKey = createNormalizedClaimSearchKey(options); + + return { + claimSearchResults: selectClaimSearchByQuery(state)[searchKey], + claimsByUri: selectClaimsByUri(state), + fetchingClaimSearch: selectFetchingClaimSearchByQuery(state)[searchKey], + showNsfw, + hideReposts, + optionsStringified: JSON.stringify(options), }; }; diff --git a/ui/component/claimTilesDiscover/view.jsx b/ui/component/claimTilesDiscover/view.jsx index f43d7a7d1..af17e6ec8 100644 --- a/ui/component/claimTilesDiscover/view.jsx +++ b/ui/component/claimTilesDiscover/view.jsx @@ -1,31 +1,29 @@ // @flow import type { Node } from 'react'; import React from 'react'; -import { createNormalizedClaimSearchKey } from 'util/claim'; import ClaimPreviewTile from 'component/claimPreviewTile'; import useFetchViewCount from 'effects/use-fetch-view-count'; -import usePrevious from 'effects/use-previous'; -type SearchOptions = { - page_size: number, - page: number, - no_totals: boolean, - any_tags: Array, - channel_ids: Array, - claim_ids?: Array, - not_channel_ids: Array, - not_tags: Array, - order_by: Array, - languages?: Array, - release_time?: string, - claim_type?: string | Array, - timestamp?: string, - fee_amount?: string, - limit_claims_per_channel?: number, - stream_types?: Array, - has_source?: boolean, - has_no_source?: boolean, -}; +// type SearchOptions = { +// page_size: number, +// page: number, +// no_totals: boolean, +// any_tags: Array, +// channel_ids: Array, +// claim_ids?: Array, +// not_channel_ids: Array, +// not_tags: Array, +// order_by: Array, +// languages?: Array, +// release_time?: string, +// claim_type?: string | Array, +// timestamp?: string, +// fee_amount?: string, +// limit_claims_per_channel?: number, +// stream_types?: Array, +// has_source?: boolean, +// has_no_source?: boolean, +// }; function urisEqual(prev: ?Array, next: ?Array) { if (!prev || !next) { @@ -68,12 +66,12 @@ type Props = { hasNoSource?: boolean, // --- select --- location: { search: string }, - claimSearchByQuery: { [string]: Array }, + claimSearchResults: Array, claimsByUri: { [string]: any }, - fetchingClaimSearchByQuery: { [string]: boolean }, + fetchingClaimSearch: boolean, showNsfw: boolean, hideReposts: boolean, - options: SearchOptions, + optionsStringified: string, // --- perform --- doClaimSearch: ({}) => void, doFetchViewCount: (claimIdCsv: string) => void, @@ -82,10 +80,10 @@ type Props = { function ClaimTilesDiscover(props: Props) { const { doClaimSearch, - claimSearchByQuery, + claimSearchResults, claimsByUri, fetchViewCount, - fetchingClaimSearchByQuery, + fetchingClaimSearch, hasNoSource, renderProperties, // pinUrls, @@ -93,16 +91,15 @@ function ClaimTilesDiscover(props: Props) { showNoSourceClaims, doFetchViewCount, pageSize = 8, - options, + optionsStringified, } = props; - const searchKey = createNormalizedClaimSearchKey(options); - const fetchingClaimSearch = fetchingClaimSearchByQuery[searchKey]; - const claimSearchUris = claimSearchByQuery[searchKey] || []; - const isUnfetchedClaimSearch = claimSearchByQuery[searchKey] === undefined; + const prevUris = React.useRef(); + const claimSearchUris = claimSearchResults || []; + + const isUnfetchedClaimSearch = claimSearchResults === undefined; // Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time - const optionsStringForEffect = JSON.stringify(options); const shouldPerformSearch = !fetchingClaimSearch && claimSearchUris.length === 0; const uris = (prefixUris || []).concat(claimSearchUris); @@ -124,21 +121,19 @@ function ClaimTilesDiscover(props: Props) { uris.push(...Array(pageSize - uris.length).fill('')); } - const prevUris = usePrevious(uris); - useFetchViewCount(fetchViewCount, uris, claimsByUri, doFetchViewCount); + const finalUris = isUnfetchedClaimSearch && prevUris.current ? prevUris.current : uris; + prevUris.current = finalUris; // Run `doClaimSearch` React.useEffect(() => { if (shouldPerformSearch) { - const searchOptions = JSON.parse(optionsStringForEffect); + const searchOptions = JSON.parse(optionsStringified); doClaimSearch(searchOptions); } - }, [doClaimSearch, shouldPerformSearch, optionsStringForEffect]); + }, [doClaimSearch, shouldPerformSearch, optionsStringified]); // Show previous results while we fetch to avoid blinkies and poor CLS. - const finalUris = isUnfetchedClaimSearch && prevUris ? prevUris : uris; - return (
      {finalUris && finalUris.length @@ -167,33 +162,33 @@ function ClaimTilesDiscover(props: Props) { export default React.memo(ClaimTilesDiscover, areEqual); -function debug_trace(val) { - if (process.env.DEBUG_TRACE) console.log(`Render due to: ${val}`); +// **************************************************************************** +// **************************************************************************** + +function trace(key, value) { + // @if process.env.DEBUG_TILE_RENDER + // $FlowFixMe "cannot coerce certain types". + console.log(`[claimTilesDiscover] ${key}: ${value}`); // eslint-disable-line no-console + // @endif } function areEqual(prev: Props, next: Props) { - const prevOptions: SearchOptions = prev.options; - const nextOptions: SearchOptions = next.options; - - const prevSearchKey = createNormalizedClaimSearchKey(prevOptions); - const nextSearchKey = createNormalizedClaimSearchKey(nextOptions); - - if (prevSearchKey !== nextSearchKey) { - debug_trace('search key'); - return false; - } - // --- Deep-compare --- - if (!urisEqual(prev.claimSearchByQuery[prevSearchKey], next.claimSearchByQuery[nextSearchKey])) { - debug_trace('claimSearchByQuery'); - return false; + // These are props that are hard to memoize from where it is passed. + + if (prev.claimType !== next.claimType) { + // Array: confirm the contents are actually different. + if (prev.claimType && next.claimType && JSON.stringify(prev.claimType) !== JSON.stringify(next.claimType)) { + trace('claimType', next.claimType); + return false; + } } const ARRAY_KEYS = ['prefixUris', 'channelIds']; for (let i = 0; i < ARRAY_KEYS.length; ++i) { const key = ARRAY_KEYS[i]; if (!urisEqual(prev[key], next[key])) { - debug_trace(`${key}`); + trace(key, next[key]); return false; } } @@ -203,22 +198,19 @@ function areEqual(prev: Props, next: Props) { // to update this function. Better to render more than miss an important one. const KEYS_TO_IGNORE = [ ...ARRAY_KEYS, - 'claimSearchByQuery', - 'fetchingClaimSearchByQuery', // We are showing previous results while fetching. - 'options', // Covered by search-key comparison. + 'claimType', // Handled above. + 'claimsByUri', // Used for view-count. Just ignore it for now. 'location', 'history', 'match', - 'claimsByUri', 'doClaimSearch', - 'doToggleTagFollowDesktop', ]; const propKeys = Object.keys(next); for (let i = 0; i < propKeys.length; ++i) { const pk = propKeys[i]; if (!KEYS_TO_IGNORE.includes(pk) && prev[pk] !== next[pk]) { - debug_trace(`${pk}`); + trace(pk, next[pk]); return false; } } diff --git a/ui/component/collectionActions/view.jsx b/ui/component/collectionActions/view.jsx index 1322a7002..6d40927e1 100644 --- a/ui/component/collectionActions/view.jsx +++ b/ui/component/collectionActions/view.jsx @@ -177,7 +177,7 @@ function CollectionActions(props: Props) { if (isMobile) { return ( -
      +
      {lhsSection} {rhsSection} {infoButtons} diff --git a/ui/component/collectionContentSidebar/view.jsx b/ui/component/collectionContentSidebar/view.jsx index 5ba082225..0ff4eeec9 100644 --- a/ui/component/collectionContentSidebar/view.jsx +++ b/ui/component/collectionContentSidebar/view.jsx @@ -58,21 +58,23 @@ export default function CollectionContent(props: Props) { return ( - - - {collectionName} - - + + + + {collectionName} + + +
      - - {isMyCollection && ( -
      @@ -181,7 +189,7 @@ const Header = (props: Props) => {
    - + ); } diff --git a/ui/component/postViewer/view.jsx b/ui/component/postViewer/view.jsx index 84eb408b5..85c32415c 100644 --- a/ui/component/postViewer/view.jsx +++ b/ui/component/postViewer/view.jsx @@ -52,17 +52,16 @@ function PostViewer(props: Props) { return (
    - - - - - - +
    + + + +
    -
    {expand === EXPAND.CREDIT_DETAILS && ( diff --git a/ui/component/publishForm/view.jsx b/ui/component/publishForm/view.jsx index 811e3bf46..8042975e8 100644 --- a/ui/component/publishForm/view.jsx +++ b/ui/component/publishForm/view.jsx @@ -129,6 +129,7 @@ function PublishForm(props: Props) { hasClaimedInitialRewards, } = props; + const inEditMode = Boolean(editingURI); const { replace, location } = useHistory(); const urlParams = new URLSearchParams(location.search); const TYPE_PARAM = 'type'; @@ -137,7 +138,7 @@ function PublishForm(props: Props) { // $FlowFixMe const AVAILABLE_MODES = Object.values(PUBLISH_MODES).filter((mode) => { // $FlowFixMe - if (editingURI) { + if (inEditMode) { if (isPostClaim) { return mode === PUBLISH_MODES.POST; } else { @@ -248,7 +249,7 @@ function PublishForm(props: Props) { submitLabel = __('Uploading...'); } } else if (previewing) { - submitLabel = __('Preparing...'); + submitLabel = ; } else { if (isStillEditing) { submitLabel = __('Save'); @@ -498,6 +499,7 @@ function PublishForm(props: Props) {
    {mode !== PUBLISH_MODES.POST && } } /> + { return {__('Uploading (%progress%%) ', { progress: progress })}; } else { return ( - +
    {__('Confirming...')} - +
    ); } }; diff --git a/ui/component/publishPrice/view.jsx b/ui/component/publishPrice/view.jsx index e21195cc0..16d7a49dd 100644 --- a/ui/component/publishPrice/view.jsx +++ b/ui/component/publishPrice/view.jsx @@ -14,44 +14,47 @@ function PublishPrice(props: Props) { const { contentIsFree, fee, updatePublishForm, disabled } = props; return ( - - updatePublishForm({ contentIsFree: true })} - /> - - updatePublishForm({ contentIsFree: false })} - /> - {!contentIsFree && ( - updatePublishForm({ fee: newFee })} + <> + + + updatePublishForm({ contentIsFree: true })} /> - )} - {fee && fee.currency !== 'LBC' && ( -

    - {__( - 'All content fees are charged in LBRY Credits. For alternative payment methods, the number of LBRY Credits charged will be adjusted based on the value of LBRY Credits at the time of purchase.' - )} -

    - )} - - } - /> + + updatePublishForm({ contentIsFree: false })} + /> + {!contentIsFree && ( + updatePublishForm({ fee: newFee })} + /> + )} + {fee && fee.currency !== 'LBC' && ( +

    + {__( + 'All content fees are charged in LBRY Credits. For alternative payment methods, the number of LBRY Credits charged will be adjusted based on the value of LBRY Credits at the time of purchase.' + )} +

    + )} + + } + /> + ); } diff --git a/ui/component/ratioBar/index.js b/ui/component/ratioBar/index.js new file mode 100644 index 000000000..738ce081c --- /dev/null +++ b/ui/component/ratioBar/index.js @@ -0,0 +1,6 @@ +import { connect } from 'react-redux'; +import RatioBar from './view'; + +const select = (state, props) => ({}); + +export default connect(select)(RatioBar); diff --git a/ui/component/ratioBar/view.jsx b/ui/component/ratioBar/view.jsx new file mode 100644 index 000000000..350c5f7cd --- /dev/null +++ b/ui/component/ratioBar/view.jsx @@ -0,0 +1,25 @@ +// @flow +import React from 'react'; + +type Props = { + likeCount: number, + dislikeCount: number, +}; + +const RatioBar = (props: Props) => { + const { likeCount, dislikeCount } = props; + + const like = (1 / (likeCount + dislikeCount)) * likeCount; + if (like || dislikeCount) { + return ( +
    +
    +
    +
    + ); + } else { + return
    ; + } +}; + +export default RatioBar; diff --git a/ui/component/recommendedContent/view.jsx b/ui/component/recommendedContent/view.jsx index a0cc00226..c067a763b 100644 --- a/ui/component/recommendedContent/view.jsx +++ b/ui/component/recommendedContent/view.jsx @@ -68,18 +68,18 @@ export default React.memo(function RecommendedContent(props: Props) { title={__('Related')} titleActions={ signingChannel && ( -
    +
    -
    - ) : ( -
    - {manualInput ? ( - - ) : ( - - openModal(MODALS.CONFIRM_THUMBNAIL_UPLOAD, { - file, - cb: (url) => !publishForm && updateThumbnailParams({ thumbnail_url: url }), - }) - } - /> - )} -
    -
    -
    - )} -
    + )} +
    + )} {status === THUMBNAIL_STATUSES.IN_PROGRESS &&

    {__('Uploading thumbnail')}...

    } diff --git a/ui/component/settingsRow/view.jsx b/ui/component/settingsRow/view.jsx index a59b7e52b..f6864e3db 100644 --- a/ui/component/settingsRow/view.jsx +++ b/ui/component/settingsRow/view.jsx @@ -7,16 +7,17 @@ type Props = { subtitle?: string, multirow?: boolean, // Displays the Value widget(s) below the Label instead of on the right. useVerticalSeparator?: boolean, // Show a separator line between Label and Value. Useful when there are multiple Values. + disabled?: boolean, children?: React$Node, }; export default function SettingsRow(props: Props) { - const { title, subtitle, multirow, useVerticalSeparator, children } = props; - + const { title, subtitle, multirow, useVerticalSeparator, disabled, children } = props; return (
    diff --git a/ui/component/sideNavigation/view.jsx b/ui/component/sideNavigation/view.jsx index e45285a29..6616ac606 100644 --- a/ui/component/sideNavigation/view.jsx +++ b/ui/component/sideNavigation/view.jsx @@ -8,11 +8,38 @@ import Button from 'component/button'; import classnames from 'classnames'; import Icon from 'component/common/icon'; import NotificationBubble from 'component/notificationBubble'; +import DebouncedInput from 'component/common/debounced-input'; import I18nMessage from 'component/i18nMessage'; import ChannelThumbnail from 'component/channelThumbnail'; -import { DOMAIN, ENABLE_UI_NOTIFICATIONS } from 'config'; +import { useIsMobile, isTouch } from 'effects/use-screensize'; import { IS_MAC } from 'component/app/view'; import { useHistory } from 'react-router'; +import { DOMAIN, ENABLE_UI_NOTIFICATIONS } from 'config'; + +const FOLLOWED_ITEM_INITIAL_LIMIT = 10; +const touch = isTouch(); +const { + location: { pathname }, +} = useHistory(); + +type SideNavLink = { + title: string, + link?: string, + route?: string, + onClick?: () => any, + icon: string, + extra?: Node, + hideForUnauth?: boolean, +}; + +const HOME = { + title: 'Home', + link: `/`, + icon: ICONS.HOME, + onClick: () => { + if (pathname === '/') window.location.reload(); + }, +}; const RECENT_FROM_FOLLOWING = { title: 'Following --[sidebar button]--', @@ -20,6 +47,57 @@ const RECENT_FROM_FOLLOWING = { icon: ICONS.SUBSCRIBE, }; +const DISCOVER = { + title: 'Discover', + link: `/$/${PAGES.DISCOVER}`, + icon: ICONS.DISCOVER, +}; + +const LIBRARY = { + title: 'Library', + link: `/$/${PAGES.LIBRARY}`, + icon: ICONS.PURCHASED, +}; + +const NOTIFICATIONS = { + title: 'Notifications', + link: `/$/${PAGES.NOTIFICATIONS}`, + icon: ICONS.NOTIFICATION, + extra: , +}; + +const PLAYLISTS = { + title: 'Lists', + link: `/$/${PAGES.LISTS}`, + icon: ICONS.STACK, +}; + +const UNAUTH_LINKS: Array = [ + { + title: 'Log In', + link: `/$/${PAGES.AUTH_SIGNIN}`, + icon: ICONS.SIGN_IN, + }, + { + title: 'Sign Up', + link: `/$/${PAGES.AUTH}`, + icon: ICONS.SIGN_UP, + }, + { + title: 'Settings', + link: `/$/${PAGES.SETTINGS}`, + icon: ICONS.SETTINGS, + }, + { + title: 'Help', + link: `/$/${PAGES.HELP}`, + icon: ICONS.HELP, + }, +]; + +// **************************************************************************** +// **************************************************************************** + type Props = { subscriptions: Array, followedTags: Array, @@ -35,16 +113,7 @@ type Props = { doClearPurchasedUriSuccess: () => void, user: ?User, homepageData: any, -}; - -type SideNavLink = { - title: string, - link?: string, - route?: string, - onClick?: () => any, - icon: string, - extra?: Node, - hideForUnauth?: boolean, + activeChannelStakedLevel: number, }; function SideNavigation(props: Props) { @@ -63,46 +132,7 @@ function SideNavigation(props: Props) { followedTags, } = props; - const { - location: { pathname }, - } = useHistory(); - - const HOME = { - title: 'Home', - link: `/`, - icon: ICONS.HOME, - onClick: () => { - if (pathname === '/') window.location.reload(); - }, - }; - const FULL_LINKS: Array = [ - { - title: 'Your Tags', - link: `/$/${PAGES.TAGS_FOLLOWING}`, - icon: ICONS.TAG, - hideForUnauth: true, - }, - { - title: 'Discover', - link: `/$/${PAGES.DISCOVER}`, - icon: ICONS.DISCOVER, - }, - { - title: 'Library', - link: `/$/${PAGES.LIBRARY}`, - icon: ICONS.PURCHASED, - hideForUnauth: true, - }, - ]; - const MOBILE_LINKS: Array = [ - { - title: 'Notifications', - link: `/$/${PAGES.NOTIFICATIONS}`, - icon: ICONS.NOTIFICATION, - extra: , - hideForUnauth: true, - }, { title: 'Upload', link: `/$/${PAGES.UPLOAD}`, @@ -171,56 +201,149 @@ function SideNavigation(props: Props) { }, ]; - const UNAUTH_LINKS: Array = [ - { - title: 'Log In', - link: `/$/${PAGES.AUTH_SIGNIN}`, - icon: ICONS.SIGN_IN, - }, - { - title: 'Sign Up', - link: `/$/${PAGES.AUTH}`, - icon: ICONS.SIGN_UP, - }, - { - title: 'Settings', - link: `/$/${PAGES.SETTINGS}`, - icon: ICONS.SETTINGS, - }, - { - title: 'Help', - link: `/$/${PAGES.HELP}`, - icon: ICONS.HELP, - }, - ]; - const notificationsEnabled = ENABLE_UI_NOTIFICATIONS || (user && user.experimental_ui); const isAuthenticated = Boolean(email); - // SIDE LINKS: FOLLOWING, HOME, [FULL,] [EXTRA] - let SIDE_LINKS: Array = []; - - SIDE_LINKS.push(HOME); - SIDE_LINKS.push(RECENT_FROM_FOLLOWING); - FULL_LINKS.push({ - title: 'Lists', - link: `/$/${PAGES.LISTS}`, - icon: ICONS.STACK, - hideForUnauth: true, - }); - SIDE_LINKS.push(...FULL_LINKS); const [pulseLibrary, setPulseLibrary] = React.useState(false); - const isAbsolute = isOnFilePage || isMediumScreen; - const microNavigation = !sidebarOpen || isMediumScreen; - const subLinks = email - ? MOBILE_LINKS.filter((link) => { - if (!notificationsEnabled && link.icon === ICONS.NOTIFICATION) { - return false; - } + const [expandSubscriptions, setExpandSubscriptions] = React.useState(false); + const [expandTags, setExpandTags] = React.useState(false); - return true; - }) - : UNAUTH_LINKS; + const isAbsolute = isOnFilePage || isMediumScreen; + const isMobile = useIsMobile(); + const [menuInitialized, setMenuInitialized] = React.useState(false); + const menuCanCloseCompletely = (isOnFilePage && !isMobile) || (isMobile && menuInitialized); + const hideMenuFromView = menuCanCloseCompletely && !sidebarOpen; + const [canDisposeMenu, setCanDisposeMenu] = React.useState(false); + + React.useEffect(() => { + if (hideMenuFromView || !menuInitialized) { + const handler = setTimeout(() => { + setMenuInitialized(true); + setCanDisposeMenu(true); + }, 250); + return () => { + clearTimeout(handler); + }; + } else { + setCanDisposeMenu(false); + } + }, [hideMenuFromView, menuInitialized]); + + const shouldRenderLargeMenu = menuCanCloseCompletely || sidebarOpen; + + const showMicroMenu = !sidebarOpen && !menuCanCloseCompletely; + const showPushMenu = sidebarOpen && !menuCanCloseCompletely; + + const showSubscriptionSection = shouldRenderLargeMenu && subscriptions && subscriptions.length > 0; + const showTagSection = sidebarOpen && followedTags && followedTags.length; + + const [subscriptionFilter, setSubscriptionFilter] = React.useState(''); + + const filteredSubscriptions = subscriptions.filter( + (sub) => !subscriptionFilter || sub.channelName.toLowerCase().includes(subscriptionFilter.toLowerCase()) + ); + + let displayedSubscriptions = filteredSubscriptions; + if ( + showSubscriptionSection && + !subscriptionFilter && + subscriptions.length > FOLLOWED_ITEM_INITIAL_LIMIT && + !expandSubscriptions + ) { + displayedSubscriptions = subscriptions.slice(0, FOLLOWED_ITEM_INITIAL_LIMIT); + } + + let displayedFollowedTags = followedTags; + if (showTagSection && followedTags.length > FOLLOWED_ITEM_INITIAL_LIMIT && !expandTags) { + displayedFollowedTags = followedTags.slice(0, FOLLOWED_ITEM_INITIAL_LIMIT); + } + + function getLink(props: SideNavLink) { + const { hideForUnauth, route, link, ...passedProps } = props; + const { title, icon, extra } = passedProps; + + if (hideForUnauth && !email) { + return null; + } + + return ( +
  • +
  • + ); + } + + function getSubscriptionSection() { + if (showSubscriptionSection) { + return ( + <> +
      + {subscriptions.length > FOLLOWED_ITEM_INITIAL_LIMIT && ( +
    • + +
    • + )} + {displayedSubscriptions.map((subscription) => ( + + ))} + {!!subscriptionFilter && !displayedSubscriptions.length && ( +
    • +
      +
      {__('No results')}
      +
      +
    • + )} + {!subscriptionFilter && subscriptions.length > FOLLOWED_ITEM_INITIAL_LIMIT && ( +
    + + ); + } + return null; + } + + function getFollowedTagsSection() { + if (showTagSection) { + return ( + <> +
      + {displayedFollowedTags.map(({ name }, key) => ( +
    • +
    • + ))} + {followedTags.length > FOLLOWED_ITEM_INITIAL_LIMIT && ( +
    + + ); + } + return null; + } React.useEffect(() => { if (purchaseSuccess) { @@ -292,151 +415,57 @@ function SideNavigation(props: Props) { return (
    - {!isOnFilePage && ( - +
    setSidebarOpen(false)} + />
    ); } diff --git a/ui/component/subscribeButton/view.jsx b/ui/component/subscribeButton/view.jsx index adc1c5be2..dda3fa588 100644 --- a/ui/component/subscribeButton/view.jsx +++ b/ui/component/subscribeButton/view.jsx @@ -69,7 +69,7 @@ export default function SubscribeButton(props: Props) { if (isSubscribed && !permanentUrl && rawChannelName) { return ( -
    +
    ) : null; diff --git a/ui/component/transactionListTable/view.jsx b/ui/component/transactionListTable/view.jsx index 6239d6589..8e86c3e4d 100644 --- a/ui/component/transactionListTable/view.jsx +++ b/ui/component/transactionListTable/view.jsx @@ -33,10 +33,10 @@ function TransactionListTable(props: Props) { - - + + - + diff --git a/ui/component/viewers/videoViewer/internal/videojs-events.jsx b/ui/component/viewers/videoViewer/internal/videojs-events.jsx index 1c665a962..914477c72 100644 --- a/ui/component/viewers/videoViewer/internal/videojs-events.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs-events.jsx @@ -25,6 +25,14 @@ const VideoJsEvents = ({ playerRef, autoplaySetting, replay, + claimId, + userId, + claimValues, + embedded, + uri, + doAnalyticsView, + claimRewards, + playerServerRef, }: { tapToUnmuteRef: any, // DOM element tapToRetryRef: any, // DOM element @@ -33,6 +41,15 @@ const VideoJsEvents = ({ playerRef: any, // DOM element autoplaySetting: boolean, replay: boolean, + claimId?: ?string, + userId?: ?number, + claimValues?: any, + embedded?: boolean, + clearPosition?: (string) => void, + uri?: string, + doAnalyticsView?: (string, number) => any, + claimRewards?: () => void, + playerServerRef?: any, }) => { // Override the player's control text. We override to: // 1. Add keyboard shortcut to the tool-tip. @@ -93,6 +110,10 @@ const VideoJsEvents = ({ function onInitialPlay() { const player = playerRef.current; + + const bigPlayButton = document.querySelector('.vjs-big-play-button'); + if (bigPlayButton) bigPlayButton.style.setProperty('display', 'none'); + if (player && (player.muted() || player.volume() === 0)) { // The css starts as "hidden". We make it visible here without // re-rendering the whole thing. @@ -223,6 +244,19 @@ const VideoJsEvents = ({ player.on('volumechange', resolveCtrlText); player.on('volumechange', onVolumeChange); player.on('error', onError); + // custom tracking plugin, event used for watchman data, and marking view/getting rewards + // hide forcing control bar show + player.on('canplaythrough', function () { + setTimeout(function () { + // $FlowFixMe + const vjsControlBar = document.querySelector('.vjs-control-bar'); + if (vjsControlBar) vjsControlBar.style.removeProperty('opacity'); + }, 1000 * 3); // wait 3 seconds to hit control bar + }); + player.on('playing', function () { + // $FlowFixMe + document.querySelector('.vjs-big-play-button').style.setProperty('display', 'none', 'important'); + }); // player.on('ended', onEnded); } diff --git a/ui/component/wallpaper/index.js b/ui/component/wallpaper/index.js new file mode 100644 index 000000000..61c65f06d --- /dev/null +++ b/ui/component/wallpaper/index.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +// import { makeSelectCoverForUri, makeSelectAvatarForUri } from 'redux/selectors/claims'; +import Wallpaper from './view'; + +/* +const select = (state, props) => { + if (props.uri && (props.uri.indexOf('@') !== -1 || props.uri.indexOf('#') !== -1)) { + return { + cover: makeSelectCoverForUri(props.uri)(state), + avatar: makeSelectAvatarForUri(props.uri)(state), + }; + } else return {}; +}; +*/ + +export default connect()(Wallpaper); diff --git a/ui/component/wallpaper/view.jsx b/ui/component/wallpaper/view.jsx new file mode 100644 index 000000000..3b21a735f --- /dev/null +++ b/ui/component/wallpaper/view.jsx @@ -0,0 +1,244 @@ +// @flow +import React from 'react'; +// import { resetColors } from 'util/theme'; +// $FlowFixMe cannot resolve ... +import freeezepeach from 'static/img/freespch.png'; +type Props = { + // uri: ?string, + // cover: ?string, + // avatar: ?string, + reset: ?boolean, +}; + +const Wallpaper = (props: Props) => { + // const { cover, avatar } = props; + + /* + if (avatar) { + toDataUrl(avatar, function (image) { + if (image) { + let threshold = 155; + getAverageRGB(image, function (rgb) { + let brightness = Math.round((parseInt(rgb.r) * 299 + parseInt(rgb.g) * 587 + parseInt(rgb.b) * 114) / 1000); + if (colorCompare(rgb) < 15) { + rgb = colorMixer(rgb, brightness > threshold ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 }, 0.7); + } + + // Tune link color in light theme + if (document.documentElement !== null) { + if (getComputedStyle(document.documentElement).getPropertyValue('--color-text') === ' #000000') { + let link = colorMixer( + rgb, + brightness > threshold ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 }, + 0.8 + ); + document.documentElement !== null && + document.documentElement.style.setProperty( + '--color-link', + 'rgba(' + link.r + ',' + link.g + ',' + link.b + ', 1)' + ); + } else { + document.documentElement !== null && + document.documentElement.style.setProperty('--color-link', 'var(--color-primary)'); + } + } + document.documentElement !== null && + document.documentElement.style.setProperty('--color-primary-dynamic', rgb.r + ',' + rgb.g + ',' + rgb.b); + document.documentElement !== null && + document.documentElement.style.setProperty( + '--color-primary-contrast', + brightness > 155 ? 'black' : 'white' + ); + + if (document.documentElement !== null) { + threshold = + getComputedStyle(document.documentElement).getPropertyValue('--color-text') === ' #000000' ? 70 : 155; + } + let rgbs = colorMixer(rgb, brightness > threshold ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 }, 0.6); + let brightnesss = Math.round( + (parseInt(rgbs.r) * 299 + parseInt(rgbs.g) * 587 + parseInt(rgbs.b) * 114) / 1000 + ); + document.documentElement !== null && + document.documentElement.style.setProperty( + '--color-secondary-dynamic', + rgbs.r + ',' + rgbs.g + ',' + rgbs.b + ); + document.documentElement !== null && + document.documentElement.style.setProperty( + '--color-secondary-contrast', + brightnesss > 155 ? 'black' : 'white' + ); + }); + } + }); + } else { + resetColors(true); + } + + function toDataUrl(url, callback) { + const xhr = new XMLHttpRequest(); + xhr.onload = function () { + const reader = new FileReader(); + reader.onloadend = function () { + const image = new Image(); + image.src = reader.result.toString(); + image.onload = () => callback(image); + }; + reader.readAsDataURL(xhr.response); + }; + xhr.open('GET', url); + xhr.responseType = 'blob'; + xhr.send(); + } + + function getAverageRGB(img, callback) { + const blockSize = 5; + const defaultRGB = { r: 0, g: 0, b: 0 }; + const canvas = document.createElement('canvas'); + const context = canvas.getContext && canvas.getContext('2d'); + const rgb = { r: 0, g: 0, b: 0 }; + const rgb_gray = { r: 0, g: 0, b: 0 }; + const blackwhite = { r: 0, g: 0, b: 0 }; + let count = 0; + let count_gray = 0; + let count_off = 0; + let i = -4; + let data; + let length; + + if (!context) { + return defaultRGB; + } + + const height = (canvas.height = img.naturalHeight || img.offsetHeight || img.height); + const width = (canvas.width = img.naturalWidth || img.offsetWidth || img.width); + + context.drawImage(img, 0, 0); + + try { + data = context.getImageData(0, 0, width, height); + } catch (e) { + return defaultRGB; + } + + length = data.data.length; + + while ((i += blockSize * 4) < length) { + if (shadeCheck(data.data, i, 75)) { + ++count; + rgb.r += data.data[i]; + rgb.g += data.data[i + 1]; + rgb.b += data.data[i + 2]; + } else if (shadeCheck(data.data, i, 25)) { + ++count_gray; + rgb_gray.r += data.data[i]; + rgb_gray.g += data.data[i + 1]; + rgb_gray.b += data.data[i + 2]; + } else { + ++count_off; + blackwhite.r += data.data[i]; + blackwhite.g += data.data[i + 1]; + blackwhite.b += data.data[i + 2]; + } + } + + if ((100 / (count + count_gray + count_off)) * count > 3) { + rgb.r = ~~(rgb.r / count); + rgb.g = ~~(rgb.g / count); + rgb.b = ~~(rgb.b / count); + } else if (count_gray > count_off) { + rgb.r = ~~(rgb_gray.r / count_gray); + rgb.g = ~~(rgb_gray.g / count_gray); + rgb.b = ~~(rgb_gray.b / count_gray); + } else { + let shade = 255; + if (document.documentElement !== null) { + shade = getComputedStyle(document.documentElement).getPropertyValue('--color-text') === ' #000000' ? 0 : 255; + } + rgb.r = shade; + rgb.g = shade; + rgb.b = shade; + } + + callback(rgb); + } + + function shadeCheck(data, i, threshold) { + let white = 0; + if (data[i] > 255 - threshold) white = white + 1; + if (data[i + 1] > 255 - threshold) white = white + 1; + if (data[i + 2] > 255 - threshold) white = white + 1; + let black = 0; + if (data[i] < threshold) black = black + 1; + if (data[i + 1] < threshold) black = black + 1; + if (data[i + 2] < threshold) black = black + 1; + + if (white > 2 || black > 2) return false; + else return true; + } + + function colorChannelMixer(a, b, mix) { + let channelA = a * mix; + let channelB = b * (1 - mix); + return parseInt(channelA + channelB); + } + + function colorMixer(rgbA, rgbB, mix) { + let r = colorChannelMixer(rgbA.r, rgbB.r, mix); + let g = colorChannelMixer(rgbA.g, rgbB.g, mix); + let b = colorChannelMixer(rgbA.b, rgbB.b, mix); + return { r: r, g: g, b: b }; + } + + function colorCompare(rgb) { + let bg = 0; + if (document.documentElement !== null) { + bg = getComputedStyle(document.documentElement).getPropertyValue('--color-text') === ' #000000' ? 221 : 32; + } + let r = Math.abs(rgb.r - bg); + let g = Math.abs(rgb.g - bg); + let b = Math.abs(rgb.b - bg); + + r = r / 255; + g = g / 255; + b = b / 255; + + return ((r + g + b) / 3) * 100; + } + */ + + /* + if (cover) { + return ( + cover && ( + <> +
    +
    + + ) + ); + } else { */ + /* +
    + */ + return ( + <> +
    +
    + + ); + // } +}; + +export default Wallpaper; diff --git a/ui/component/wunderbarSuggestions/view.jsx b/ui/component/wunderbarSuggestions/view.jsx index cdb0bbd61..c897e1347 100644 --- a/ui/component/wunderbarSuggestions/view.jsx +++ b/ui/component/wunderbarSuggestions/view.jsx @@ -8,7 +8,6 @@ import classnames from 'classnames'; import Icon from 'component/common/icon'; import { isURIValid, normalizeURI, parseURI } from 'util/lbryURI'; import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox'; -// import '@reach/combobox/styles.css'; --> 'scss/third-party.scss' import useLighthouse from 'effects/use-lighthouse'; import { Form } from 'component/common/form'; import Button from 'component/button'; @@ -279,11 +278,9 @@ export default function WunderBarSuggestions(props: Props) { return () => { window.removeEventListener('keydown', handleKeyDown); - // @if TARGET='app' if (inputRef.current) { inputRef.current.removeEventListener('dblclick', handleDoubleClick); } - // @endif }; }, [inputRef]); @@ -309,7 +306,7 @@ export default function WunderBarSuggestions(props: Props) { className={classnames('wunderbar__wrapper', { 'wunderbar__wrapper--mobile': isMobile })} onSubmit={() => handleSelect(term)} > - + { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/ui/effects/use-persisted-state.js b/ui/effects/use-persisted-state.js index c45838688..6272dc3d3 100644 --- a/ui/effects/use-persisted-state.js +++ b/ui/effects/use-persisted-state.js @@ -7,7 +7,7 @@ function getSetAllValues(key, setValue) { // If no key just return the normal setValue function return setValue; } - return value => listeners[key].forEach(fn => fn(value)); + return (value) => listeners[key].forEach((fn) => fn(value)); } export default function usePersistedState(key, firstTimeDefault) { @@ -58,7 +58,7 @@ export default function usePersistedState(key, firstTimeDefault) { return () => { if (key) { // remove hook on unmount - listeners[key] = listeners[key].filter(listener => listener !== setValue); + listeners[key] = listeners[key].filter((listener) => listener !== setValue); } }; }, [key, value, localStorageAvailable]); diff --git a/ui/effects/use-screensize.js b/ui/effects/use-screensize.js index f0b315f5c..b7404c3ee 100644 --- a/ui/effects/use-screensize.js +++ b/ui/effects/use-screensize.js @@ -34,3 +34,7 @@ export function useIsLargeScreen() { const windowSize = useWindowSize(); return windowSize > 1600; } + +export function isTouch() { + return 'ontouchstart' in window || 'onmsgesturechange' in window; +} diff --git a/ui/page/channel/view.jsx b/ui/page/channel/view.jsx index efba3b151..d263e948e 100644 --- a/ui/page/channel/view.jsx +++ b/ui/page/channel/view.jsx @@ -19,7 +19,6 @@ import ChannelForm from 'component/channelForm'; import classnames from 'classnames'; import HelpLink from 'component/common/help-link'; import ClaimSupportButton from 'component/claimSupportButton'; -import ChannelStakedIndicator from 'component/channelStakedIndicator'; import ClaimMenuList from 'component/claimMenuList'; import OptimizedImage from 'component/optimizedImage'; import Yrbl from 'component/yrbl'; @@ -208,7 +207,7 @@ function ChannelPage(props: Props) { } return ( - +
    {isMyYouTubeChannel && ( @@ -228,12 +227,11 @@ function ChannelPage(props: Props) { {cover && } {cover && }
    - +

    {title || (channelName && '@' + channelName)} -

    diff --git a/ui/page/channels/view.jsx b/ui/page/channels/view.jsx index 785e6e1b9..4d850d810 100644 --- a/ui/page/channels/view.jsx +++ b/ui/page/channels/view.jsx @@ -45,7 +45,7 @@ export default function ChannelsPage(props: Props) { }, [setRewardData]); return ( - +
    {hasYoutubeChannels && } diff --git a/ui/page/channelsFollowingDiscover/view.jsx b/ui/page/channelsFollowingDiscover/view.jsx index 86f04770f..cd4c9ba6d 100644 --- a/ui/page/channelsFollowingDiscover/view.jsx +++ b/ui/page/channelsFollowingDiscover/view.jsx @@ -98,7 +98,7 @@ function ChannelsFollowingDiscover(props: Props) { }); return ( - + {rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (

    diff --git a/ui/page/collection/view.jsx b/ui/page/collection/view.jsx index 0e33f1e68..83983a27e 100644 --- a/ui/page/collection/view.jsx +++ b/ui/page/collection/view.jsx @@ -269,7 +269,8 @@ export default function CollectionPage(props: Props) { if (urlsReady) { return ( - + + {editing}
    {info} diff --git a/ui/page/elements/index.js b/ui/page/elements/index.js new file mode 100644 index 000000000..2313998ba --- /dev/null +++ b/ui/page/elements/index.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { selectTotalBalance } from 'redux/selectors/wallet'; +import { doOpenModal } from 'redux/actions/app'; +import Elements from './view'; + +const select = (state) => ({ + totalBalance: selectTotalBalance(state), +}); + +export default connect(select, { + doOpenModal, +})(Elements); diff --git a/ui/page/elements/view.jsx b/ui/page/elements/view.jsx new file mode 100644 index 000000000..7fa6f98ed --- /dev/null +++ b/ui/page/elements/view.jsx @@ -0,0 +1,42 @@ +// @flow +import React from 'react'; +import { useHistory } from 'react-router'; +import TxoList from 'component/txoList'; +import Page from 'component/page'; +import WalletBalance from 'component/walletBalance'; +import ClaimList from 'component/claimList'; + +type Props = { + history: { action: string, push: (string) => void, replace: (string) => void }, + location: { search: string, pathname: string }, +}; + +const Elements = (props: Props) => { + const { + location: { search }, + } = useHistory(); + + return ( + +
    + + + + + + + + + +
    +
    + ); +}; + +export default Elements; diff --git a/ui/page/fileListPublished/view.jsx b/ui/page/fileListPublished/view.jsx index 02aa73dbc..ab3c2b158 100644 --- a/ui/page/fileListPublished/view.jsx +++ b/ui/page/fileListPublished/view.jsx @@ -166,12 +166,19 @@ function FileListPublished(props: Props) { headerAltControls={

    {__('Date')}{<>{__('Type')}}{__('Date')}{<>{__('Type')}} {__('Details')} {__('Transaction')}{__('Transaction')}