UNPKG

react-native-wishlist

Version:
215 lines (191 loc) 6.71 kB
import { useMemo } from 'react'; import { useOnFlushCallback, useScheduleSyncUp } from './OrchestratorBinding'; import { useWishlistContext } from './WishlistContext'; export type Item = { key: string; }; export type UpdateJob<T extends Item> = (datacopy: DataCopy<T>) => unknown; export interface DataCopy<T extends Item> { getIndex: (key: string) => number | undefined; deque: Array<T>; at: (index: number) => T | undefined; length: () => number; set: (key: string, value: T) => void; setItem: (key: string, value: T) => void; get: (key: string) => T | undefined; forKey: (key: string) => T | undefined; setAt: (index: number, value: T) => void; push: (value: T) => void; unshift: (value: T) => void; applyChanges: (pendingUpdates: Array<UpdateJob<T>>) => Set<string>; isTrackingChanges: boolean; dirtyKeys: Set<string>; } export interface Data<T extends Item> { update: (job: UpdateJob<T>, callback?: (results: unknown) => void) => void; at: (index: number) => T | undefined; length: () => number; forKey: (key: string) => T | undefined; pendingUpdates: Array<UpdateJob<T>>; __currentlyRenderedCopy: DataCopy<T>; __nextCopy: DataCopy<T>; } export function useInternalWishlistData<T extends Item>( wishlistId: string, initialData: Array<T>, ) { const scheduleSyncUp = useScheduleSyncUp(wishlistId); const data = useMemo(() => { return () => { 'worklet'; if (!global.dataCtx) { global.dataCtx = {}; } if (!global.dataCtx[wishlistId]) { function createItemsDataStructure(initialData: Array<T>) { // classes doesn't work :( // TODO it can be implmented so that all ops are O(log n) const thiz: DataCopy<T> = { deque: initialData, getIndex: function getIndex(key: string) { // That's linear but can be log n (only for testing) for (let i = 0; i < this.deque.length; ++i) { if (this.deque[i].key === key) { return i; } } return undefined; }, at: function at(index: number) { if (index == undefined || index >= this.length() || index < 0) { return undefined; } return this.deque[index]; }, length: function length() { return this.deque.length; }, forKey: function forKey(key: string) { const index = this.getIndex(key); if (!index) { return undefined; } return this.at(index); }, get: function get(key: string) { return this.forKey(key); }, setItem: function setItem(key: string, value: T) { const index = this.getIndex(key); this.setAt(index!, value); }, set: function set(key: string, value: T) { return this.setItem(key, value); }, setAt: function setAt(index: number, value: T) { this.deque[index] = value; if (this.isTrackingChanges) { this.dirtyKeys.add(value.key); } }, push: function push(value: T) { this.deque.push(value); }, unshift: function unshift(value: T) { this.deque.unshift(value); }, applyChanges: function applyChanges(pendingUpdates) { this.isTrackingChanges = true; for (let updateJob of pendingUpdates) { updateJob(this); } this.isTrackingChanges = false; const res = this.dirtyKeys; this.dirtyKeys = new Set(); return res; }, dirtyKeys: new Set<string>(), isTrackingChanges: false, }; return thiz; } function deepClone<ObjT>(x: ObjT): ObjT { if ((x as any).map != null) { return (x as any).map((ele: unknown) => deepClone(ele)); } if (typeof x === 'object') { const res: any = {}; for (let key of Object.keys(x as any)) { res[key] = deepClone((x as any)[key]); } return res; } return x; } const initialDataCopy__next = deepClone(initialData); const initialDataCopy__cur = deepClone(initialData); const __nextCopy = createItemsDataStructure(initialDataCopy__next); const __currentlyRenderedCopy = createItemsDataStructure(initialDataCopy__cur); const pendingUpdates: Array<UpdateJob<T>> = []; function update( updateJob: UpdateJob<T>, callback?: (result: any) => void, ) { updateJob(__nextCopy); pendingUpdates.push((dataCopy: DataCopy<T>) => { const result = updateJob(dataCopy); callback?.(result); }); scheduleSyncUp(); } function at(index: number) { return __currentlyRenderedCopy.at(index); } function length() { return __currentlyRenderedCopy.length(); } function forKey(key: string) { return __currentlyRenderedCopy.forKey(key); } const internalData = { update, at, forKey, length, __currentlyRenderedCopy, __nextCopy, pendingUpdates, }; global.dataCtx[wishlistId] = internalData; } return global.dataCtx[wishlistId] as Data<T>; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useOnFlushCallback((viewportObserver) => { 'worklet'; const pendingUpdates = data().pendingUpdates; const pendingUpdatesCopy = pendingUpdates.splice(0, pendingUpdates.length); const dirty = data().__currentlyRenderedCopy.applyChanges(pendingUpdatesCopy); const window = viewportObserver.getAllVisibleItems(); // Right now we only support adding items but it can be easily extended const newIndex = data().__currentlyRenderedCopy.getIndex(window[0].key); viewportObserver.updateIndices(newIndex!); const dirtyItems = []; let i = 0; for (let item of window) { if (dirty.has(item.key)) { dirtyItems.push(i); } i++; } viewportObserver.markItemsDirty(dirtyItems); }, wishlistId); return data as () => Data<T>; } export function useData<T extends Item>() { const { data } = useWishlistContext(); return data as () => Data<T>; }