From 0bcea943d56ca0f2e655f4687fc83f49e6356d4c Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Tue, 31 May 2022 17:19:21 +0800 Subject: [PATCH] Re-implement/enable sticky ad ## Ticket 1583: "add incontent ads to category/channel pages (break up every X claims?), or add back bottom ad in those areas." ## Behavioral changes - Hide when in homepage (as currently we have ads between categories). - Fix the light theme (it was transparent). ## Code changes - While an Effect is the 'right' choice given that there is no jsx to mount, the need to prop-drill from the parent was getting a bit annoying, so converted to a Component instead. - Remove the delay for Core Vitals avoidance for now -- seems to make the sticky less likely to serve an ad. - Now that the membership state is correctly populated for incognito (see 9d830615), there is no more need to check for `isAuthenticated`. --- ui/component/app/view.jsx | 3 ++ ui/effects/use-ad-outbrain.js | 65 -------------------------- ui/scss/component/_ads.scss | 2 + web/component/adsSticky/index.js | 20 ++++++++ web/component/adsSticky/view.jsx | 80 ++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 65 deletions(-) delete mode 100644 ui/effects/use-ad-outbrain.js create mode 100644 web/component/adsSticky/index.js create mode 100644 web/component/adsSticky/view.jsx diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index bf6686417..a67a6f02c 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -21,6 +21,7 @@ import usePersistedState from 'effects/use-persisted-state'; import useConnectionStatus from 'effects/use-connection-status'; import Spinner from 'component/spinner'; import LANGUAGES from 'constants/languages'; +import AdsSticky from 'web/component/adsSticky'; import YoutubeWelcome from 'web/component/youtubeReferralWelcome'; import { useDegradedPerformance, @@ -558,6 +559,8 @@ function App(props: Props) { )} {getStatusNag()} + + )} diff --git a/ui/effects/use-ad-outbrain.js b/ui/effects/use-ad-outbrain.js deleted file mode 100644 index 4a25957e0..000000000 --- a/ui/effects/use-ad-outbrain.js +++ /dev/null @@ -1,65 +0,0 @@ -// @flow -import React from 'react'; -import * as PAGES from 'constants/pages'; - -function inIFrame() { - try { - return window.self !== window.top; - } catch (e) { - return true; - } -} - -const EXCLUDED_PATHS = Object.freeze([`/$/${PAGES.AUTH}`, `/$/${PAGES.AUTH_SIGNIN}`, `/$/${PAGES.AUTH_VERIFY}`]); - -const LOAD_AD_DELAY_MS = 3000; // Wait past boot-up and core-vitals period. -const OUTBRAIN_CONTAINER_KEY = 'outbrainSizeDiv'; -let script; - -/** - * @param hasPremiumPlus `undefined` if not yet fetched, boolean otherwise. - * @param isAuthenticated `undefined` if email is not fetched, boolean - * otherwise. - * @param pathname Reminder: the component using this effect must be listening - * to path changes (e.g. useHistory, etc.). This value must not - * come from window.location.pathname, which doesn't spark an - * update. - */ -export default function useAdOutbrain(hasPremiumPlus: ?boolean, isAuthenticated: ?boolean, pathname: string) { - // Still need to look at `isAuthenticated` because `hasPremiumPlus` remains - // in unfetched (`undefined) state in Incognito. - const loadScript = isAuthenticated === false || hasPremiumPlus === false; - - const propRef = React.useRef({ hasPremiumPlus, pathname }); - propRef.current = { hasPremiumPlus, pathname }; - - function resolveVisibility() { - if (window[OUTBRAIN_CONTAINER_KEY]) { - if (propRef.current.hasPremiumPlus) { - window[OUTBRAIN_CONTAINER_KEY].style.display = 'none'; - } else { - window[OUTBRAIN_CONTAINER_KEY].style.display = EXCLUDED_PATHS.includes(propRef.current.pathname) ? 'none' : ''; - } - } - } - - React.useEffect(() => { - if (!inIFrame() && loadScript && !script) { - const loadTimer = setTimeout(() => { - script = document.createElement('script'); - script.src = 'https://adncdnend.azureedge.net/adtags/odysee.adn.js'; - script.async = true; - script.addEventListener('load', resolveVisibility); - - // $FlowFixMe - document.body.appendChild(script); - }, LOAD_AD_DELAY_MS); - - return () => clearTimeout(loadTimer); - } - }, [loadScript]); - - React.useEffect(() => { - resolveVisibility(); - }, [hasPremiumPlus, pathname]); -} diff --git a/ui/scss/component/_ads.scss b/ui/scss/component/_ads.scss index 9032d6f5e..8bfd9ba8e 100644 --- a/ui/scss/component/_ads.scss +++ b/ui/scss/component/_ads.scss @@ -323,6 +323,8 @@ .ob-widget .ob-unit.ob-rec-text { font-size: 12px !important; } + + background-color: var(--color-ads-background); } .closeButton { diff --git a/web/component/adsSticky/index.js b/web/component/adsSticky/index.js new file mode 100644 index 000000000..fef07fec1 --- /dev/null +++ b/web/component/adsSticky/index.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux'; +import * as SETTINGS from 'constants/settings'; +import { doSetAdBlockerFound } from 'redux/actions/app'; +import { selectAdBlockerFound } from 'redux/selectors/app'; +import { selectClientSetting } from 'redux/selectors/settings'; +import { selectOdyseeMembershipIsPremiumPlus, selectUserCountry } from 'redux/selectors/user'; +import AdsSticky from './view'; + +const select = (state, props) => ({ + isAdBlockerFound: selectAdBlockerFound(state), + userHasPremiumPlus: selectOdyseeMembershipIsPremiumPlus(state), + userCountry: selectUserCountry(state), + currentTheme: selectClientSetting(state, SETTINGS.THEME), +}); + +const perform = { + doSetAdBlockerFound, +}; + +export default connect(select, perform)(AdsSticky); diff --git a/web/component/adsSticky/view.jsx b/web/component/adsSticky/view.jsx new file mode 100644 index 000000000..be4e9a1f2 --- /dev/null +++ b/web/component/adsSticky/view.jsx @@ -0,0 +1,80 @@ +// @flow +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import * as PAGES from 'constants/pages'; +import useShouldShowAds from 'effects/use-should-show-ads'; + +// **************************************************************************** +// AdsSticky +// **************************************************************************** + +const PATH_BLACKLIST = [ + // Don't show sticky in these paths: + { path: `/`, exact: true }, + { path: `/$/${PAGES.AUTH}`, exact: false }, + { path: `/$/${PAGES.AUTH_SIGNIN}`, exact: false }, + { path: `/$/${PAGES.AUTH_VERIFY}`, exact: false }, + { path: `/$/${PAGES.SETTINGS}`, exact: false }, +]; + +const OUTBRAIN_CONTAINER_KEY = 'outbrainSizeDiv'; +let gScript; + +type Props = { + isAdBlockerFound: ?boolean, + userHasPremiumPlus: boolean, + userCountry: string, + currentTheme: string, + doSetAdBlockerFound: (boolean) => void, +}; + +export default function AdsSticky(props: Props) { + const { isAdBlockerFound, userHasPremiumPlus, userCountry, doSetAdBlockerFound } = props; + const shouldShowAds = useShouldShowAds(userHasPremiumPlus, userCountry, isAdBlockerFound, doSetAdBlockerFound); + const { + location: { pathname }, + } = useHistory(); + + // -- Mount script; 1 per session. + React.useEffect(() => { + if (shouldShowAds && !gScript && !inIFrame()) { + gScript = document.createElement('script'); + gScript.src = 'https://adncdnend.azureedge.net/adtags/odysee.adn.js'; + gScript.async = true; + + // $FlowFixMe + document.body.appendChild(gScript); + } + }, [shouldShowAds]); + + // -- Update visibility per pathname + React.useEffect(() => { + const container = window[OUTBRAIN_CONTAINER_KEY]; + if (container) { + for (const x of PATH_BLACKLIST) { + const found = (x.exact && pathname === x.path) || (!x.exact && pathname.startsWith(x.path)); + if (found) { + container.style.display = 'none'; + return; + } + } + + container.style.display = ''; + } + }, [pathname]); + + // Nothing for us to mount; the ad script will handle everything. + return null; +} + +// **************************************************************************** +// Helpers +// **************************************************************************** + +function inIFrame() { + try { + return window.self !== window.top; + } catch (e) { + return true; + } +}