Tile Grid Revamp (#1502)
* Save * Save * Add pulse * Adjust footer ad * Adjust tile ad * Adjust tile ad hover * Fix premium badge alignment in tile grid * Adjust livestream icon * Adjust livestream icon * Save scheduled livestreasm & tile ad * Fix scheduled callback * Fix playlist icon size on file page * Fix grid distortion in 3 & 4 column layout * - * Fix grid on category & channel page * Fix Premium Plus Grid * Add custom tile for adblockers * Reset env * Remove collapsed tiles * Remove setLoaded on scheduled livestreams page * - * Make isHidden optional * Remove px * Review adjustments * Inject Premium+ ads * Fix injection * Fix injection when using the last tile * Fix injection when using the last tile * Enable stripe dev * Create PremiumPlusTile component and add list view design * Create PremiumPlusTile component and add list view design * Adjust ads in list view * Remove setState from render loop * Clean code * Fix livestream margin in list view * Rewrite & tune some logic - Homepage & Channel page * Clean details... * Clean details... * Requested review changes Signed-off-by: Raphael Wickihalder <raphael.wickihalder@odysee.com> * Requested review changes Signed-off-by: Raphael Wickihalder <raphael.wickihalder@odysee.com>
This commit is contained in:
parent
96e704e5d9
commit
ffdb5abf63
24 changed files with 439 additions and 58 deletions
1
flow-typed/gui.js
vendored
1
flow-typed/gui.js
vendored
|
@ -2,5 +2,4 @@
|
||||||
declare type ListInjectedItem = {
|
declare type ListInjectedItem = {
|
||||||
node: Node | (index: number, lastVisibleIndex: ?number, pageSize: ?number) => Node,
|
node: Node | (index: number, lastVisibleIndex: ?number, pageSize: ?number) => Node,
|
||||||
index?: number,
|
index?: number,
|
||||||
replace?: boolean
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,11 +7,13 @@ import {
|
||||||
makeSelectTotalPagesInChannelSearch,
|
makeSelectTotalPagesInChannelSearch,
|
||||||
selectClaimForUri,
|
selectClaimForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
|
import { selectOdyseeMembershipIsPremiumPlus } from 'redux/selectors/user';
|
||||||
import { doResolveUris } from 'redux/actions/claims';
|
import { doResolveUris } from 'redux/actions/claims';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
|
import { selectAdBlockerFound } from 'redux/selectors/app';
|
||||||
import { doFetchChannelLiveStatus } from 'redux/actions/livestream';
|
import { doFetchChannelLiveStatus } from 'redux/actions/livestream';
|
||||||
import { selectActiveLivestreamForChannel, selectActiveLivestreamInitialized } from 'redux/selectors/livestream';
|
import { selectActiveLivestreamForChannel, selectActiveLivestreamInitialized } from 'redux/selectors/livestream';
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { getChannelIdFromClaim } from 'util/claim';
|
||||||
|
@ -35,6 +37,8 @@ const select = (state, props) => {
|
||||||
tileLayout: selectClientSetting(state, SETTINGS.TILE_LAYOUT),
|
tileLayout: selectClientSetting(state, SETTINGS.TILE_LAYOUT),
|
||||||
activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
|
activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
|
||||||
activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
|
activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
|
||||||
|
adBlockerFound: selectAdBlockerFound(state),
|
||||||
|
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import ScheduledStreams from 'component/scheduledStreams';
|
||||||
import { SearchResults } from './internal/searchResults';
|
import { SearchResults } from './internal/searchResults';
|
||||||
import useFetchLiveStatus from 'effects/use-fetch-live';
|
import useFetchLiveStatus from 'effects/use-fetch-live';
|
||||||
import { useIsLargeScreen } from 'effects/use-screensize';
|
import { useIsLargeScreen } from 'effects/use-screensize';
|
||||||
|
import PremiumPlusTile from 'component/premiumPlusTile';
|
||||||
|
|
||||||
const TYPES_TO_ALLOW_FILTER = ['stream', 'repost'];
|
const TYPES_TO_ALLOW_FILTER = ['stream', 'repost'];
|
||||||
|
|
||||||
|
@ -41,6 +42,8 @@ type Props = {
|
||||||
doFetchChannelLiveStatus: (string) => void,
|
doFetchChannelLiveStatus: (string) => void,
|
||||||
activeLivestreamForChannel: any,
|
activeLivestreamForChannel: any,
|
||||||
activeLivestreamInitialized: boolean,
|
activeLivestreamInitialized: boolean,
|
||||||
|
adBlockerFound: ?boolean,
|
||||||
|
hasPremiumPlus: ?boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelContent(props: Props) {
|
function ChannelContent(props: Props) {
|
||||||
|
@ -62,6 +65,8 @@ function ChannelContent(props: Props) {
|
||||||
doFetchChannelLiveStatus,
|
doFetchChannelLiveStatus,
|
||||||
activeLivestreamForChannel,
|
activeLivestreamForChannel,
|
||||||
activeLivestreamInitialized,
|
activeLivestreamInitialized,
|
||||||
|
adBlockerFound,
|
||||||
|
hasPremiumPlus,
|
||||||
} = props;
|
} = props;
|
||||||
// const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
// const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
|
|
||||||
|
@ -159,7 +164,15 @@ function ChannelContent(props: Props) {
|
||||||
defaultOrderBy={CS.ORDER_BY_NEW}
|
defaultOrderBy={CS.ORDER_BY_NEW}
|
||||||
pageSize={dynamicPageSize}
|
pageSize={dynamicPageSize}
|
||||||
infiniteScroll={defaultInfiniteScroll}
|
infiniteScroll={defaultInfiniteScroll}
|
||||||
injectedItem={{ node: <Ads type="video" tileLayout={tileLayout} small /> }}
|
injectedItem={
|
||||||
|
!hasPremiumPlus && {
|
||||||
|
node: adBlockerFound ? (
|
||||||
|
<PremiumPlusTile tileLayout={tileLayout} />
|
||||||
|
) : (
|
||||||
|
<Ads small type="video" tileLayout />
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
meta={
|
meta={
|
||||||
showFilters && (
|
showFilters && (
|
||||||
<Form onSubmit={() => {}} className="wunderbar--inline">
|
<Form onSubmit={() => {}} className="wunderbar--inline">
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default function CollectionAddButton(props: Props) {
|
||||||
button={!fileAction ? 'alt' : undefined}
|
button={!fileAction ? 'alt' : undefined}
|
||||||
className={classnames({ 'button--file-action': fileAction })}
|
className={classnames({ 'button--file-action': fileAction })}
|
||||||
icon={fileAction ? (!isSaved ? ICONS.ADD : ICONS.STACK) : ICONS.LIBRARY}
|
icon={fileAction ? (!isSaved ? ICONS.ADD : ICONS.STACK) : ICONS.LIBRARY}
|
||||||
iconSize={fileAction ? 22 : undefined}
|
iconSize={fileAction ? 16 : undefined}
|
||||||
label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')}
|
label={uri ? (!isSaved ? __('Save') : __('Saved')) : __('New List')}
|
||||||
requiresAuth
|
requiresAuth
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { MAIN_CLASS } from 'constants/classnames';
|
import { MAIN_CLASS } from 'constants/classnames';
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
|
@ -61,6 +61,7 @@ type Props = {
|
||||||
unavailableUris?: Array<string>,
|
unavailableUris?: Array<string>,
|
||||||
showMemberBadge?: boolean,
|
showMemberBadge?: boolean,
|
||||||
inWatchHistory?: boolean,
|
inWatchHistory?: boolean,
|
||||||
|
onHidden: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ClaimList(props: Props) {
|
export default function ClaimList(props: Props) {
|
||||||
|
@ -102,9 +103,11 @@ export default function ClaimList(props: Props) {
|
||||||
unavailableUris,
|
unavailableUris,
|
||||||
showMemberBadge,
|
showMemberBadge,
|
||||||
inWatchHistory,
|
inWatchHistory,
|
||||||
|
onHidden,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||||
|
const [uriBuffer, setUriBuffer] = useState([]);
|
||||||
|
|
||||||
// Resolve the index for injectedItem, if provided; else injectedIndex will be 'undefined'.
|
// Resolve the index for injectedItem, if provided; else injectedIndex will be 'undefined'.
|
||||||
const listRef = React.useRef();
|
const listRef = React.useRef();
|
||||||
|
@ -154,12 +157,6 @@ export default function ClaimList(props: Props) {
|
||||||
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
|
return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// @if process.env.NODE_ENV!='production'
|
|
||||||
if (injectedItem && injectedItem.replace) {
|
|
||||||
throw new Error('claimList: "injectedItem.replace" is not implemented yet');
|
|
||||||
}
|
|
||||||
// @endif
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = debounce((e) => {
|
const handleScroll = debounce((e) => {
|
||||||
if (page && pageSize && onScrollBottom) {
|
if (page && pageSize && onScrollBottom) {
|
||||||
|
@ -208,6 +205,19 @@ export default function ClaimList(props: Props) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
tileUris.forEach((uri, index) => {
|
||||||
|
if (uri) {
|
||||||
|
const inj = getInjectedItem(index);
|
||||||
|
if (inj) {
|
||||||
|
if (uriBuffer.indexOf(index) === -1) {
|
||||||
|
setUriBuffer([index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [tileUris, injectedItem, lastVisibleIndex, pageSize]);
|
||||||
|
|
||||||
const getInjectedItem = (index) => {
|
const getInjectedItem = (index) => {
|
||||||
if (injectedItem && injectedItem.node) {
|
if (injectedItem && injectedItem.node) {
|
||||||
if (typeof injectedItem.node === 'function') {
|
if (typeof injectedItem.node === 'function') {
|
||||||
|
@ -228,21 +238,30 @@ export default function ClaimList(props: Props) {
|
||||||
<>
|
<>
|
||||||
<section ref={listRef} className={classnames('claim-grid', { 'swipe-list': swipeLayout })}>
|
<section ref={listRef} className={classnames('claim-grid', { 'swipe-list': swipeLayout })}>
|
||||||
{urisLength > 0 &&
|
{urisLength > 0 &&
|
||||||
tileUris.map((uri, index) => (
|
tileUris.map((uri, index) => {
|
||||||
<React.Fragment key={uri}>
|
if (uri) {
|
||||||
{getInjectedItem(index)}
|
const inj = getInjectedItem(index);
|
||||||
<ClaimPreviewTile
|
return (
|
||||||
uri={uri}
|
<React.Fragment key={uri}>
|
||||||
showHiddenByUser={showHiddenByUser}
|
{inj && inj}
|
||||||
showUnresolvedClaims={showUnresolvedClaims}
|
{(index < tileUris.length - uriBuffer.length ||
|
||||||
properties={renderProperties}
|
(pageSize && index < pageSize - uriBuffer.length)) && (
|
||||||
collectionId={collectionId}
|
<ClaimPreviewTile
|
||||||
fypId={fypId}
|
uri={uri}
|
||||||
showNoSourceClaims={showNoSourceClaims}
|
showHiddenByUser={showHiddenByUser}
|
||||||
swipeLayout={swipeLayout}
|
showUnresolvedClaims={showUnresolvedClaims}
|
||||||
/>
|
properties={renderProperties}
|
||||||
</React.Fragment>
|
collectionId={collectionId}
|
||||||
))}
|
fypId={fypId}
|
||||||
|
showNoSourceClaims={showNoSourceClaims}
|
||||||
|
swipeLayout={swipeLayout}
|
||||||
|
onHidden={onHidden}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
{!timedOut && urisLength === 0 && !loading && !noEmpty && (
|
{!timedOut && urisLength === 0 && !loading && !noEmpty && (
|
||||||
<div className="empty main--empty">{empty || noResultMsg}</div>
|
<div className="empty main--empty">{empty || noResultMsg}</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -732,7 +732,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
{loading && useSkeletonScreen && (
|
{loading && useSkeletonScreen && (
|
||||||
<div className="claim-grid">
|
<div className="claim-grid">
|
||||||
{new Array(dynamicPageSize).fill(1).map((x, i) => (
|
{new Array(dynamicPageSize).fill(1).map((x, i) => (
|
||||||
<ClaimPreviewTile key={i} placeholder="loading" />
|
<ClaimPreviewTile key={i} placeholder="loading" pulse />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -55,6 +55,8 @@ type Props = {
|
||||||
isLivestreamActive: boolean,
|
isLivestreamActive: boolean,
|
||||||
livestreamViewerCount: ?number,
|
livestreamViewerCount: ?number,
|
||||||
swipeLayout: boolean,
|
swipeLayout: boolean,
|
||||||
|
onHidden?: (string) => void,
|
||||||
|
pulse?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
// preview image cards used in related video functionality, channel overview page and homepage
|
// preview image cards used in related video functionality, channel overview page and homepage
|
||||||
|
@ -86,6 +88,8 @@ function ClaimPreviewTile(props: Props) {
|
||||||
mediaDuration,
|
mediaDuration,
|
||||||
viewCount,
|
viewCount,
|
||||||
swipeLayout = false,
|
swipeLayout = false,
|
||||||
|
onHidden,
|
||||||
|
pulse,
|
||||||
} = props;
|
} = props;
|
||||||
const isRepost = claim && claim.repost_channel_url;
|
const isRepost = claim && claim.repost_channel_url;
|
||||||
const isCollection = claim && claim.value_type === 'collection';
|
const isCollection = claim && claim.value_type === 'collection';
|
||||||
|
@ -158,6 +162,7 @@ function ClaimPreviewTile(props: Props) {
|
||||||
banState.filtered ||
|
banState.filtered ||
|
||||||
(!showHiddenByUser && (banState.muted || banState.blocked)) ||
|
(!showHiddenByUser && (banState.muted || banState.blocked)) ||
|
||||||
(isAbandoned && !showUnresolvedClaims));
|
(isAbandoned && !showUnresolvedClaims));
|
||||||
|
if (onHidden && shouldHide) onHidden(props.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldHide || (isLivestream && !showNoSourceClaims)) {
|
if (shouldHide || (isLivestream && !showNoSourceClaims)) {
|
||||||
|
@ -173,6 +178,7 @@ function ClaimPreviewTile(props: Props) {
|
||||||
<li
|
<li
|
||||||
className={classnames('placeholder claim-preview--tile', {
|
className={classnames('placeholder claim-preview--tile', {
|
||||||
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
|
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
|
||||||
|
pulse: pulse,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="media__thumb">
|
<div className="media__thumb">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ClaimPreviewTile from 'component/claimPreviewTile';
|
import ClaimPreviewTile from 'component/claimPreviewTile';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
@ -52,6 +52,7 @@ type Props = {
|
||||||
hasSource?: boolean,
|
hasSource?: boolean,
|
||||||
hasNoSource?: boolean,
|
hasNoSource?: boolean,
|
||||||
forceShowReposts?: boolean, // overrides SETTINGS.HIDE_REPOSTS
|
forceShowReposts?: boolean, // overrides SETTINGS.HIDE_REPOSTS
|
||||||
|
loading: boolean,
|
||||||
// --- select ---
|
// --- select ---
|
||||||
location: { search: string },
|
location: { search: string },
|
||||||
claimSearchResults: Array<string>,
|
claimSearchResults: Array<string>,
|
||||||
|
@ -90,6 +91,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
doFetchUserMemberships,
|
doFetchUserMemberships,
|
||||||
doResolveClaimIds,
|
doResolveClaimIds,
|
||||||
doResolveUris,
|
doResolveUris,
|
||||||
|
loading,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const listRef = React.useRef();
|
const listRef = React.useRef();
|
||||||
|
@ -100,6 +102,7 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
const claimSearchUris = claimSearchResults || [];
|
const claimSearchUris = claimSearchResults || [];
|
||||||
const isUnfetchedClaimSearch = claimSearchResults === undefined;
|
const isUnfetchedClaimSearch = claimSearchResults === undefined;
|
||||||
const resolvedPinUris = useResolvePins({ pins, claimsById, doResolveClaimIds, doResolveUris });
|
const resolvedPinUris = useResolvePins({ pins, claimsById, doResolveClaimIds, doResolveUris });
|
||||||
|
const [uriBuffer, setUriBuffer] = useState([]);
|
||||||
|
|
||||||
const timedOut = claimSearchResults === null;
|
const timedOut = claimSearchResults === null;
|
||||||
const shouldPerformSearch = !fetchingClaimSearch && !timedOut && claimSearchUris.length === 0;
|
const shouldPerformSearch = !fetchingClaimSearch && !timedOut && claimSearchUris.length === 0;
|
||||||
|
@ -172,6 +175,19 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
}
|
}
|
||||||
}, [doClaimSearch, shouldPerformSearch, optionsStringified]);
|
}, [doClaimSearch, shouldPerformSearch, optionsStringified]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
finalUris.forEach((uri, index) => {
|
||||||
|
if (uri) {
|
||||||
|
const inj = getInjectedItem(index);
|
||||||
|
if (inj) {
|
||||||
|
if (uriBuffer.indexOf(index) === -1) {
|
||||||
|
setUriBuffer([index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [finalUris, injectedItem, lastVisibleIndex, pageSize]);
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -200,14 +216,14 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul ref={listRef} className="claim-grid">
|
<ul ref={listRef} className="claim-grid">
|
||||||
{finalUris && finalUris.length
|
{!loading && finalUris && finalUris.length
|
||||||
? finalUris.map((uri, i) => {
|
? finalUris.map((uri, i) => {
|
||||||
if (uri) {
|
if (uri) {
|
||||||
const inj = getInjectedItem(i);
|
const inj = getInjectedItem(i);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={uri}>
|
<React.Fragment key={uri}>
|
||||||
{inj && inj}
|
{inj && inj}
|
||||||
{(!inj || !injectedItem || !injectedItem.replace) && (
|
{(i < finalUris.length - uriBuffer.length || i < pageSize - uriBuffer.length) && (
|
||||||
<ClaimPreviewTile
|
<ClaimPreviewTile
|
||||||
showNoSourceClaims={hasNoSource || showNoSourceClaims}
|
showNoSourceClaims={hasNoSource || showNoSourceClaims}
|
||||||
uri={uri}
|
uri={uri}
|
||||||
|
@ -217,13 +233,15 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <ClaimPreviewTile showNoSourceClaims={hasNoSource || showNoSourceClaims} key={i} placeholder />;
|
return (
|
||||||
|
<ClaimPreviewTile showNoSourceClaims={hasNoSource || showNoSourceClaims} key={i} placeholder pulse />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
: new Array(pageSize)
|
: new Array(pageSize)
|
||||||
.fill(1)
|
.fill(1)
|
||||||
.map((x, i) => (
|
.map((x, i) => (
|
||||||
<ClaimPreviewTile showNoSourceClaims={hasNoSource || showNoSourceClaims} key={i} placeholder />
|
<ClaimPreviewTile showNoSourceClaims={hasNoSource || showNoSourceClaims} key={i} placeholder pulse />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
@ -236,6 +254,7 @@ export default React.memo<Props>(ClaimTilesDiscover, areEqual);
|
||||||
|
|
||||||
function trace(key, value) {
|
function trace(key, value) {
|
||||||
// @if process.env.DEBUG_TILE_RENDER
|
// @if process.env.DEBUG_TILE_RENDER
|
||||||
|
|
||||||
// $FlowFixMe "cannot coerce certain types".
|
// $FlowFixMe "cannot coerce certain types".
|
||||||
console.log(`[claimTilesDiscover] ${key}: ${value}`); // eslint-disable-line no-console
|
console.log(`[claimTilesDiscover] ${key}: ${value}`); // eslint-disable-line no-console
|
||||||
// @endif
|
// @endif
|
||||||
|
|
6
ui/component/premiumPlusTile/index.js
Normal file
6
ui/component/premiumPlusTile/index.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PremiumPlusTile from './view';
|
||||||
|
|
||||||
|
const select = (state) => ({});
|
||||||
|
|
||||||
|
export default connect(select, {})(PremiumPlusTile);
|
68
ui/component/premiumPlusTile/view.jsx
Normal file
68
ui/component/premiumPlusTile/view.jsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tileLayout?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
const PremiumPlusTile = (props: Props) => {
|
||||||
|
const { tileLayout } = props;
|
||||||
|
|
||||||
|
const title = __('No ads and access to exclusive features!');
|
||||||
|
const channel = __('Get Odysee Premium+');
|
||||||
|
const time = __('Now');
|
||||||
|
|
||||||
|
return tileLayout ? (
|
||||||
|
<li className="card claim-preview--tile claim-preview--premium-plus">
|
||||||
|
<a href={`/$/${PAGES.ODYSEE_MEMBERSHIP}`}>
|
||||||
|
<div className="media__thumb" />
|
||||||
|
<div className="claim-tile__header">
|
||||||
|
<h2 className="claim-tile__title">{title}</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="claim-tile__info">
|
||||||
|
<Icon icon={ICONS.UPGRADE} />
|
||||||
|
<div className="claim-tile__about">
|
||||||
|
<div className="channel-name">{channel}</div>
|
||||||
|
<div className="claim-tile__about--counts">
|
||||||
|
<span className="date_time">{time}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li className="claim-preview__wrapper claim-preview--premium-plus">
|
||||||
|
<a href={`/$/${PAGES.ODYSEE_MEMBERSHIP}`}>
|
||||||
|
<div className="claim-preview">
|
||||||
|
<div className="media__thumb" />
|
||||||
|
<div className="claim-preview__text">
|
||||||
|
<div className="claim-preview-metadata">
|
||||||
|
<div className="claim-preview-info">
|
||||||
|
<div className="claim-preview__title">{title}</div>
|
||||||
|
</div>
|
||||||
|
<div className="claim-tile__info">
|
||||||
|
<div className="claim-preview__channel-staked">
|
||||||
|
<Icon icon={ICONS.UPGRADE} />
|
||||||
|
</div>
|
||||||
|
<div className="media__subtitle">
|
||||||
|
<div className="button__content">
|
||||||
|
<span className="channel-name">{channel}</span>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<span sclassName="view_count">{time}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PremiumPlusTile;
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import ClaimPreviewTile from 'component/claimPreviewTile';
|
import ClaimPreviewTile from 'component/claimPreviewTile';
|
||||||
|
@ -45,6 +45,22 @@ export default function RecommendedPersonal(props: Props) {
|
||||||
const count = personalRecommendations.uris.length;
|
const count = personalRecommendations.uris.length;
|
||||||
const countCollapsed = getSuitablePageSizeForScreen(12, isLargeScreen, isMediumScreen);
|
const countCollapsed = getSuitablePageSizeForScreen(12, isLargeScreen, isMediumScreen);
|
||||||
const finalCount = view === VIEW.ALL_VISIBLE ? count : view === VIEW.COLLAPSED ? countCollapsed : 36;
|
const finalCount = view === VIEW.ALL_VISIBLE ? count : view === VIEW.COLLAPSED ? countCollapsed : 36;
|
||||||
|
const [hiddenArray, setHiddenArray] = useState([]);
|
||||||
|
|
||||||
|
function onClaimHidden(hiddenUri) {
|
||||||
|
let newArray = hiddenArray;
|
||||||
|
if (newArray.indexOf(hiddenUri) === -1) {
|
||||||
|
newArray.push(hiddenUri);
|
||||||
|
setHiddenArray(newArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getHidden() {
|
||||||
|
let hidden = hiddenArray.length;
|
||||||
|
for (let uri of hiddenArray) {
|
||||||
|
if (personalRecommendations.uris.indexOf(uri) > finalCount) hidden--;
|
||||||
|
}
|
||||||
|
return hidden;
|
||||||
|
}
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// Effects
|
// Effects
|
||||||
|
@ -147,8 +163,9 @@ export default function RecommendedPersonal(props: Props) {
|
||||||
|
|
||||||
<ClaimList
|
<ClaimList
|
||||||
tileLayout
|
tileLayout
|
||||||
uris={personalRecommendations.uris.slice(0, finalCount)}
|
uris={personalRecommendations.uris.slice(0, finalCount + getHidden())}
|
||||||
fypId={personalRecommendations.gid}
|
fypId={personalRecommendations.gid}
|
||||||
|
onHidden={onClaimHidden}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{view !== VIEW.ALL_VISIBLE && (
|
{view !== VIEW.ALL_VISIBLE && (
|
||||||
|
|
|
@ -23,6 +23,7 @@ type Props = {
|
||||||
doShowSnackBar: (string) => void,
|
doShowSnackBar: (string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* NEKO MARK */
|
||||||
const ScheduledStreams = (props: Props) => {
|
const ScheduledStreams = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
channelIds,
|
channelIds,
|
||||||
|
@ -34,6 +35,7 @@ const ScheduledStreams = (props: Props) => {
|
||||||
onLoad,
|
onLoad,
|
||||||
showHideSetting = true,
|
showHideSetting = true,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isMobileScreen = useIsMobile();
|
const isMobileScreen = useIsMobile();
|
||||||
const isLargeScreen = useIsLargeScreen();
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { selectActiveLivestreams } from 'redux/selectors/livestream';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||||
import { selectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
import { selectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
||||||
|
import { selectAdBlockerFound } from 'redux/selectors/app';
|
||||||
|
import { selectOdyseeMembershipIsPremiumPlus } from 'redux/selectors/user';
|
||||||
import DiscoverPage from './view';
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -23,6 +25,8 @@ const select = (state, props) => {
|
||||||
activeLivestreams: selectActiveLivestreams(state),
|
activeLivestreams: selectActiveLivestreams(state),
|
||||||
languageSetting: selectLanguage(state),
|
languageSetting: selectLanguage(state),
|
||||||
searchInLanguage: selectClientSetting(state, SETTINGS.SEARCH_IN_LANGUAGE),
|
searchInLanguage: selectClientSetting(state, SETTINGS.SEARCH_IN_LANGUAGE),
|
||||||
|
adBlockerFound: selectAdBlockerFound(state),
|
||||||
|
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import LivestreamSection from './livestreamSection';
|
import LivestreamSection from './livestreamSection';
|
||||||
|
import PremiumPlusTile from 'component/premiumPlusTile';
|
||||||
|
|
||||||
const CATEGORY_CONTENT_TYPES_FILTER = CS.CONTENT_TYPES.filter((x) => x !== CS.CLAIM_REPOST);
|
const CATEGORY_CONTENT_TYPES_FILTER = CS.CONTENT_TYPES.filter((x) => x !== CS.CLAIM_REPOST);
|
||||||
|
|
||||||
|
@ -35,6 +36,8 @@ type Props = {
|
||||||
tileLayout: boolean,
|
tileLayout: boolean,
|
||||||
activeLivestreams: ?LivestreamInfo,
|
activeLivestreams: ?LivestreamInfo,
|
||||||
doFetchActiveLivestreams: (orderBy: ?Array<string>, lang: ?Array<string>) => void,
|
doFetchActiveLivestreams: (orderBy: ?Array<string>, lang: ?Array<string>) => void,
|
||||||
|
adBlockerFound: ?boolean,
|
||||||
|
hasPremiumPlus: ?boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DiscoverPage(props: Props) {
|
function DiscoverPage(props: Props) {
|
||||||
|
@ -51,6 +54,8 @@ function DiscoverPage(props: Props) {
|
||||||
activeLivestreams,
|
activeLivestreams,
|
||||||
doFetchActiveLivestreams,
|
doFetchActiveLivestreams,
|
||||||
dynamicRouteProps,
|
dynamicRouteProps,
|
||||||
|
adBlockerFound,
|
||||||
|
hasPremiumPlus,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const buttonRef = useRef();
|
const buttonRef = useRef();
|
||||||
|
@ -221,7 +226,16 @@ function DiscoverPage(props: Props) {
|
||||||
tags={tags}
|
tags={tags}
|
||||||
hiddenNsfwMessage={<HiddenNsfw type="page" />}
|
hiddenNsfwMessage={<HiddenNsfw type="page" />}
|
||||||
repostedClaimId={repostedClaim ? repostedClaim.claim_id : null}
|
repostedClaimId={repostedClaim ? repostedClaim.claim_id : null}
|
||||||
injectedItem={!isWildWest && { node: <Ads small type="video" tileLayout={tileLayout} /> }}
|
injectedItem={
|
||||||
|
!isWildWest &&
|
||||||
|
!hasPremiumPlus && {
|
||||||
|
node: adBlockerFound ? (
|
||||||
|
<PremiumPlusTile tileLayout={tileLayout} />
|
||||||
|
) : (
|
||||||
|
<Ads small type="video" tileLayout />
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO: find a better way to determine discover / wild west vs other modes release times
|
// TODO: find a better way to determine discover / wild west vs other modes release times
|
||||||
// for now including && !tags so that
|
// for now including && !tags so that
|
||||||
releaseTime={releaseTime || undefined}
|
releaseTime={releaseTime || undefined}
|
||||||
|
|
|
@ -5,7 +5,12 @@ import { doFetchActiveLivestreams } from 'redux/actions/livestream';
|
||||||
import { selectAdBlockerFound } from 'redux/selectors/app';
|
import { selectAdBlockerFound } from 'redux/selectors/app';
|
||||||
import { selectActiveLivestreams, selectFetchingActiveLivestreams } from 'redux/selectors/livestream';
|
import { selectActiveLivestreams, selectFetchingActiveLivestreams } from 'redux/selectors/livestream';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import { selectHasOdyseeMembership, selectHomepageFetched, selectUserVerifiedEmail } from 'redux/selectors/user';
|
import {
|
||||||
|
selectOdyseeMembershipIsPremiumPlus,
|
||||||
|
selectHasOdyseeMembership,
|
||||||
|
selectHomepageFetched,
|
||||||
|
selectUserVerifiedEmail,
|
||||||
|
} from 'redux/selectors/user';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
import {
|
import {
|
||||||
selectShowMatureContent,
|
selectShowMatureContent,
|
||||||
|
@ -30,6 +35,7 @@ const select = (state) => ({
|
||||||
adBlockerFound: selectAdBlockerFound(state),
|
adBlockerFound: selectAdBlockerFound(state),
|
||||||
homepageOrder: selectClientSetting(state, SETTINGS.HOMEPAGE_ORDER),
|
homepageOrder: selectClientSetting(state, SETTINGS.HOMEPAGE_ORDER),
|
||||||
hasMembership: selectHasOdyseeMembership(state),
|
hasMembership: selectHasOdyseeMembership(state),
|
||||||
|
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { splitBySeparator } from 'util/lbryURI';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Ads from 'web/component/ads';
|
import Ads from 'web/component/ads';
|
||||||
import Meme from 'web/component/meme';
|
import Meme from 'web/component/meme';
|
||||||
|
import PremiumPlusTile from 'component/premiumPlusTile';
|
||||||
|
|
||||||
const FYP_SECTION: RowDataItem = {
|
const FYP_SECTION: RowDataItem = {
|
||||||
id: 'FYP',
|
id: 'FYP',
|
||||||
|
@ -46,6 +47,7 @@ type Props = {
|
||||||
homepageOrder: HomepageOrder,
|
homepageOrder: HomepageOrder,
|
||||||
doOpenModal: (id: string, ?{}) => void,
|
doOpenModal: (id: string, ?{}) => void,
|
||||||
hasMembership: ?boolean,
|
hasMembership: ?boolean,
|
||||||
|
hasPremiumPlus: ?boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function HomePage(props: Props) {
|
function HomePage(props: Props) {
|
||||||
|
@ -65,6 +67,7 @@ function HomePage(props: Props) {
|
||||||
homepageOrder,
|
homepageOrder,
|
||||||
doOpenModal,
|
doOpenModal,
|
||||||
hasMembership,
|
hasMembership,
|
||||||
|
hasPremiumPlus,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
|
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
|
||||||
|
@ -163,12 +166,14 @@ function HomePage(props: Props) {
|
||||||
prefixUris={getLivestreamUris(activeLivestreams, options.channelIds)}
|
prefixUris={getLivestreamUris(activeLivestreams, options.channelIds)}
|
||||||
pins={{ urls: pinUrls, claimIds: pinnedClaimIds }}
|
pins={{ urls: pinUrls, claimIds: pinnedClaimIds }}
|
||||||
injectedItem={
|
injectedItem={
|
||||||
index === 0 && {
|
index === 0 &&
|
||||||
node: <Ads small type="video" tileLayout />,
|
!hasPremiumPlus && {
|
||||||
replace: adBlockerFound === false && isLargeScreen,
|
node: adBlockerFound ? <PremiumPlusTile tileLayout /> : <Ads small type="video" tileLayout />,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
forceShowReposts={id !== 'FOLLOWING'}
|
forceShowReposts={id !== 'FOLLOWING'}
|
||||||
|
loading={id === 'FOLLOWING' ? fetchingActiveLivestreams : false}
|
||||||
|
adBlockerFound={adBlockerFound}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,17 @@
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
$minWidth: calc(var(--file-list-thumbnail-width) * 0.8);
|
$minWidth: calc(var(--file-list-thumbnail-width) * 0.8);
|
||||||
min-width: $minWidth;
|
min-width: $minWidth;
|
||||||
|
background-color: #283263;
|
||||||
|
background-image: url('https://odysee.com/public/img/astronaut_n_friends.png');
|
||||||
|
background-size: 100%;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 0.1) inset;
|
||||||
|
|
||||||
video {
|
video {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
border-radius: var(--border-radius) !important;
|
||||||
|
border: 1px solid rgba(var(--color-primary-dynamic), 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
|
@ -93,6 +100,33 @@
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.ad__container {
|
||||||
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 1) inset;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
border: 1px solid rgba(var(--color-primary-dynamic), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claim-list {
|
||||||
|
.ads__claim-item--tile {
|
||||||
|
background-color: var(--color-ads-background);
|
||||||
|
margin-left: unset;
|
||||||
|
margin-bottom: var(--spacing-xxs);
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.ad__container {
|
||||||
|
width: calc(var(--file-list-thumbnail-width) * 1.2);
|
||||||
|
// margin-left: var(--spacing-s);
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ads__claim-item--tile {
|
.ads__claim-item--tile {
|
||||||
|
@ -105,10 +139,30 @@
|
||||||
border-bottom: unset;
|
border-bottom: unset;
|
||||||
|
|
||||||
.ads__claim-text {
|
.ads__claim-text {
|
||||||
margin: var(--spacing-s) 0 0 0;
|
margin: var(--spacing-xs) 0 0 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
.ads__title {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads__subtitle {
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
color: rgba(var(--color-text-base), 0.6);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
line-height: 1rem;
|
||||||
|
font-size: var(--font-xsmall);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--color-text);
|
||||||
|
width: 2.1rem;
|
||||||
|
height: 2.1rem;
|
||||||
|
float: left;
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ad__container {
|
.ad__container {
|
||||||
|
@ -118,10 +172,52 @@
|
||||||
.ads__claim-text {
|
.ads__claim-text {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#close-btn {
|
||||||
|
left: unset !important;
|
||||||
|
right: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 0 var(--border-radius) 0 var(--border-radius) !important;
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
display: none !important;
|
||||||
|
position: absolute !important;
|
||||||
|
top: 0px !important;
|
||||||
|
z-index: 99 !important;
|
||||||
|
width: 3rem !important;
|
||||||
|
height: 3rem !important;
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
border-radius: 0 var(--border-radius) 0 var(--border-radius) !important;
|
||||||
|
background-size: 60% !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ads__claim-item--recommended {
|
.ads__claim-item--recommended {
|
||||||
padding: var(--spacing-s);
|
// padding: var(--spacing-s);
|
||||||
|
padding: 0;
|
||||||
|
margin-top: var(--spacing-m);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border-bottom: unset;
|
||||||
|
|
||||||
|
.ad__container {
|
||||||
|
// width:100%;
|
||||||
|
width: var(--file-list-thumbnail-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads__claim-text {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
|
|
||||||
|
.ads__title {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: $breakpoint-medium) {
|
@media (min-width: $breakpoint-medium) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -203,8 +299,66 @@
|
||||||
// Outbrain
|
// Outbrain
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
|
||||||
|
.OUTBRAIN {
|
||||||
|
border-radius: var(--border-radius) 0 0 0 !important;
|
||||||
|
height: unset !important;
|
||||||
|
|
||||||
|
.ob-widget .ob-unit.ob-rec-text {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
background-color: var(--color-primary) !important;
|
||||||
|
color: var(--color-primary-contrast) !important;
|
||||||
|
border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
|
||||||
|
top: -27px !important;
|
||||||
|
width: 2rem !important;
|
||||||
|
padding-top: 3px;
|
||||||
|
// padding-bottom:4px !important;
|
||||||
|
height: 27px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ob-widget-footer {
|
||||||
|
position: absolute !important;
|
||||||
|
right: 32px;
|
||||||
|
top: -27px;
|
||||||
|
|
||||||
|
background: black;
|
||||||
|
border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 6px;
|
||||||
|
padding-top: 3px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
|
||||||
|
.ob_what_resp {
|
||||||
|
padding: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ob-widget-items-container {
|
.ob-widget-items-container {
|
||||||
padding-left: var(--spacing-xs);
|
padding-left: var(--spacing-xs);
|
||||||
padding-right: var(--spacing-xs);
|
padding-right: var(--spacing-xs);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius) 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// Neko Patch
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
.ad__container {
|
||||||
|
div {
|
||||||
|
max-width: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#av-container #av-inner #gui::before {
|
||||||
|
background: unset !important;
|
||||||
|
#timeline #timeline-progress {
|
||||||
|
background: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#av-container #av-inner #gui #timeline #timeline-progress {
|
||||||
|
background: var(--color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -397,7 +397,7 @@
|
||||||
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: var(--spacing-s);
|
margin-right: var(--spacing-s);
|
||||||
box-shadow: 0px 0px 0px 1px rgba(var(--color-primary-dynamic), 0.1) inset;
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 0.1) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media__thumb-placeholder-text {
|
.media__thumb-placeholder-text {
|
||||||
|
@ -413,7 +413,7 @@
|
||||||
// show watch later button and duration divs when hovered
|
// show watch later button and duration divs when hovered
|
||||||
&:hover {
|
&:hover {
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
box-shadow: 0px 0px 0px 1px rgba(var(--color-primary-dynamic), 1) inset;
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 1) inset;
|
||||||
}
|
}
|
||||||
.claim-preview__title {
|
.claim-preview__title {
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
|
@ -447,6 +447,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-preview--premium-plus {
|
||||||
|
.media__thumb {
|
||||||
|
background-color: #283263;
|
||||||
|
background-image: url('https://odysee.com/public/img/astronaut_n_friends.png');
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--color-text);
|
||||||
|
width: 2.1rem;
|
||||||
|
height: 2.1rem;
|
||||||
|
float: left;
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-name {
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.claim-preview__empty {
|
.claim-preview__empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -938,11 +961,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment__badge {
|
||||||
|
display: inherit;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
box-shadow: 0px 0px 0px 1px rgba(var(--color-primary-dynamic), 1) inset;
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 1) inset;
|
||||||
background-size: 108%;
|
background-size: 108%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -965,7 +997,7 @@
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
box-shadow: 0px 0px 0px 1px rgba(var(--color-primary-dynamic), 1) inset;
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 1) inset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1332,7 +1364,7 @@ $claim-preview-progress-bar-height: 5px;
|
||||||
margin-bottom: -4px;
|
margin-bottom: -4px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-right: var(--spacing-xxs);
|
margin-right: var(--spacing-xxs);
|
||||||
text-overflow: hidden;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -1349,7 +1381,7 @@ $claim-preview-progress-bar-height: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media__thumb {
|
.media__thumb {
|
||||||
box-shadow: 0px 0px 0px 1px rgba(var(--color-primary-dynamic), 1) inset;
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 1) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-preview:not(.claim-preview--collection-mine):before {
|
.claim-preview:not(.claim-preview--collection-mine):before {
|
||||||
|
@ -1371,14 +1403,13 @@ $claim-preview-progress-bar-height: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-preview__live {
|
.claim-preview__live {
|
||||||
|
// margin-bottom: 0 !important;
|
||||||
.claim-preview__file-property-overlay {
|
.claim-preview__file-property-overlay {
|
||||||
opacity: 1; // The original 0.7 is not visible over bright thumbnails
|
opacity: 0.9; // The original 0.7 is not visible over bright thumbnails
|
||||||
color: var(--color-white-alt);
|
color: var(--color-white-alt);
|
||||||
background-color: var(--color-live);
|
background-color: var(--color-live);
|
||||||
.claim-preview__overlay-properties {
|
.claim-preview__overlay-properties {
|
||||||
margin-bottom: -2px;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: var(--font-weight-bold);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-left: var(--spacing-xs);
|
margin-left: var(--spacing-xxs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
box-shadow: 0px 0px 0px 1px rgba(var(--color-primary-dynamic), 0.1) inset;
|
box-shadow: 0 0 0 1px rgba(var(--color-primary-dynamic), 0.1) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
// M E D I A
|
// M E D I A
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
.section__header--actions {
|
.section__header--actions {
|
||||||
padding-top: var(--spacing-m);
|
padding-top: var(--spacing-m);
|
||||||
margin-top: var(--spacing-m);
|
margin-top: var(--spacing-m);
|
||||||
margin-bottom: var(--spacing-m);
|
margin-bottom: var(--spacing-l);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -188,6 +188,10 @@
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 2s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
@mixin mediaThumbHoverZoom {
|
@mixin mediaThumbHoverZoom {
|
||||||
.media__thumb,
|
.media__thumb,
|
||||||
img {
|
img {
|
||||||
|
|
|
@ -160,8 +160,8 @@
|
||||||
--color-scrollbar-track-bg: transparent;
|
--color-scrollbar-track-bg: transparent;
|
||||||
--color-body-scrollbar-track-bg: rgba(0, 0, 0, 0.5);
|
--color-body-scrollbar-track-bg: rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
--background-shade: linear-gradient(-180deg, rgba(25, 25, 25, 0.2) 2%, #202020),
|
--background-shade: linear-gradient(-180deg, rgba(25, 25, 25, 0.3) 2%, #202020),
|
||||||
radial-gradient(circle at 50% 117%, rgba(25, 25, 25, 0.2) 0, #202020 100%);
|
radial-gradient(circle at 50% 117%, rgba(25, 25, 25, 0.4) 0, #202020 100%);
|
||||||
|
|
||||||
--mui-background: #000;
|
--mui-background: #000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import I18nMessage from 'component/i18nMessage';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { platform } from 'util/platform';
|
import { platform } from 'util/platform';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const AD_CONFIGS = Object.freeze({
|
const AD_CONFIGS = Object.freeze({
|
||||||
|
@ -124,7 +126,7 @@ function Ads(props: Props) {
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Hate these? %sign_up_for_premium% for an ad free experience.
|
%sign_up_for_premium% for an ad free experience.
|
||||||
</I18nMessage>
|
</I18nMessage>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -143,8 +145,16 @@ function Ads(props: Props) {
|
||||||
'ads__claim-text--small': small,
|
'ads__claim-text--small': small,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>Ad</div>
|
<div className="ads__title">
|
||||||
<p>{adsSignInDriver}</p>
|
{__('Ad')}
|
||||||
|
<br />
|
||||||
|
{__('Hate these?')}
|
||||||
|
{/* __('No ads, a custom badge and access to exclusive features, try Odysee Premium!') */}
|
||||||
|
</div>
|
||||||
|
<div className="ads__subtitle">
|
||||||
|
<Icon icon={ICONS.UPGRADE} />
|
||||||
|
{adsSignInDriver}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue