Support callback method to inject items (1351)
This commit is contained in:
commit
805b607e73
6 changed files with 96 additions and 74 deletions
6
flow-typed/gui.js
vendored
Normal file
6
flow-typed/gui.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
declare type ListInjectedItem = {
|
||||||
|
node: Node | (index: number, lastVisibleIndex: ?number, pageSize: ?number) => Node,
|
||||||
|
index?: number,
|
||||||
|
replace?: boolean
|
||||||
|
};
|
|
@ -7,7 +7,7 @@ import ClaimPreview from 'component/claimPreview';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import useLastVisibleItem from 'effects/use-last-visible-item';
|
import useGetLastVisibleSlot from 'effects/use-get-last-visible-slot';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
import ClaimPreviewTile from 'component/claimPreviewTile';
|
import ClaimPreviewTile from 'component/claimPreviewTile';
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ type Props = {
|
||||||
renderActions?: (Claim) => ?Node,
|
renderActions?: (Claim) => ?Node,
|
||||||
renderProperties?: (Claim) => ?Node,
|
renderProperties?: (Claim) => ?Node,
|
||||||
includeSupportAction?: boolean,
|
includeSupportAction?: boolean,
|
||||||
injectedItem?: { node: Node, index?: number, replace?: boolean },
|
injectedItem?: ListInjectedItem,
|
||||||
timedOutMessage?: Node,
|
timedOutMessage?: Node,
|
||||||
tileLayout?: boolean,
|
tileLayout?: boolean,
|
||||||
searchInLanguage: boolean,
|
searchInLanguage: boolean,
|
||||||
|
@ -106,7 +106,8 @@ export default function ClaimList(props: Props) {
|
||||||
|
|
||||||
// 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();
|
||||||
const injectedIndex = useLastVisibleItem(injectedItem, listRef);
|
const findLastVisibleSlot = injectedItem && injectedItem.node && injectedItem.index === undefined;
|
||||||
|
const lastVisibleIndex = useGetLastVisibleSlot(listRef, !findLastVisibleSlot);
|
||||||
|
|
||||||
// Exclude prefix uris in these results variables. We don't want to show
|
// Exclude prefix uris in these results variables. We don't want to show
|
||||||
// anything if the search failed or timed out.
|
// anything if the search failed or timed out.
|
||||||
|
@ -205,9 +206,18 @@ export default function ClaimList(props: Props) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const getInjectedItem = (index) => {
|
const getInjectedItem = (index) => {
|
||||||
if (injectedItem && injectedItem.node && injectedIndex === index) {
|
if (injectedItem && injectedItem.node) {
|
||||||
return injectedItem.node;
|
if (typeof injectedItem.node === 'function') {
|
||||||
|
return injectedItem.node(index, lastVisibleIndex, pageSize);
|
||||||
|
} else {
|
||||||
|
if (injectedItem.index === undefined || injectedItem.index === null) {
|
||||||
|
return index === lastVisibleIndex ? injectedItem.node : null;
|
||||||
|
} else {
|
||||||
|
return index === injectedItem.index ? injectedItem.node : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ type Props = {
|
||||||
header?: Node,
|
header?: Node,
|
||||||
headerLabel?: string | Node,
|
headerLabel?: string | Node,
|
||||||
hiddenNsfwMessage?: Node,
|
hiddenNsfwMessage?: Node,
|
||||||
injectedItem?: { node: Node, index?: number, replace?: boolean },
|
injectedItem?: ListInjectedItem,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
subSection?: Node, // Additional section below [Header|Meta]
|
subSection?: Node, // Additional section below [Header|Meta]
|
||||||
renderProperties?: (Claim) => Node,
|
renderProperties?: (Claim) => Node,
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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';
|
||||||
import useFetchViewCount from 'effects/use-fetch-view-count';
|
import useFetchViewCount from 'effects/use-fetch-view-count';
|
||||||
import useLastVisibleItem from 'effects/use-last-visible-item';
|
import useGetLastVisibleSlot from 'effects/use-get-last-visible-slot';
|
||||||
import useResolvePins from 'effects/use-resolve-pins';
|
import useResolvePins from 'effects/use-resolve-pins';
|
||||||
import useGetUserMemberships from 'effects/use-get-user-memberships';
|
import useGetUserMemberships from 'effects/use-get-user-memberships';
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ type Props = {
|
||||||
prefixUris?: Array<string>,
|
prefixUris?: Array<string>,
|
||||||
pins?: { urls?: Array<string>, claimIds?: Array<string>, onlyPinForOrder?: string },
|
pins?: { urls?: Array<string>, claimIds?: Array<string>, onlyPinForOrder?: string },
|
||||||
uris: Array<string>,
|
uris: Array<string>,
|
||||||
injectedItem?: { node: Node, index?: number, replace?: boolean },
|
injectedItem?: ListInjectedItem,
|
||||||
showNoSourceClaims?: boolean,
|
showNoSourceClaims?: boolean,
|
||||||
renderProperties?: (Claim) => ?Node,
|
renderProperties?: (Claim) => ?Node,
|
||||||
fetchViewCount?: boolean,
|
fetchViewCount?: boolean,
|
||||||
|
@ -93,7 +93,8 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const listRef = React.useRef();
|
const listRef = React.useRef();
|
||||||
const injectedIndex = useLastVisibleItem(injectedItem, listRef);
|
const findLastVisibleSlot = injectedItem && injectedItem.node && injectedItem.index === undefined;
|
||||||
|
const lastVisibleIndex = useGetLastVisibleSlot(listRef, !findLastVisibleSlot);
|
||||||
|
|
||||||
const prevUris = React.useRef();
|
const prevUris = React.useRef();
|
||||||
const claimSearchUris = claimSearchResults || [];
|
const claimSearchUris = claimSearchResults || [];
|
||||||
|
@ -141,6 +142,22 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInjectedItem = (index) => {
|
||||||
|
if (injectedItem && injectedItem.node) {
|
||||||
|
if (typeof injectedItem.node === 'function') {
|
||||||
|
return injectedItem.node(index, lastVisibleIndex, pageSize);
|
||||||
|
} else {
|
||||||
|
if (injectedItem.index === undefined || injectedItem.index === null) {
|
||||||
|
return index === lastVisibleIndex ? injectedItem.node : null;
|
||||||
|
} else {
|
||||||
|
return index === injectedItem.index ? injectedItem.node : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -186,18 +203,17 @@ function ClaimTilesDiscover(props: Props) {
|
||||||
{finalUris && finalUris.length
|
{finalUris && finalUris.length
|
||||||
? finalUris.map((uri, i) => {
|
? finalUris.map((uri, i) => {
|
||||||
if (uri) {
|
if (uri) {
|
||||||
if (injectedIndex === i && injectedItem && injectedItem.replace) {
|
const inj = getInjectedItem(i);
|
||||||
return <React.Fragment key={uri}>{injectedItem.node}</React.Fragment>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={uri}>
|
<React.Fragment key={uri}>
|
||||||
{injectedIndex === i && injectedItem && injectedItem.node}
|
{inj && inj}
|
||||||
<ClaimPreviewTile
|
{(!inj || !injectedItem || !injectedItem.replace) && (
|
||||||
showNoSourceClaims={hasNoSource || showNoSourceClaims}
|
<ClaimPreviewTile
|
||||||
uri={uri}
|
showNoSourceClaims={hasNoSource || showNoSourceClaims}
|
||||||
properties={renderProperties}
|
uri={uri}
|
||||||
/>
|
properties={renderProperties}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
45
ui/effects/use-get-last-visible-slot.js
Normal file
45
ui/effects/use-get-last-visible-slot.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last visible slot in a list.
|
||||||
|
*
|
||||||
|
* @param listRef A reference to the list.
|
||||||
|
* @param skipEval Skip the evaluation/effect. Useful if the client knows the effect is not needed in certain scenarios.
|
||||||
|
* @param checkDelayMs Delay before running the effect. Useful if the list is known to not be populated until later.
|
||||||
|
* @returns {number | undefined} Zero-indexed number representing the last visible slot.
|
||||||
|
*/
|
||||||
|
export default function useGetLastVisibleSlot(listRef: any, skipEval: boolean, checkDelayMs: number = 1500) {
|
||||||
|
const [lastVisibleIndex, setLastVisibleIndex] = React.useState(undefined);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!skipEval) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (listRef.current) {
|
||||||
|
const screenBottom = window.innerHeight;
|
||||||
|
const items = listRef.current.children;
|
||||||
|
|
||||||
|
if (items.length) {
|
||||||
|
let i = 2; // Start from 2, so that the min possible is index-1
|
||||||
|
for (; i < items.length; ++i) {
|
||||||
|
const rect = items[i].getBoundingClientRect();
|
||||||
|
if (rect.top > screenBottom || rect.bottom > screenBottom) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLastVisibleIndex(i - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to index-1 (2nd item) for failures. No retries.
|
||||||
|
setLastVisibleIndex(1);
|
||||||
|
}, checkDelayMs);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return lastVisibleIndex;
|
||||||
|
}
|
|
@ -1,55 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from 'react';
|
|
||||||
import type { Node } from 'react';
|
|
||||||
|
|
||||||
type InjectedItem = { node: Node, index?: number, replace?: boolean };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index indicating where in the claim-grid to inject the ad element
|
|
||||||
* @param injectedItem
|
|
||||||
* @param listRef - A reference to the claim-grid
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
export default function useLastVisibleItem(injectedItem: ?InjectedItem, listRef: any) {
|
|
||||||
const [injectedIndex, setInjectedIndex] = React.useState(injectedItem?.index);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
// Move to default injection index (last visible item)
|
|
||||||
if (injectedItem && injectedItem.index === undefined) {
|
|
||||||
// AD_INJECTION_DELAY_MS = average total-blocking-time incurred for
|
|
||||||
// loading ads. Delay to let higher priority tasks run first. Ideally,
|
|
||||||
// should use 'requestIdleCallback/requestAnimationFrame'.
|
|
||||||
const AD_INJECTION_DELAY_MS = 1500;
|
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
if (listRef.current) {
|
|
||||||
const screenBottom = window.innerHeight;
|
|
||||||
|
|
||||||
// claim preview tiles
|
|
||||||
const items = listRef.current.children;
|
|
||||||
|
|
||||||
// algo to return index of item, where ad will be injected before it
|
|
||||||
if (items.length) {
|
|
||||||
let i = 2; // Start from 2, so that the min possible is index-1
|
|
||||||
for (; i < items.length; ++i) {
|
|
||||||
const rect = items[i].getBoundingClientRect();
|
|
||||||
if (rect.top > screenBottom || rect.bottom > screenBottom) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInjectedIndex(i - 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to index-1 (2nd item) for failures. No retries.
|
|
||||||
setInjectedIndex(1);
|
|
||||||
}, AD_INJECTION_DELAY_MS);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return injectedIndex;
|
|
||||||
}
|
|
Loading…
Reference in a new issue