@applicaster/zapp-react-native-ui-components
Version:
Applicaster Zapp React Native ui components for the Quick Brick App
218 lines (173 loc) • 5.83 kB
text/typescript
import { all, equals, isEmpty, path, pluck, prop, values } from "ramda";
import { useEffect, useMemo } from "react";
import {
useLayoutPresets,
useZappPipesFeed,
} from "@applicaster/zapp-react-native-redux";
import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
import { Categories } from "./logger";
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
import {
ZappPipesScreenContext,
ZappPipesSearchContext,
} from "@applicaster/zapp-react-native-ui-components/Contexts";
import {
getInflatedDataSourceUrl,
getSearchContext,
} from "@applicaster/zapp-react-native-utils/reactHooks/feed/useInflatedUrl";
import { produce } from "immer";
import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
// types reference
declare interface CurationEntry {
preset_name: string;
feed_url: string;
}
type Feeds = Record<string, ZappPipesData>;
type LayoutPresets = PresetsMapping["presets_mappings"];
const TABS_SCREEN_TYPE = "tabs_screen";
const QB_TABS_SCREEN_TYPE = "quick-brick-tabs";
const SMART_COMPONENT_TYPE = "quick-brick-smart-component";
const SOURCE_PATH = ["data", "source"];
const MAPPING_PATH = ["data", "mapping"];
const isSmartComponent = (component) =>
component.component_type === SMART_COMPONENT_TYPE;
const logger = createLogger({ category: Categories.CURATION_API });
export const getTransformedPreset = (
preset: CurationEntry,
componentId: ZappUIComponent["id"],
index: number,
layoutPresets: LayoutPresets
): ZappUIComponent | undefined => {
const presetComponent = layoutPresets?.[preset?.preset_name];
if (!presetComponent) {
logger.log_error(
`Preset "${preset?.preset_name}" missing or wrong data format`,
{ entry: preset }
);
return;
}
const updatedPresetComponent = produce(presetComponent, (draft) => {
draft.id = `${componentId}-${preset?.feed_url}-${index}`;
draft.data.source = preset?.feed_url;
if (draft.ui_components) {
draft.ui_components.forEach((comp, uiIndex) => {
comp.id = `${draft.id}-${uiIndex}`;
});
}
});
return updatedPresetComponent;
};
export const enrichComponent = (
comp: ZappUIComponent,
index: number,
feeds: Feeds,
layoutPresets: LayoutPresets,
urlsMap: { [key: string]: string }
): ZappUIComponent[] | null => {
if (!isSmartComponent(comp)) {
return [comp];
}
const presets = prop<string[]>(urlsMap[comp.id], feeds);
if (presets && presets.data && presets.data.entry) {
return presets.data.entry
.map((preset: CurationEntry, _index) =>
getTransformedPreset(preset, comp.id, index + _index, layoutPresets)
)
.filter(Boolean);
}
return null;
};
export const getFinalComponents = (
enrichedComponents: Array<ZappUIComponent>,
smartComponents: ZappUIComponent[],
urls: string[],
feeds: Feeds,
components: ZappUIComponent[]
): ZappUIComponent[] => {
if (isEmpty(smartComponents)) {
return components;
}
if (all(isEmptyOrNil)(urls)) {
return enrichedComponents;
}
if (isEmptyOrNil(feeds)) {
return [];
}
if (all(equals(false))(values(pluck("loading", feeds)))) {
return enrichedComponents;
} else {
return [];
}
};
export const useCurationAPI = (
components: Array<ZappUIComponent>
): ZappUIComponent[] => {
const smartComponents = useMemo(
() => components?.filter?.(isSmartComponent) ?? [],
[components]
);
const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
const [screenContext] = ZappPipesScreenContext.useZappPipesContext();
const screenContextType = screenContext?.type;
const isNestedScreen =
screenContextType === TABS_SCREEN_TYPE ||
screenContextType === QB_TABS_SCREEN_TYPE;
const screenContextData = useScreenContext();
const entryContext = ((isNestedScreen && screenContextData?.nested?.entry
? screenContextData?.nested?.entry
: (screenContextData?.entry?.payload ?? screenContextData?.entry)) ||
{}) as ZappEntry;
const urlsMap = useMemo<{ [key: string]: string }>(() => {
const map = {};
smartComponents?.forEach?.((component) => {
const url = path(SOURCE_PATH, component);
const mapping = path(MAPPING_PATH, component);
map[component.id] = getInflatedDataSourceUrl({
source: url,
contexts: {
entry: entryContext,
screen: screenContext,
search: getSearchContext(searchContext, mapping),
},
mapping,
});
});
return map;
}, [smartComponents, entryContext, screenContext, searchContext]);
const urls = useMemo<string[]>(() => Object.values(urlsMap), [urlsMap]);
const loadPipesDataDispatcher = useLoadPipesDataDispatch();
useEffect(() => {
urls.forEach((url, index) => {
if (url) {
loadPipesDataDispatcher(url, { clearCache: false });
} else {
logger.log_error("Curation url is empty", {
componentId: smartComponents?.[index]?.id,
});
}
});
}, [urls, loadPipesDataDispatcher]);
const feeds = useZappPipesFeed(urls);
const layoutPresets = useLayoutPresets();
const enrichedComponents = useMemo(() => {
if (!components) return [];
return components.reduce((acc, comp, index) => {
const enrichedComp = enrichComponent(
comp,
index,
feeds,
layoutPresets,
urlsMap
);
return enrichedComp ? [...acc, ...enrichedComp] : acc;
}, [] as Array<ZappUIComponent>);
}, [components, feeds, layoutPresets, urlsMap]);
return getFinalComponents(
enrichedComponents,
smartComponents,
urls,
feeds,
components
);
};