diff --git a/components/GenericFlipList/GenericFlipList.module.css b/components/GenericFlipList/GenericFlipList.module.css index 859a71f36..5b77202cf 100644 --- a/components/GenericFlipList/GenericFlipList.module.css +++ b/components/GenericFlipList/GenericFlipList.module.css @@ -18,6 +18,7 @@ width: calc(100% - 10px); height: auto; padding-bottom: 15px; + line-height: 1.8; } @media all and (min-width: 768px) { diff --git a/components/GenericFlipList/GenericFlipList.tsx b/components/GenericFlipList/GenericFlipList.tsx index 23cc7c8f7..2f2c47af6 100644 --- a/components/GenericFlipList/GenericFlipList.tsx +++ b/components/GenericFlipList/GenericFlipList.tsx @@ -8,6 +8,8 @@ import GoogleSignIn from '../GoogleSignIn/GoogleSignIn' import api from '../../api/ApiHelper' import styles from './GenericFlipList.module.css' import { useSortedAndFilteredItems } from '../../hooks/useSortedAndFilteredItems' +import ListItemAdElement from '../ListItemAdElement/ListItemAdElement' +import { GENRIC_FLIP_LIST_COLUMNS, getSetting, setSetting } from '../../utils/SettingsUtils' export interface FlipListProps { items: T[] @@ -66,8 +68,10 @@ export function GenericFlipList({ const [hasPremium, setHasPremium] = useState(false) const [isLoggedIn, setIsLoggedIn] = useState(false) const [showTechSavvyMessage, setShowTechSavvyMessage] = useState(false) - const [columns, setColumns] = useState() + const [columns, _setColumns] = useState() const [showPremiumModal, setShowPremiumModal] = useState(false) + const [listElementSizes, setListElementSizes] = useState<{ width: number; height: number }>() + const listRef = React.useRef(null) const { processedItems, isProcessing } = useSortedAndFilteredItems(items, orderBy, nameFilter, minimumProfit, filterFunction, sortFunctionArgs) @@ -80,11 +84,17 @@ export function GenericFlipList({ useEffect(() => { setTimeout(setBlurObserver, 100) if (showColumns) { - setColumns(getDefaultColumns()) + let columns = parseInt(getSetting(GENRIC_FLIP_LIST_COLUMNS, getDefaultColumns().toString())) + setColumns(isNaN(columns) ? getDefaultColumns() : columns.valueOf()) } setRenderedCount(Math.max(3, safeInitial)) }, []) + function setColumns(value: number) { + _setColumns(value) + setSetting(GENRIC_FLIP_LIST_COLUMNS, value) + } + // Observe the sentinel to incrementally render more items when the user // scrolls near the end of the currently rendered batch. useEffect(() => { @@ -101,6 +111,14 @@ export function GenericFlipList({ return () => observer.disconnect() }, [sentinelRef.current, processedItems.length, batchSize]) + useEffect(() => { + if (listRef.current && listRef.current.children) { + let height = listRef.current.children[0]?.clientHeight - 15 || 0 + let width = listRef.current.children[0]?.clientWidth - 15 || 0 + setListElementSizes({ width: width, height: height }) + } + }, [listRef.current, columns, showColumns]) + function setBlurObserver() { if (observer) { observer.disconnect() @@ -144,6 +162,19 @@ export function GenericFlipList({ } } + function getAdSizes() { + let sizes: [number, number][] = [[300, 250], [336, 280], [320, 100], [970, 90], [728, 90], [970, 250]] + if (listElementSizes) { + // Filter ad sizes to not exceed list element width + // Height can be up to 20% higher than list elements + sizes = sizes.filter(size => + size[0] <= listElementSizes.width && + size[1] <= listElementSizes.height * 1.5 + ) + } + return sizes; + } + const onNameFilterChange = useCallback((e: any) => { setNameFilter(e.target.value) }, []) @@ -302,37 +333,57 @@ export function GenericFlipList({ let shown = 0 // Only render up to renderedCount to reduce DOM size const toRender = processedItems.slice(0, renderedCount) - const list = toRender.map(item => { + const list: React.ReactNode[] = [] + toRender.forEach((item, i) => { + + if ((list.length + 1) % 12 === 0 || (!hasPremium && i === 1)) { + let ad: React.ReactNode = null; + if (listElementSizes) { + ad =
+ +
+ } else { + ad =
+ {getListElement(item, true)} +
+ } + list.push(ad) + } + const defaultContent = getListElement(item, false) - if (!hasPremium && ++shown <= 3) { + if (!hasPremium && ++shown <= 2) { const censoredItem = censoredItemGenerator ? censoredItemGenerator(item) : item const censoredContent = getListElement(censoredItem, true) if (customItemWrapper) { - return customItemWrapper(censoredItem, true, getItemKeyAction(item), censoredContent, styles.flipCard) + list.push(customItemWrapper(censoredItem, true, getItemKeyAction(item), censoredContent, styles.flipCard)); + return; } - return ( + list.push(
{censoredContent}
) + return; } else { if (customItemWrapper) { - return customItemWrapper(item, false, getItemKeyAction(item), defaultContent, styles.flipCard) + list.push(customItemWrapper(item, false, getItemKeyAction(item), defaultContent, styles.flipCard)); + return; } - return ( + list.push(
{defaultContent}
) + return; } }) return list - }, [processedItems, hasPremium, isProcessing, censoredItemGenerator, customItemWrapper, renderedCount]) + }, [processedItems, hasPremium, isProcessing, censoredItemGenerator, customItemWrapper, renderedCount, listElementSizes]) const flipListClass = showColumns && columns ? `${styles.flipList} ${styles[`columns-${columns}`]}` : styles.flipList return ( @@ -391,7 +442,7 @@ export function GenericFlipList({ const insertIndex = Math.max(0, visibleList.length - 6) visibleList.splice(insertIndex, 0,
) } - return {visibleList} + return {visibleList} })()} )} diff --git a/components/ListItemAdElement/ListItemAdElement.module.css b/components/ListItemAdElement/ListItemAdElement.module.css new file mode 100644 index 000000000..d4dc3dbc1 --- /dev/null +++ b/components/ListItemAdElement/ListItemAdElement.module.css @@ -0,0 +1,3 @@ +.adStyle { + min-height: auto !important; +} \ No newline at end of file diff --git a/components/ListItemAdElement/ListItemAdElement.tsx b/components/ListItemAdElement/ListItemAdElement.tsx new file mode 100644 index 000000000..ec0d962c8 --- /dev/null +++ b/components/ListItemAdElement/ListItemAdElement.tsx @@ -0,0 +1,28 @@ +'use client' + +import NitroAdSlot from '../Ads/NitroAdSlot' + +interface ListItemAdElementProps { + slotId: string, + sizes: [number, number][] +} + +export default function ListItemAdElement(props: ListItemAdElementProps) { + + return ( + + ) +} diff --git a/hooks/useNitroAds.ts b/hooks/useNitroAds.ts index eded80d3f..3aef77ad8 100644 --- a/hooks/useNitroAds.ts +++ b/hooks/useNitroAds.ts @@ -37,7 +37,7 @@ export function useNitroAds(slotId: string, config: Record, enabled console.warn('Failed to remove NitroAd via API:', e) } } - + // This prevents React from trying to manipulate nodes that may have been modified by ad scripts try { const adContainer = document.getElementById(slotId) @@ -64,7 +64,7 @@ export function useNitroAds(slotId: string, config: Record, enabled } catch (e) { console.debug('Ad container cleanup skipped (already removed):', slotId) } - + hasRequestedRef.current = false }, [slotId]) @@ -105,7 +105,7 @@ export function useNitroAds(slotId: string, config: Record, enabled if (cancelled) return const created = createAd() - + if (!created && attempt < maxAttempts) { attempt++ const delay = Math.min(500, 100 + attempt * 50) diff --git a/utils/SettingsUtils.tsx b/utils/SettingsUtils.tsx index d842d0dec..352d41069 100644 --- a/utils/SettingsUtils.tsx +++ b/utils/SettingsUtils.tsx @@ -525,3 +525,4 @@ export const ITEM_FILER_SHOW_ADVANCED = 'itemFilterShowAdvanced' export const AUTO_REDIRECT_FROM_LINKVERTISE_EXPLANATION = 'autoRedirectFromLinkvertiseExplanation' export const ITEM_ICON_TYPE = 'itemIconType' export const ITEM_FAVORITES_KEY = 'favoriteItems' +export const GENRIC_FLIP_LIST_COLUMNS = 'genericFlipListColumns'