Show ads in feed

environments/review-ads-5t32bl/deployments/685
Alex Gleason 2022-08-01 22:43:28 -05:00
rodzic 6d1539cf9c
commit b02141874e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
4 zmienionych plików z 79 dodań i 6 usunięć

Wyświetl plik

@ -6,13 +6,17 @@ import { FormattedMessage } from 'react-intl';
import LoadGap from 'soapbox/components/load_gap';
import ScrollableList from 'soapbox/components/scrollable_list';
import StatusContainer from 'soapbox/containers/status_container';
import Ad from 'soapbox/features/ads/components/ad';
import FeedSuggestions from 'soapbox/features/feed-suggestions/feed-suggestions';
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
import PendingStatus from 'soapbox/features/ui/components/pending_status';
import { useSoapboxConfig } from 'soapbox/hooks';
import useAds from 'soapbox/queries/ads';
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
import type { VirtuosoHandle } from 'react-virtuoso';
import type { IScrollableList } from 'soapbox/components/scrollable_list';
import type { Ad as AdEntity } from 'soapbox/features/ads/providers';
interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
/** Unique key to preserve the scroll position when navigating back. */
@ -37,6 +41,8 @@ interface IStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children'> {
timelineId?: string,
/** Whether to display a gap or border between statuses in the list. */
divideType?: 'space' | 'border',
/** Whether to display ads. */
showAds?: boolean,
}
/** Feed of statuses, built atop ScrollableList. */
@ -49,8 +55,12 @@ const StatusList: React.FC<IStatusList> = ({
timelineId,
isLoading,
isPartial,
showAds = false,
...other
}) => {
const { data: ads } = useAds();
const soapboxConfig = useSoapboxConfig();
const adsInterval = Number(soapboxConfig.extensions.getIn(['ads', 'interval'], 40)) || 0;
const node = useRef<VirtuosoHandle>(null);
const getFeaturedStatusCount = () => {
@ -123,6 +133,15 @@ const StatusList: React.FC<IStatusList> = ({
);
};
const renderAd = (ad: AdEntity) => {
return (
<Ad
card={ad.card}
impression={ad.impression}
/>
);
};
const renderPendingStatus = (statusId: string) => {
const idempotencyKey = statusId.replace(/^末pending-/, '');
@ -156,17 +175,27 @@ const StatusList: React.FC<IStatusList> = ({
const renderStatuses = (): React.ReactNode[] => {
if (isLoading || statusIds.size > 0) {
return statusIds.toArray().map((statusId, index) => {
return statusIds.toList().reduce((acc, statusId, index) => {
const adIndex = ads ? Math.floor((index + 1) / adsInterval) % ads.length : 0;
const ad = ads ? ads[adIndex] : undefined;
const showAd = (index + 1) % adsInterval === 0;
if (statusId === null) {
return renderLoadGap(index);
acc.push(renderLoadGap(index));
} else if (statusId.startsWith('末suggestions-')) {
return renderFeedSuggestions();
acc.push(renderFeedSuggestions());
} else if (statusId.startsWith('末pending-')) {
return renderPendingStatus(statusId);
acc.push(renderPendingStatus(statusId));
} else {
return renderStatus(statusId);
acc.push(renderStatus(statusId));
}
});
if (showAds && ad && showAd) {
acc.push(renderAd(ad));
}
return acc;
}, [] as React.ReactNode[]);
} else {
return [];
}

Wyświetl plik

@ -1,6 +1,13 @@
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import type { RootState } from 'soapbox/store';
import type { Card } from 'soapbox/types/entities';
/** Map of available provider modules. */
const PROVIDERS: Record<string, () => Promise<AdProvider>> = {
soapbox: async() => (await import(/* webpackChunkName: "features/ads/soapbox" */'./soapbox-config')).default,
};
/** Ad server implementation. */
interface AdProvider {
getAds(getState: () => RootState): Promise<Ad[]>,
@ -14,4 +21,17 @@ interface Ad {
impression?: string,
}
/** Gets the current provider based on config. */
const getProvider = async(getState: () => RootState): Promise<AdProvider | undefined> => {
const state = getState();
const soapboxConfig = getSoapboxConfig(state);
const isEnabled = soapboxConfig.extensions.getIn(['ads', 'enabled'], false) === true;
const providerName = soapboxConfig.extensions.getIn(['ads', 'provider'], 'soapbox') as string;
if (isEnabled && PROVIDERS[providerName]) {
return PROVIDERS[providerName]();
}
};
export { getProvider };
export type { Ad, AdProvider };

Wyświetl plik

@ -90,6 +90,7 @@ const HomeTimeline: React.FC = () => {
onLoadMore={handleLoadMore}
timelineId='home'
divideType='space'
showAds
emptyMessage={
<Stack space={1}>
<Text size='xl' weight='medium' align='center'>

Wyświetl plik

@ -0,0 +1,23 @@
import { useQuery } from '@tanstack/react-query';
import { Ad, getProvider } from 'soapbox/features/ads/providers';
import { useAppDispatch } from 'soapbox/hooks';
export default function useAds() {
const dispatch = useAppDispatch();
const getAds = async() => {
return dispatch(async(_, getState) => {
const provider = await getProvider(getState);
if (provider) {
return provider.getAds(getState);
} else {
return [];
}
});
};
return useQuery<Ad[]>(['ads'], getAds, {
placeholderData: [],
});
}