initial commit for inline video ads

This commit is contained in:
Sean Yesmunt 2020-03-26 17:47:07 -04:00
parent 829c2eac50
commit c4fc2993d5
12 changed files with 162 additions and 67 deletions

View file

@ -4,28 +4,49 @@ import React, { useEffect } from 'react';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames';
const ADS_URL = '//assets.revcontent.com/master/delivery.js'; const ADS_URL = '//assets.revcontent.com/master/delivery.js';
const IS_MOBILE = typeof window.orientation !== 'undefined'; const IS_MOBILE = typeof window.orientation !== 'undefined';
type Props = { type Props = {
location: { pathname: string }, location: { pathname: string },
type: string,
small: boolean,
}; };
function Ads(props: Props) { function Ads(props: Props) {
const { const {
location: { pathname }, location: { pathname },
type = 'sidebar',
small,
} = props; } = props;
useEffect(() => {
if (!IS_MOBILE) {
const script = document.createElement('script');
useEffect(() => {
if (type === 'video') {
try {
const d = document;
const s = 'script';
const n = 'playbuzz-stream';
let js;
let fjs = d.getElementsByTagName(s)[0];
js = d.createElement(s);
js.className = n;
js.src = 'https://stream.playbuzz.com/player/62d1eb10-e362-4873-99ed-c64a4052b43b';
// $FlowFixMe
fjs.parentNode.inmodifiedUrlQuerysertBefore(js, fjs);
} catch (e) {}
}
}, [type]);
useEffect(() => {
if (!IS_MOBILE && type === 'sidebar') {
const script = document.createElement('script');
script.src = ADS_URL; script.src = ADS_URL;
script.async = true; script.async = true;
// $FlowFixMe // $FlowFixMe
document.body.appendChild(script); document.body.appendChild(script);
return () => { return () => {
// $FlowFixMe // $FlowFixMe
document.body.removeChild(script); document.body.removeChild(script);
@ -37,27 +58,37 @@ function Ads(props: Props) {
} }
}; };
} }
}, []); }, [type]);
return ( const adsSignInDriver = (
<I18nMessage
tokens={{
sign_in_to_lbrytv: (
<Button button="link" label={__('Sign in to lbry.tv')} navigate={`/$/${PAGES.AUTH}?redirect=${pathname}`} />
),
download_the_app: <Button button="link" label={__('download the app')} href="https://lbry.com/get" />,
}}
>
Hate these? %sign_in_to_lbrytv% or %download_the_app% for an ad free experience.
</I18nMessage>
);
return type === 'video' ? (
<div className="ads__claim-item">
<div id="62d1eb10-e362-4873-99ed-c64a4052b43b" />
<div
className={classnames('ads__claim-text', {
'ads__claim-text--small': small,
})}
>
<div>Ad</div>
<p>{adsSignInDriver}</p>
</div>
</div>
) : (
<div className="ads-wrapper"> <div className="ads-wrapper">
<p>Ads</p> <p>Ads</p>
<p> <p>{adsSignInDriver}</p>
<I18nMessage
tokens={{
sign_in_to_lbrytv: (
<Button
button="link"
label={__('Sign in to lbry.tv')}
navigate={`/$/${PAGES.AUTH}?redirect=${pathname}`}
/>
),
download_the_app: <Button button="link" label={__('download the app')} href="https://lbry.com/get" />,
}}
>
Hate these? %sign_in_to_lbrytv% or %download_the_app% for an ad free experience.
</I18nMessage>
</p>
<div <div
id="rc-widget-0a74cf" id="rc-widget-0a74cf"
data-rc-widget data-rc-widget

View file

@ -10,6 +10,7 @@ import {
makeSelectClaimForUri, makeSelectClaimForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { selectUserVerifiedEmail } from 'lbryinc';
import ChannelPage from './view'; import ChannelPage from './view';
const select = (state, props) => { const select = (state, props) => {
@ -23,6 +24,7 @@ const select = (state, props) => {
channelIsMine: makeSelectClaimIsMine(props.uri)(state), channelIsMine: makeSelectClaimIsMine(props.uri)(state),
channelIsBlocked: selectChannelIsBlocked(props.uri)(state), channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
claim: props.uri && makeSelectClaimForUri(props.uri)(state), claim: props.uri && makeSelectClaimForUri(props.uri)(state),
isAuthenticated: selectUserVerifiedEmail(state),
}; };
}; };
@ -30,9 +32,4 @@ const perform = dispatch => ({
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)), fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
}); });
export default withRouter( export default withRouter(connect(select, perform)(ChannelPage));
connect(
select,
perform
)(ChannelPage)
);

View file

@ -5,6 +5,7 @@ import { withRouter } from 'react-router-dom';
import Button from 'component/button'; import Button from 'component/button';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import * as CS from 'constants/claim_search'; import * as CS from 'constants/claim_search';
import Ads from 'lbrytv/component/ads';
type Props = { type Props = {
uri: string, uri: string,
@ -17,10 +18,11 @@ type Props = {
fetchClaims: (string, number) => void, fetchClaims: (string, number) => void,
channelIsBlackListed: boolean, channelIsBlackListed: boolean,
claim: ?Claim, claim: ?Claim,
isAuthenticated: boolean,
}; };
function ChannelContent(props: Props) { function ChannelContent(props: Props) {
const { uri, fetching, channelIsMine, channelIsBlocked, channelIsBlackListed, claim } = props; const { uri, fetching, channelIsMine, channelIsBlocked, channelIsBlackListed, claim, isAuthenticated } = props;
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
return ( return (
@ -51,7 +53,11 @@ function ChannelContent(props: Props) {
{!channelIsMine && claimsInChannel > 0 && <HiddenNsfwClaims uri={uri} />} {!channelIsMine && claimsInChannel > 0 && <HiddenNsfwClaims uri={uri} />}
{claim && claimsInChannel > 0 ? ( {claim && claimsInChannel > 0 ? (
<ClaimListDiscover channelIds={[claim.claim_id]} defaultOrderBy={CS.ORDER_BY_NEW} /> <ClaimListDiscover
channelIds={[claim.claim_id]}
defaultOrderBy={CS.ORDER_BY_NEW}
injectedItem={!isAuthenticated && IS_WEB && <Ads type="video" />}
/>
) : ( ) : (
<section className="main--empty">This channel hasn't published anything yet</section> <section className="main--empty">This channel hasn't published anything yet</section>
)} )}

View file

@ -31,6 +31,7 @@ type Props = {
renderProperties: ?(Claim) => Node, renderProperties: ?(Claim) => Node,
includeSupportAction?: boolean, includeSupportAction?: boolean,
hideBlock: boolean, hideBlock: boolean,
injectedItem: ?Node,
}; };
export default function ClaimList(props: Props) { export default function ClaimList(props: Props) {
@ -53,6 +54,7 @@ export default function ClaimList(props: Props) {
renderProperties, renderProperties,
includeSupportAction, includeSupportAction,
hideBlock, hideBlock,
injectedItem,
} = props; } = props;
const [scrollBottomCbMap, setScrollBottomCbMap] = useState({}); const [scrollBottomCbMap, setScrollBottomCbMap] = useState({});
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
@ -130,26 +132,28 @@ export default function ClaimList(props: Props) {
{urisLength > 0 && ( {urisLength > 0 && (
<ul className="card ul--no-style"> <ul className="card ul--no-style">
{sortedUris.map((uri, index) => ( {sortedUris.map((uri, index) => (
<ClaimPreview <React.Fragment key={uri}>
key={uri} {injectedItem && index === 4 && <li>{injectedItem}</li>}
uri={uri} <ClaimPreview
type={type} uri={uri}
includeSupportAction={includeSupportAction} type={type}
showUnresolvedClaim={showUnresolvedClaims} includeSupportAction={includeSupportAction}
properties={renderProperties || (type !== 'small' ? undefined : false)} showUnresolvedClaim={showUnresolvedClaims}
showUserBlocked={showHiddenByUser} properties={renderProperties || (type !== 'small' ? undefined : false)}
hideBlock={hideBlock} showUserBlocked={showHiddenByUser}
customShouldHide={(claim: StreamClaim) => { hideBlock={hideBlock}
// Hack to hide spee.ch thumbnail publishes customShouldHide={(claim: StreamClaim) => {
// If it meets these requirements, it was probably uploaded here: // Hack to hide spee.ch thumbnail publishes
// https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79 // If it meets these requirements, it was probably uploaded here:
if (claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch') { // https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79
return true; if (claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch') {
} else { return true;
return false; } else {
} return false;
}} }
/> }}
/>
</React.Fragment>
))} ))}
</ul> </ul>
)} )}

View file

@ -52,6 +52,7 @@ type Props = {
repostedClaimId?: string, repostedClaimId?: string,
pageSize?: number, pageSize?: number,
followedTags?: Array<Tag>, followedTags?: Array<Tag>,
injectedItem: ?Node,
}; };
function ClaimListDiscover(props: Props) { function ClaimListDiscover(props: Props) {
@ -87,6 +88,7 @@ function ClaimListDiscover(props: Props) {
repostedClaimId, repostedClaimId,
hideFilter, hideFilter,
followedTags, followedTags,
injectedItem,
} = props; } = props;
const didNavigateForward = history.action === 'PUSH'; const didNavigateForward = history.action === 'PUSH';
const { search } = location; const { search } = location;
@ -596,6 +598,7 @@ function ClaimListDiscover(props: Props) {
renderProperties={renderProperties} renderProperties={renderProperties}
includeSupportAction={includeSupportAction} includeSupportAction={includeSupportAction}
hideBlock={hideBlock} hideBlock={hideBlock}
injectedItem={injectedItem}
/> />
{loading && ( {loading && (

View file

@ -6,6 +6,7 @@ import {
makeSelectRecommendedContentForUri, makeSelectRecommendedContentForUri,
selectIsSearching, selectIsSearching,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectUserVerifiedEmail } from 'lbryinc';
import RecommendedVideos from './view'; import RecommendedVideos from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -13,13 +14,11 @@ const select = (state, props) => ({
mature: makeSelectClaimIsNsfw(props.uri)(state), mature: makeSelectClaimIsNsfw(props.uri)(state),
recommendedContent: makeSelectRecommendedContentForUri(props.uri)(state), recommendedContent: makeSelectRecommendedContentForUri(props.uri)(state),
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),
isAuthenticated: selectUserVerifiedEmail(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
search: (query, options) => dispatch(doSearch(query, options)), search: (query, options) => dispatch(doSearch(query, options)),
}); });
export default connect( export default connect(select, perform)(RecommendedVideos);
select,
perform
)(RecommendedVideos);

View file

@ -1,6 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import Ads from 'lbrytv/component/ads';
type Options = { type Options = {
related_to: string, related_to: string,
@ -15,6 +16,7 @@ type Props = {
isSearching: boolean, isSearching: boolean,
search: (string, Options) => void, search: (string, Options) => void,
mature: boolean, mature: boolean,
isAuthenticated: boolean,
}; };
export default class RecommendedContent extends React.PureComponent<Props> { export default class RecommendedContent extends React.PureComponent<Props> {
@ -59,13 +61,14 @@ export default class RecommendedContent extends React.PureComponent<Props> {
didSearch: ?boolean; didSearch: ?boolean;
render() { render() {
const { recommendedContent, isSearching } = this.props; const { recommendedContent, isSearching, isAuthenticated } = this.props;
return ( return (
<ClaimList <ClaimList
type="small" type="small"
loading={isSearching} loading={isSearching}
uris={recommendedContent} uris={recommendedContent}
injectedItem={!isAuthenticated && IS_WEB && <Ads type="video" small />}
header={__('Related')} header={__('Related')}
empty={__('No related content found')} empty={__('No related content found')}
/> />

View file

@ -1,5 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectClaimForUri, selectFollowedTags, doResolveUri } from 'lbry-redux'; import { makeSelectClaimForUri, selectFollowedTags, doResolveUri } from 'lbry-redux';
import { selectUserVerifiedEmail } from 'lbryinc';
import { doToggleTagFollowDesktop } from 'redux/actions/tags'; import { doToggleTagFollowDesktop } from 'redux/actions/tags';
import * as CS from 'constants/claim_search'; import * as CS from 'constants/claim_search';
import Tags from './view'; import Tags from './view';
@ -13,6 +14,7 @@ const select = (state, props) => {
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
repostedUri: repostedUri, repostedUri: repostedUri,
repostedClaim: repostedUri ? makeSelectClaimForUri(repostedUri)(state) : null, repostedClaim: repostedUri ? makeSelectClaimForUri(repostedUri)(state) : null,
isAuthenticated: selectUserVerifiedEmail(state),
}; };
}; };

View file

@ -9,6 +9,7 @@ import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw'; import HiddenNsfw from 'component/common/hidden-nsfw';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import * as CS from 'constants/claim_search'; import * as CS from 'constants/claim_search';
import Ads from 'lbrytv/component/ads';
type Props = { type Props = {
location: { search: string }, location: { search: string },
@ -17,6 +18,7 @@ type Props = {
repostedClaim: ?GenericClaim, repostedClaim: ?GenericClaim,
doToggleTagFollowDesktop: string => void, doToggleTagFollowDesktop: string => void,
doResolveUri: string => void, doResolveUri: string => void,
isAuthenticated: boolean,
}; };
function DiscoverPage(props: Props) { function DiscoverPage(props: Props) {
@ -27,6 +29,7 @@ function DiscoverPage(props: Props) {
repostedUri, repostedUri,
doToggleTagFollowDesktop, doToggleTagFollowDesktop,
doResolveUri, doResolveUri,
isAuthenticated,
} = props; } = props;
const buttonRef = useRef(); const buttonRef = useRef();
const isHovering = useHover(buttonRef); const isHovering = useHover(buttonRef);
@ -89,6 +92,7 @@ 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={!isAuthenticated && IS_WEB && <Ads type="video" />}
meta={ meta={
tag && ( tag && (
<Button <Button

View file

@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { doSearch, selectIsSearching, makeSelectSearchUris, makeSelectQueryWithOptions, doToast } from 'lbry-redux'; import { doSearch, selectIsSearching, makeSelectSearchUris, makeSelectQueryWithOptions, doToast } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectUserVerifiedEmail } from 'lbryinc';
import analytics from 'analytics'; import analytics from 'analytics';
import SearchPage from './view'; import SearchPage from './view';
@ -17,6 +18,7 @@ const select = state => {
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
uris: uris, uris: uris,
isAuthenticated: selectUserVerifiedEmail(state),
}; };
}; };
@ -42,7 +44,4 @@ const perform = dispatch => ({
}, },
}); });
export default connect( export default connect(select, perform)(SearchPage);
select,
perform
)(SearchPage);

View file

@ -9,6 +9,7 @@ import Page from 'component/page';
import SearchOptions from 'component/searchOptions'; import SearchOptions from 'component/searchOptions';
import Button from 'component/button'; import Button from 'component/button';
import ClaimUri from 'component/claimUri'; import ClaimUri from 'component/claimUri';
import Ads from 'lbrytv/component/ads';
type AdditionalOptions = { type AdditionalOptions = {
isBackgroundSearch: boolean, isBackgroundSearch: boolean,
@ -23,10 +24,20 @@ type Props = {
onFeedbackNegative: string => void, onFeedbackNegative: string => void,
onFeedbackPositive: string => void, onFeedbackPositive: string => void,
showNsfw: boolean, showNsfw: boolean,
isAuthenticated: boolean,
}; };
export default function SearchPage(props: Props) { export default function SearchPage(props: Props) {
const { search, uris, onFeedbackPositive, onFeedbackNegative, location, isSearching, showNsfw } = props; const {
search,
uris,
onFeedbackPositive,
onFeedbackNegative,
location,
isSearching,
showNsfw,
isAuthenticated,
} = props;
const urlParams = new URLSearchParams(location.search); const urlParams = new URLSearchParams(location.search);
const urlQuery = urlParams.get('q') || ''; const urlQuery = urlParams.get('q') || '';
const additionalOptions: AdditionalOptions = { isBackgroundSearch: false }; const additionalOptions: AdditionalOptions = { isBackgroundSearch: false };
@ -44,12 +55,13 @@ export default function SearchPage(props: Props) {
isValid = false; isValid = false;
} }
const modifiedUrlQuery = isValid const modifiedUrlQuery =
? path isValid && path
: urlQuery ? path
.trim() : urlQuery
.replace(/\s+/g, '-') .trim()
.replace(INVALID_URI_CHARS, ''); .replace(/\s+/g, '-')
.replace(INVALID_URI_CHARS, '');
const uriFromQuery = `lbry://${modifiedUrlQuery}`; const uriFromQuery = `lbry://${modifiedUrlQuery}`;
useEffect(() => { useEffect(() => {
@ -85,6 +97,7 @@ export default function SearchPage(props: Props) {
uris={uris} uris={uris}
loading={isSearching} loading={isSearching}
header={<SearchOptions additionalOptions={additionalOptions} />} header={<SearchOptions additionalOptions={additionalOptions} />}
injectedItem={!isAuthenticated && IS_WEB && <Ads type="video" />}
headerAltControls={ headerAltControls={
<Fragment> <Fragment>
<span>{__('Find what you were looking for?')}</span> <span>{__('Find what you were looking for?')}</span>

View file

@ -14,3 +14,37 @@
color: var(--color-ads-link); color: var(--color-ads-link);
} }
} }
// Inline Video Ads
.ads__claim-item {
border-bottom: 1px solid var(--color-border);
padding: var(--spacing-medium);
background-color: var(--color-ads-background);
display: flex;
> div {
width: 50%;
position: relative !important;
}
.avp-p-gui {
z-index: 1 !important;
}
.pbs__player.pb-stream-sticky-on {
position: relative !important;
z-index: 1 !important;
margin-top: -140px !important;
}
}
.ads__claim-text {
display: flex;
flex-direction: column;
justify-content: center;
padding-left: var(--spacing-large);
}
.ads__claim-text--small {
font-size: var(--font-small);
}