Adnimation #1447
This commit is contained in:
commit
4d7f1c1d42
12 changed files with 323 additions and 84 deletions
|
@ -8,13 +8,7 @@ import {
|
||||||
} from 'redux/selectors/sync';
|
} from 'redux/selectors/sync';
|
||||||
import { doUserSetReferrer } from 'redux/actions/user';
|
import { doUserSetReferrer } from 'redux/actions/user';
|
||||||
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
||||||
import {
|
import { selectUser, selectUserLocale, selectUserVerifiedEmail, selectHomepageFetched } from 'redux/selectors/user';
|
||||||
selectOdyseeMembershipIsPremiumPlus,
|
|
||||||
selectUser,
|
|
||||||
selectUserLocale,
|
|
||||||
selectUserVerifiedEmail,
|
|
||||||
selectHomepageFetched,
|
|
||||||
} from 'redux/selectors/user';
|
|
||||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||||
import { doFetchChannelListMine, doFetchCollectionListMine } from 'redux/actions/claims';
|
import { doFetchChannelListMine, doFetchCollectionListMine } from 'redux/actions/claims';
|
||||||
import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
||||||
|
@ -49,7 +43,6 @@ const select = (state) => ({
|
||||||
syncFatalError: selectSyncFatalError(state),
|
syncFatalError: selectSyncFatalError(state),
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
myChannelClaimIds: selectMyChannelClaimIds(state),
|
myChannelClaimIds: selectMyChannelClaimIds(state),
|
||||||
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
|
||||||
homepageFetched: selectHomepageFetched(state),
|
homepageFetched: selectHomepageFetched(state),
|
||||||
defaultChannelClaim: selectDefaultChannelClaim(state),
|
defaultChannelClaim: selectDefaultChannelClaim(state),
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,6 @@ import useKonamiListener from 'util/enhanced-layout';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import FileRenderFloating from 'component/fileRenderFloating';
|
import FileRenderFloating from 'component/fileRenderFloating';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import useAdOutbrain from 'effects/use-ad-outbrain';
|
|
||||||
import usePrevious from 'effects/use-previous';
|
import usePrevious from 'effects/use-previous';
|
||||||
import Nag from 'component/common/nag';
|
import Nag from 'component/common/nag';
|
||||||
import REWARDS from 'rewards';
|
import REWARDS from 'rewards';
|
||||||
|
@ -83,7 +82,6 @@ type Props = {
|
||||||
syncFatalError: boolean,
|
syncFatalError: boolean,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
myChannelClaimIds: ?Array<string>,
|
myChannelClaimIds: ?Array<string>,
|
||||||
hasPremiumPlus: ?boolean,
|
|
||||||
setIncognito: (boolean) => void,
|
setIncognito: (boolean) => void,
|
||||||
fetchModBlockedList: () => void,
|
fetchModBlockedList: () => void,
|
||||||
fetchModAmIList: () => void,
|
fetchModAmIList: () => void,
|
||||||
|
@ -122,7 +120,6 @@ function App(props: Props) {
|
||||||
activeChannelClaim,
|
activeChannelClaim,
|
||||||
setIncognito,
|
setIncognito,
|
||||||
fetchModBlockedList,
|
fetchModBlockedList,
|
||||||
hasPremiumPlus,
|
|
||||||
fetchModAmIList,
|
fetchModAmIList,
|
||||||
homepageFetched,
|
homepageFetched,
|
||||||
defaultChannelClaim,
|
defaultChannelClaim,
|
||||||
|
@ -509,8 +506,6 @@ function App(props: Props) {
|
||||||
|
|
||||||
useDegradedPerformance(setLbryTvApiStatus, user);
|
useDegradedPerformance(setLbryTvApiStatus, user);
|
||||||
|
|
||||||
useAdOutbrain(Boolean(hasPremiumPlus), isAuthenticated, history?.location?.pathname);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// When language is changed or translations are fetched, we render.
|
// When language is changed or translations are fetched, we render.
|
||||||
setLangRenderKey(Date.now());
|
setLangRenderKey(Date.now());
|
||||||
|
|
|
@ -166,7 +166,13 @@ function ChannelContent(props: Props) {
|
||||||
infiniteScroll={defaultInfiniteScroll}
|
infiniteScroll={defaultInfiniteScroll}
|
||||||
injectedItem={
|
injectedItem={
|
||||||
!hasPremiumPlus && {
|
!hasPremiumPlus && {
|
||||||
node: <Ads small type="video" tileLayout />,
|
node: (index, lastVisibleIndex, pageSize) => {
|
||||||
|
if (pageSize && index < pageSize) {
|
||||||
|
return index === lastVisibleIndex ? <Ads type="video" tileLayout={tileLayout} small /> : null;
|
||||||
|
} else {
|
||||||
|
return index % (pageSize * 2) === 0 ? <Ads type="video" tileLayout={tileLayout} small /> : null;
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta={
|
meta={
|
||||||
|
|
|
@ -17,25 +17,25 @@ const OUTBRAIN_CONTAINER_KEY = 'outbrainSizeDiv';
|
||||||
let script;
|
let script;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param hasPremiumPlus
|
* @param hasPremiumPlus `undefined` if not yet fetched, boolean otherwise.
|
||||||
* @param isAuthenticated
|
* @param isAuthenticated `undefined` if email is not fetched, boolean
|
||||||
|
* otherwise.
|
||||||
* @param pathname Reminder: the component using this effect must be listening
|
* @param pathname Reminder: the component using this effect must be listening
|
||||||
* to path changes (e.g. useHistory, etc.). This value must not
|
* to path changes (e.g. useHistory, etc.). This value must not
|
||||||
* come from window.location.pathname, which doesn't spark an
|
* come from window.location.pathname, which doesn't spark an
|
||||||
* update.
|
* update.
|
||||||
*/
|
*/
|
||||||
export default function useAdOutbrain(hasPremiumPlus: boolean, isAuthenticated: boolean, pathname: string) {
|
export default function useAdOutbrain(hasPremiumPlus: ?boolean, isAuthenticated: ?boolean, pathname: string) {
|
||||||
// Only look at authentication for now. Eventually, we'll only use 'hasPremiumPlus'.
|
// Still need to look at `isAuthenticated` because `hasPremiumPlus` remains
|
||||||
// Authenticated will return undefined if not yet populated, so wait and only show
|
// in unfetched (`undefined) state in Incognito.
|
||||||
// when returned as false
|
const loadScript = isAuthenticated === false || hasPremiumPlus === false;
|
||||||
const isNotAuthenticated = isAuthenticated === false;
|
|
||||||
|
|
||||||
const propRef = React.useRef({ isAuthenticated, pathname });
|
const propRef = React.useRef({ hasPremiumPlus, pathname });
|
||||||
propRef.current = { isAuthenticated, pathname };
|
propRef.current = { hasPremiumPlus, pathname };
|
||||||
|
|
||||||
function resolveVisibility() {
|
function resolveVisibility() {
|
||||||
if (window[OUTBRAIN_CONTAINER_KEY]) {
|
if (window[OUTBRAIN_CONTAINER_KEY]) {
|
||||||
if (propRef.current.isAuthenticated) {
|
if (propRef.current.hasPremiumPlus) {
|
||||||
window[OUTBRAIN_CONTAINER_KEY].style.display = 'none';
|
window[OUTBRAIN_CONTAINER_KEY].style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
window[OUTBRAIN_CONTAINER_KEY].style.display = EXCLUDED_PATHS.includes(propRef.current.pathname) ? 'none' : '';
|
window[OUTBRAIN_CONTAINER_KEY].style.display = EXCLUDED_PATHS.includes(propRef.current.pathname) ? 'none' : '';
|
||||||
|
@ -44,7 +44,7 @@ export default function useAdOutbrain(hasPremiumPlus: boolean, isAuthenticated:
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!inIFrame() && isNotAuthenticated && !script) {
|
if (!inIFrame() && loadScript && !script) {
|
||||||
const loadTimer = setTimeout(() => {
|
const loadTimer = setTimeout(() => {
|
||||||
script = document.createElement('script');
|
script = document.createElement('script');
|
||||||
script.src = 'https://adncdnend.azureedge.net/adtags/odysee.adn.js';
|
script.src = 'https://adncdnend.azureedge.net/adtags/odysee.adn.js';
|
||||||
|
@ -57,10 +57,9 @@ export default function useAdOutbrain(hasPremiumPlus: boolean, isAuthenticated:
|
||||||
|
|
||||||
return () => clearTimeout(loadTimer);
|
return () => clearTimeout(loadTimer);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [loadScript]);
|
||||||
}, [isNotAuthenticated]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
resolveVisibility();
|
resolveVisibility();
|
||||||
}, [isAuthenticated, pathname]);
|
}, [hasPremiumPlus, pathname]);
|
||||||
}
|
}
|
||||||
|
|
50
ui/effects/use-should-show-ads.js
Normal file
50
ui/effects/use-should-show-ads.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { SHOW_ADS } from 'config';
|
||||||
|
|
||||||
|
const NO_COUNTRY_CHECK = true;
|
||||||
|
|
||||||
|
const GOOGLE_AD_URL = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
|
||||||
|
let ad_blocker_detected;
|
||||||
|
|
||||||
|
export default function useShouldShowAds(
|
||||||
|
hasPremiumPlus: boolean,
|
||||||
|
userCountry: string,
|
||||||
|
doSetAdBlockerFound: (boolean) => void
|
||||||
|
) {
|
||||||
|
const [shouldShowAds, setShouldShowAds] = React.useState(resolveAdVisibility());
|
||||||
|
|
||||||
|
function resolveAdVisibility() {
|
||||||
|
// 'ad_blocker_detected' will be undefined at startup. Wait until we are
|
||||||
|
// sure it is not blocked (i.e. === false) before showing the component.
|
||||||
|
return ad_blocker_detected === false && SHOW_ADS && !hasPremiumPlus && (NO_COUNTRY_CHECK || userCountry === 'US');
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (ad_blocker_detected === undefined) {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
|
fetch(GOOGLE_AD_URL)
|
||||||
|
.then((response) => {
|
||||||
|
const detected = response.redirected === true;
|
||||||
|
ad_blocker_detected = detected;
|
||||||
|
doSetAdBlockerFound(detected); // Also stash in redux for components to listen to.
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ad_blocker_detected = true;
|
||||||
|
doSetAdBlockerFound(true);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (mounted) {
|
||||||
|
setShouldShowAds(resolveAdVisibility());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return shouldShowAds;
|
||||||
|
}
|
|
@ -223,7 +223,18 @@ 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 && !hasPremiumPlus && { node: <Ads small type="video" tileLayout /> }}
|
injectedItem={
|
||||||
|
!isWildWest &&
|
||||||
|
!hasPremiumPlus && {
|
||||||
|
node: (index, lastVisibleIndex, pageSize) => {
|
||||||
|
if (pageSize && index < pageSize) {
|
||||||
|
return index === lastVisibleIndex ? <Ads small type="video" tileLayout={tileLayout} /> : null;
|
||||||
|
} else {
|
||||||
|
return index % (pageSize * 2) === 0 ? <Ads small type="video" tileLayout={tileLayout} /> : null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
// 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}
|
||||||
|
|
|
@ -34,6 +34,7 @@ const select = (state) => ({
|
||||||
homepageOrder: selectClientSetting(state, SETTINGS.HOMEPAGE_ORDER),
|
homepageOrder: selectClientSetting(state, SETTINGS.HOMEPAGE_ORDER),
|
||||||
hasMembership: selectHasOdyseeMembership(state),
|
hasMembership: selectHasOdyseeMembership(state),
|
||||||
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
hasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||||
|
currentTheme: selectClientSetting(state, SETTINGS.THEME),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = (dispatch) => ({
|
||||||
|
|
|
@ -15,12 +15,13 @@ import Icon from 'component/common/icon';
|
||||||
import WaitUntilOnPage from 'component/common/wait-until-on-page';
|
import WaitUntilOnPage from 'component/common/wait-until-on-page';
|
||||||
import RecommendedPersonal from 'component/recommendedPersonal';
|
import RecommendedPersonal from 'component/recommendedPersonal';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import { useIsLargeScreen } from 'effects/use-screensize';
|
import { useIsLargeScreen, useIsMobile } from 'effects/use-screensize';
|
||||||
import { GetLinksData } from 'util/buildHomepage';
|
import { GetLinksData } from 'util/buildHomepage';
|
||||||
import { getLivestreamUris } from 'util/livestream';
|
import { getLivestreamUris } from 'util/livestream';
|
||||||
import ScheduledStreams from 'component/scheduledStreams';
|
import ScheduledStreams from 'component/scheduledStreams';
|
||||||
import { splitBySeparator } from 'util/lbryURI';
|
import { splitBySeparator } from 'util/lbryURI';
|
||||||
import Ads from 'web/component/ads';
|
import Ads from 'web/component/ads';
|
||||||
|
import AdsBanner from 'web/component/adsBanner';
|
||||||
import Meme from 'web/component/meme';
|
import Meme from 'web/component/meme';
|
||||||
|
|
||||||
const CATEGORY_LIVESTREAM_LIMIT = 3;
|
const CATEGORY_LIVESTREAM_LIMIT = 3;
|
||||||
|
@ -43,6 +44,7 @@ type Props = {
|
||||||
doOpenModal: (id: string, ?{}) => void,
|
doOpenModal: (id: string, ?{}) => void,
|
||||||
hasMembership: ?boolean,
|
hasMembership: ?boolean,
|
||||||
hasPremiumPlus: boolean,
|
hasPremiumPlus: boolean,
|
||||||
|
currentTheme: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function HomePage(props: Props) {
|
function HomePage(props: Props) {
|
||||||
|
@ -62,12 +64,14 @@ function HomePage(props: Props) {
|
||||||
doOpenModal,
|
doOpenModal,
|
||||||
hasMembership,
|
hasMembership,
|
||||||
hasPremiumPlus,
|
hasPremiumPlus,
|
||||||
|
currentTheme,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
|
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
|
||||||
const showPersonalizedTags = (authenticated || !IS_WEB) && followedTags && followedTags.length > 0;
|
const showPersonalizedTags = (authenticated || !IS_WEB) && followedTags && followedTags.length > 0;
|
||||||
const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
|
const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
|
||||||
const isLargeScreen = useIsLargeScreen();
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
const isMobileScreen = useIsMobile();
|
||||||
const subscriptionChannelIds = subscribedChannels.map((sub) => splitBySeparator(sub.uri)[1]);
|
const subscriptionChannelIds = subscribedChannels.map((sub) => splitBySeparator(sub.uri)[1]);
|
||||||
|
|
||||||
const rowData: Array<RowDataItem> = GetLinksData(
|
const rowData: Array<RowDataItem> = GetLinksData(
|
||||||
|
@ -182,6 +186,8 @@ function HomePage(props: Props) {
|
||||||
label={__('View More')}
|
label={__('View More')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isMobileScreen && <AdsBanner key={`${currentTheme}:${title}`} />}
|
||||||
|
{!isMobileScreen && (index === 0 || index % 2 === 0) && <AdsBanner key={`${currentTheme}:${title}`} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -319,6 +319,7 @@
|
||||||
height: 27px !important;
|
height: 27px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
.ob-widget-footer {
|
.ob-widget-footer {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
right: 32px;
|
right: 32px;
|
||||||
|
@ -339,6 +340,7 @@
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
.ob-widget-items-container {
|
.ob-widget-items-container {
|
||||||
padding-left: var(--spacing-xs);
|
padding-left: var(--spacing-xs);
|
||||||
|
@ -366,3 +368,81 @@
|
||||||
#av-container #av-inner #gui #timeline #timeline-progress {
|
#av-container #av-inner #gui #timeline #timeline-progress {
|
||||||
background: var(--color-primary) !important;
|
background: var(--color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// Banner
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
$BANNER_BORDER: 1px;
|
||||||
|
|
||||||
|
.banner-ad {
|
||||||
|
margin-top: var(--spacing-l);
|
||||||
|
background-color: var(--color-ads-background);
|
||||||
|
border: $BANNER_BORDER solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-ad__container {
|
||||||
|
min-height: 250px;
|
||||||
|
|
||||||
|
padding: var(--spacing-xxs) var(--spacing-m);
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
padding: var(--spacing-xxs) var(--spacing-xxs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-xlarge) {
|
||||||
|
max-width: calc(#{$breakpoint-xlarge} * 3 / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-ad__driver {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
max-width: 50%;
|
||||||
|
|
||||||
|
border-left: $BANNER_BORDER solid var(--color-border);
|
||||||
|
border-bottom: $BANNER_BORDER solid var(--color-border);
|
||||||
|
border-radius: 0 var(--border-radius) 0 var(--border-radius);
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
font-size: var(--font-xsmall);
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
font-size: var(--font-xxsmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: 0 var(--spacing-xxxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-ad__driver-label {
|
||||||
|
color: var(--color-text-subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-ad__driver-value {
|
||||||
|
color: var(--color-text-subtitle);
|
||||||
|
border-left: $BANNER_BORDER solid var(--color-border);
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
@supports (-webkit-line-clamp: 3) {
|
||||||
|
white-space: initial;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: calc(var(--spacing-xxxs) / 2) var(--spacing-xxxs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { SHOW_ADS } from 'config';
|
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import PremiumPlusTile from 'component/premiumPlusTile';
|
import PremiumPlusTile from 'component/premiumPlusTile';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import useShouldShowAds from 'effects/use-should-show-ads';
|
||||||
import { platform } from 'util/platform';
|
import { platform } from 'util/platform';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import { LocalStorage, LS } from 'util/storage';
|
import { LocalStorage, LS } from 'util/storage';
|
||||||
|
|
||||||
|
const USE_ADNIMATION = true;
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const AD_CONFIGS = Object.freeze({
|
const AD_CONFIGS = Object.freeze({
|
||||||
DEFAULT: {
|
DEFAULT: {
|
||||||
|
@ -25,10 +27,30 @@ const AD_CONFIGS = Object.freeze({
|
||||||
url: 'https://tg1.vidcrunch.com/api/adserver/spt?AV_TAGID=61dff05c599f1e20b01085d4&AV_PUBLISHERID=6182c8993c8ae776bd5635e9',
|
url: 'https://tg1.vidcrunch.com/api/adserver/spt?AV_TAGID=61dff05c599f1e20b01085d4&AV_PUBLISHERID=6182c8993c8ae776bd5635e9',
|
||||||
tag: 'AV61dff05c599f1e20b01085d4',
|
tag: 'AV61dff05c599f1e20b01085d4',
|
||||||
},
|
},
|
||||||
|
ADNIMATION: {
|
||||||
|
url: 'https://tg1.aniview.com/api/adserver/spt?AV_TAGID=6252bb6f28951333ec10a7a6&AV_PUBLISHERID=601d9a7f2e688a79e17c1265',
|
||||||
|
tag: 'AV6252bb6f28951333ec10a7a6',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Internal use only. One-time update flag.
|
// ****************************************************************************
|
||||||
let ad_blocker_detected;
|
// Helpers
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
function removeIfExists(querySelector) {
|
||||||
|
const element = document.querySelector(querySelector);
|
||||||
|
if (element) element.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveVidcrunchConfig() {
|
||||||
|
const mobileAds = platform.isAndroid() || platform.isIOS();
|
||||||
|
const isInEu = LocalStorage.getItem(LS.GDPR_REQUIRED) === 'true';
|
||||||
|
return isInEu ? AD_CONFIGS.EU : mobileAds ? AD_CONFIGS.MOBILE : AD_CONFIGS.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// Ads
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: string,
|
type: string,
|
||||||
|
@ -37,18 +59,11 @@ type Props = {
|
||||||
className?: string,
|
className?: string,
|
||||||
noFallback?: boolean,
|
noFallback?: boolean,
|
||||||
// --- redux ---
|
// --- redux ---
|
||||||
claim: Claim,
|
|
||||||
isMature: boolean,
|
|
||||||
userHasPremiumPlus: boolean,
|
userHasPremiumPlus: boolean,
|
||||||
userCountry: string,
|
userCountry: string,
|
||||||
doSetAdBlockerFound: (boolean) => void,
|
doSetAdBlockerFound: (boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function removeIfExists(querySelector) {
|
|
||||||
const element = document.querySelector(querySelector);
|
|
||||||
if (element) element.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function Ads(props: Props) {
|
function Ads(props: Props) {
|
||||||
const {
|
const {
|
||||||
type = 'video',
|
type = 'video',
|
||||||
|
@ -61,46 +76,8 @@ function Ads(props: Props) {
|
||||||
doSetAdBlockerFound,
|
doSetAdBlockerFound,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [shouldShowAds, setShouldShowAds] = React.useState(resolveAdVisibility());
|
const shouldShowAds = useShouldShowAds(userHasPremiumPlus, userCountry, doSetAdBlockerFound);
|
||||||
const mobileAds = platform.isAndroid() || platform.isIOS();
|
const adConfig = USE_ADNIMATION ? AD_CONFIGS.ADNIMATION : resolveVidcrunchConfig();
|
||||||
|
|
||||||
// this is populated from app based on location
|
|
||||||
const isInEu = LocalStorage.getItem(LS.GDPR_REQUIRED) === 'true';
|
|
||||||
const adConfig = isInEu ? AD_CONFIGS.EU : mobileAds ? AD_CONFIGS.MOBILE : AD_CONFIGS.DEFAULT;
|
|
||||||
|
|
||||||
function resolveAdVisibility() {
|
|
||||||
// 'ad_blocker_detected' will be undefined at startup. Wait until we are
|
|
||||||
// sure it is not blocked (i.e. === false) before showing the component.
|
|
||||||
return ad_blocker_detected === false && SHOW_ADS && !userHasPremiumPlus && userCountry === 'US';
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ad_blocker_detected === undefined) {
|
|
||||||
let mounted = true;
|
|
||||||
const GOOGLE_AD_URL = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
|
|
||||||
|
|
||||||
fetch(GOOGLE_AD_URL)
|
|
||||||
.then((response) => {
|
|
||||||
const detected = response.redirected === true;
|
|
||||||
window.odysee_ad_blocker_detected = detected;
|
|
||||||
ad_blocker_detected = detected;
|
|
||||||
doSetAdBlockerFound(detected);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
ad_blocker_detected = true;
|
|
||||||
doSetAdBlockerFound(true);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (mounted) {
|
|
||||||
setShouldShowAds(resolveAdVisibility());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
mounted = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// add script to DOM
|
// add script to DOM
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -122,11 +99,20 @@ function Ads(props: Props) {
|
||||||
delete window.__VIDCRUNCH_CONFIG_618bb4d28aac298191eec411__;
|
delete window.__VIDCRUNCH_CONFIG_618bb4d28aac298191eec411__;
|
||||||
delete window.__player_618bb4d28aac298191eec411__;
|
delete window.__player_618bb4d28aac298191eec411__;
|
||||||
|
|
||||||
|
const styles = document.querySelectorAll('body > style');
|
||||||
|
styles.forEach((s) => {
|
||||||
|
// We are asking Adnimation to supply us with a specific ID or
|
||||||
|
// pattern so that our query wouldn't break when they change their
|
||||||
|
// script. For now, this is the "best effort".
|
||||||
|
if (s.innerText && s.innerText.startsWith('#outbrain')) {
|
||||||
|
s.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// clean DOM elements from ad related elements
|
// clean DOM elements from ad related elements
|
||||||
removeIfExists('[src^="https://cdn.vidcrunch.com/618bb4d28aac298191eec411.js"]');
|
removeIfExists('[src^="https://player.avplayer.com"]');
|
||||||
removeIfExists('[src^="https://player.aniview.com/script/6.1/aniview.js"]');
|
removeIfExists('[src^="https://gum.criteo.com"]');
|
||||||
removeIfExists('[id^="AVLoaderaniplayer_vidcrunch"]');
|
removeIfExists('[id^="AVLoaderaniview_slot"]');
|
||||||
removeIfExists('#av_css_id');
|
|
||||||
};
|
};
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
18
web/component/adsBanner/index.js
Normal file
18
web/component/adsBanner/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import * as SETTINGS from 'constants/settings';
|
||||||
|
import { doSetAdBlockerFound } from 'redux/actions/app';
|
||||||
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import { selectOdyseeMembershipIsPremiumPlus, selectUserCountry } from 'redux/selectors/user';
|
||||||
|
import AdsBanner from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
userHasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state),
|
||||||
|
userCountry: selectUserCountry(state),
|
||||||
|
currentTheme: selectClientSetting(state, SETTINGS.THEME),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = {
|
||||||
|
doSetAdBlockerFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(select, perform)(AdsBanner);
|
94
web/component/adsBanner/view.jsx
Normal file
94
web/component/adsBanner/view.jsx
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import useShouldShowAds from 'effects/use-should-show-ads';
|
||||||
|
|
||||||
|
const AD_SCRIPT_URL = 'https://widgets.outbrain.com/outbrain.js';
|
||||||
|
|
||||||
|
const AD_CONFIG = {
|
||||||
|
AR_18: 'AR_18', // 5 tiles.
|
||||||
|
AR_60: 'AR_60', // 6 tiles. Doesn't work well on mobile (6 tiles compresses to 1, text only).
|
||||||
|
AR_3: 'AR_3', // 4 tiles on desktop, dynamic count on mobile.
|
||||||
|
};
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
const adsSignInDriver = (
|
||||||
|
<I18nMessage
|
||||||
|
tokens={{
|
||||||
|
sign_up_for_premium: (
|
||||||
|
<Button button="link" label={__('Get Odysee Premium+')} navigate={`/$/${PAGES.ODYSEE_MEMBERSHIP}`} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
%sign_up_for_premium% for an ad free experience.
|
||||||
|
</I18nMessage>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// AdsBanner
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
|
let gReferenceCounter = 0;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
userHasPremiumPlus: boolean,
|
||||||
|
userCountry: string,
|
||||||
|
currentTheme: string,
|
||||||
|
doSetAdBlockerFound: (boolean) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AdsBanner(props: Props) {
|
||||||
|
const { userHasPremiumPlus, userCountry, currentTheme, doSetAdBlockerFound } = props;
|
||||||
|
const shouldShowAds = useShouldShowAds(userHasPremiumPlus, userCountry, doSetAdBlockerFound);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (shouldShowAds) {
|
||||||
|
try {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = AD_SCRIPT_URL;
|
||||||
|
script.async = true;
|
||||||
|
script.onload = () => {
|
||||||
|
++gReferenceCounter;
|
||||||
|
};
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
document.body.appendChild(script);
|
||||||
|
return () => {
|
||||||
|
// $FlowFixMe
|
||||||
|
document.body.removeChild(script);
|
||||||
|
|
||||||
|
if (--gReferenceCounter <= 0) {
|
||||||
|
// Note: This method has the bad requirement of the parent having to
|
||||||
|
// mount and dismount all banners in the same cycle.
|
||||||
|
delete window.OBR;
|
||||||
|
// TODO: clear styles after the team adds an ID or class for us to query.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}, [shouldShowAds]);
|
||||||
|
|
||||||
|
if (!shouldShowAds) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="banner-ad">
|
||||||
|
<div className="banner-ad__driver">
|
||||||
|
<div className="banner-ad__driver-label">Ad</div>
|
||||||
|
<div className="banner-ad__driver-value">{adsSignInDriver}</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="banner-ad__container OUTBRAIN"
|
||||||
|
data-ob-contenturl="DROP_PERMALINK_HERE"
|
||||||
|
data-widget-id={AD_CONFIG.AR_18}
|
||||||
|
data-ob-installation-key="ADNIMKAJDGAG4GAO6AGG6H5KP"
|
||||||
|
data-dark-mode={currentTheme === 'dark' ? 'true' : 'false'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in a new issue