/// /// import React, { useEffect, useMemo } from "react"; import * as R from "ramda"; import { Platform } from "react-native"; import Url from "url"; import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites"; import { getInflatedDataSourceUrl, getSearchContext, useFeedLoader, useFeedRefresh, useRoute, } from "@applicaster/zapp-react-native-utils/reactHooks"; import { ComponentDataSourceContext, ZappPipesDataProps } from "../types"; import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore"; import { isVerticalListOrGrid } from "../utils"; import { useFilter } from "../utils/useFilter"; import { subscribeForKeyChanges } from "@applicaster/zapp-pipes-v2-client"; const FAVORITES_TYPE = "FAVOURITES"; function getDataSourceUrl({ component, feedUrl, screenData, entryContext, searchContext, screenContext, }): { dataSourceUrl: string | null; type: string | null } { if (feedUrl) { return { dataSourceUrl: feedUrl, type: null }; } if (component.data) { const { data: { source, type, mapping }, } = component; if (type === FAVORITES_TYPE) { return { dataSourceUrl: source || FAVORITES_TYPE, type }; } if (source) { if (type === "APPLICASTER_COLLECTION" && !R.includes("://", source)) { return { dataSourceUrl: `applicaster://fetchData?type=${type}&collectionId=${source}`, type, }; } if (type === "APPLICASTER_CATEGORY" && !R.includes("://", source)) { return { dataSourceUrl: `applicaster://fetchData?type=${type}&categoryId=${source}&platform=${Platform.OS}`, type, }; } if (mapping) { const contexts = { entry: entryContext, screen: screenContext || screenData, search: getSearchContext(searchContext, mapping), }; const dataSourceUrl = getInflatedDataSourceUrl({ source, mapping, contexts, }); if (!dataSourceUrl) { return { dataSourceUrl: null, type: null }; } return { dataSourceUrl, type }; } return { dataSourceUrl: source, type }; } } return { dataSourceUrl: null, type: null }; } function getMethodAndParams(component) { const method: "get" | "post" = R.pathOr("get", ["data", "method"], component); const bodyParams: Record = R.pathOr( {}, ["data", "bodyParams"], component ); return { method, bodyParams, }; } function getListenerFromPlugin( dataSourceUrl: string, plugins: QuickBrickPlugin[] ): AddDataSourceListener | void { const url = Url.parse(dataSourceUrl, false); const addListener = R.compose( R.path(["module", "addDataSourceListener"]), R.find(R.propEq("identifier", url.host)) )(plugins); return addListener; } function applyItemLimit(zappPipesData, component) { if (!zappPipesData || !zappPipesData.data) { return zappPipesData; } const { data } = zappPipesData; const { rules: { item_limit }, } = component; if (item_limit && data.entry && data.entry.length) { return { ...zappPipesData, data: { ...data, entry: R.slice( 0, Math.min(Number(item_limit), R.length(data.entry)), data.entry ), }, }; } return zappPipesData; } type UrlFeedResolverProps = ComponentDataSourceContext & { children: (dataProps: ZappPipesDataProps) => React.ReactNode; }; export function UrlFeedResolver({ children, component, feedUrl, isLast, entryContext, screenContext, searchContext, screenData, plugins, }: UrlFeedResolverProps) { const { dataSourceUrl, type } = useMemo( () => getDataSourceUrl({ component, feedUrl, screenData, entryContext, searchContext, screenContext, }), [ component?.id, feedUrl, entryContext, searchContext, screenContext, screenData?.id, ] ); const shouldClearCache = useMemo( () => Boolean(component?.rules?.clear_cache_on_reload), [component?.id] ); const pipesOptions = useMemo( () => ({ clearCache: type === FAVORITES_TYPE, loadLocalFavorites: type === FAVORITES_TYPE, silentRefresh: !shouldClearCache, ...getMethodAndParams(component), }), [type, shouldClearCache, component] ); const { reloadData, loadNext, error, loading, url, data } = useFeedLoader({ feedUrl: dataSourceUrl ?? "", pipesOptions, }); const { pathname } = useRoute(); const screenStateStore = useScreenStateStore(); // Setup listeners for data source URL useEffect(() => { if (!reloadData) { return; } if (dataSourceUrl?.includes("pipesv2://")) { ( getListenerFromPlugin(dataSourceUrl, plugins) as AddDataSourceListener )?.(reloadData); } else { return subscribeForKeyChanges({ url: dataSourceUrl, pathname, screenStateStore, callback: reloadData, }); } }, [dataSourceUrl, reloadData, pathname, screenStateStore]); // Setup favorites listener useEffect(() => { if (type === FAVORITES_TYPE && reloadData) { favoritesListener.on("FAVORITES_CHANGED", reloadData); return () => { favoritesListener.removeHandler("FAVORITES_CHANGED", reloadData); }; } }, [type, reloadData]); // Apply feed refresh hook useFeedRefresh({ reloadData, component, }); const loadNextData = useMemo( () => (!isLast && isVerticalListOrGrid(component) ? undefined : loadNext), [isLast, component, loadNext] ); const applyItemFilter = useFilter({ url, loading, data, error }, component); const zappPipesDataProps = useMemo(() => { const pipeData = { url, loading, data, error }; return { zappPipesData: applyItemLimit(applyItemFilter(pipeData), component), reloadData, loadNextData, }; }, [ url, loading, data, error, component, reloadData, loadNextData, applyItemFilter, ]); return <>{children(zappPipesDataProps)}; }