1af8fd012Sandyimport * as React from 'react'; 2*43fd9ed3Sandyimport { Animated } from 'react-native'; 3af8fd012Sandy 4*43fd9ed3Sandyexport type StackAction = 'pushstart' | 'pushend' | 'popstart' | 'popend'; 5*43fd9ed3Sandyexport type Status = 'pushing' | 'popping' | 'settled' | 'popped'; 6af8fd012Sandy 7*43fd9ed3Sandyexport type StackItemComponent<T = any> = React.JSXElementConstructor<T>; 8af8fd012Sandy 9*43fd9ed3Sandyexport type StackEvent<T = any> = { 10*43fd9ed3Sandy state: StackState<T>; 11*43fd9ed3Sandy event: { 12*43fd9ed3Sandy action: StackAction; 13af8fd012Sandy key: string; 14af8fd012Sandy }; 15*43fd9ed3Sandy}; 16af8fd012Sandy 17*43fd9ed3Sandyconst AValue = new Animated.Value(0); 18af8fd012Sandy 19*43fd9ed3Sandyexport type StackItem<T = any> = { 20*43fd9ed3Sandy key: string; 21*43fd9ed3Sandy status: Status; 22*43fd9ed3Sandy promise: Promise<StackItem<T>>; 23*43fd9ed3Sandy pop: () => void; 24*43fd9ed3Sandy onPushEnd: () => void; 25*43fd9ed3Sandy onPopEnd: () => void; 26*43fd9ed3Sandy data: T; 27*43fd9ed3Sandy animatedValue: typeof AValue; 28*43fd9ed3Sandy}; 29*43fd9ed3Sandy 30*43fd9ed3Sandyexport type StackState<T = any> = { 31*43fd9ed3Sandy items: StackItem<T>[]; 32*43fd9ed3Sandy lookup: Record<string, StackItem<T>>; 33*43fd9ed3Sandy getItemByKey: (key: string) => StackItem<T> | null; 34*43fd9ed3Sandy}; 35*43fd9ed3Sandy 36*43fd9ed3Sandyexport type Stack<T> = { 37*43fd9ed3Sandy push: (data?: T | undefined) => StackItem<T>; 38*43fd9ed3Sandy pop: (amount?: number, startIndex?: number) => StackItem<any>[]; 39*43fd9ed3Sandy subscribe: (listener: (state: StackEvent<T>) => void) => () => void; 40*43fd9ed3Sandy getState: () => StackState; 41*43fd9ed3Sandy}; 42*43fd9ed3Sandy 43*43fd9ed3Sandyexport function createAsyncStack<T = any>() { 44af8fd012Sandy let keys: string[] = []; 45af8fd012Sandy const lookup: Record<string, StackItem<T>> = {}; 46*43fd9ed3Sandy let count = 0; 47af8fd012Sandy 48*43fd9ed3Sandy const pushResolvers: Record<string, any> = {}; 49af8fd012Sandy const popResolvers: Record<string, Function> = {}; 50af8fd012Sandy 51af8fd012Sandy let listeners: any[] = []; 52af8fd012Sandy 53*43fd9ed3Sandy function push(data?: T) { 54*43fd9ed3Sandy count += 1; 55*43fd9ed3Sandy const key = '' + count; 56af8fd012Sandy 57af8fd012Sandy keys.push(key); 58af8fd012Sandy 59*43fd9ed3Sandy const promise = new Promise<StackItem<T>>((resolve) => { 60af8fd012Sandy pushResolvers[key] = resolve; 61af8fd012Sandy }); 62af8fd012Sandy 63*43fd9ed3Sandy const item: StackItem<T> = { 64*43fd9ed3Sandy key, 65*43fd9ed3Sandy promise, 66*43fd9ed3Sandy // @ts-ignore 67*43fd9ed3Sandy data, 68*43fd9ed3Sandy status: 'pushing' as Status, 69*43fd9ed3Sandy pop: () => pop(`${key}`), 70*43fd9ed3Sandy onPushEnd: () => onPushEnd(key), 71*43fd9ed3Sandy onPopEnd: () => onPopEnd(key), 72*43fd9ed3Sandy animatedValue: new Animated.Value(0), 73*43fd9ed3Sandy }; 74*43fd9ed3Sandy 75*43fd9ed3Sandy if (data) { 76*43fd9ed3Sandy item.data = data; 77*43fd9ed3Sandy } 78*43fd9ed3Sandy 79*43fd9ed3Sandy lookup[key] = item; 80*43fd9ed3Sandy 81af8fd012Sandy emit('pushstart', key); 82af8fd012Sandy 83*43fd9ed3Sandy return item; 84af8fd012Sandy } 85af8fd012Sandy 86af8fd012Sandy function onPushEnd(key: string) { 87af8fd012Sandy const item = lookup[key]; 88af8fd012Sandy 89980c55c4Sandy if (item.status === 'pushing') { 90af8fd012Sandy item.status = 'settled'; 91af8fd012Sandy 92af8fd012Sandy const resolver = pushResolvers[key]; 93af8fd012Sandy 94af8fd012Sandy if (resolver) { 95*43fd9ed3Sandy resolver(getItemByKey(key)); 96*43fd9ed3Sandy delete pushResolvers[key]; 97af8fd012Sandy } 98af8fd012Sandy 99*43fd9ed3Sandy emit('pushend', key); 100*43fd9ed3Sandy } 101*43fd9ed3Sandy 102*43fd9ed3Sandy return item; 103*43fd9ed3Sandy } 104*43fd9ed3Sandy 105*43fd9ed3Sandy function pop(amount: number | string = 1) { 106*43fd9ed3Sandy const items: StackItem[] = []; 107*43fd9ed3Sandy 108*43fd9ed3Sandy if (typeof amount === 'string') { 109*43fd9ed3Sandy const key = amount; 110*43fd9ed3Sandy const item = lookup[key]; 111*43fd9ed3Sandy 112*43fd9ed3Sandy if (item) { 113*43fd9ed3Sandy if (item.status === 'pushing') { 114*43fd9ed3Sandy onPushEnd(key); 115*43fd9ed3Sandy } 116*43fd9ed3Sandy 117*43fd9ed3Sandy item.status = 'popping'; 118*43fd9ed3Sandy 119*43fd9ed3Sandy const promise = new Promise<StackItem<T>>((resolve) => { 120*43fd9ed3Sandy popResolvers[key] = resolve; 121*43fd9ed3Sandy }); 122*43fd9ed3Sandy 123*43fd9ed3Sandy item.promise = promise; 124*43fd9ed3Sandy 125*43fd9ed3Sandy emit('popstart', key); 126*43fd9ed3Sandy items.push(item); 127*43fd9ed3Sandy } 128*43fd9ed3Sandy 129*43fd9ed3Sandy return items; 130*43fd9ed3Sandy } 131af8fd012Sandy 132af8fd012Sandy if (amount === -1) { 133af8fd012Sandy // pop them all 134af8fd012Sandy amount = keys.length; 135af8fd012Sandy } 136af8fd012Sandy 137*43fd9ed3Sandy let startIndex = keys.length - 1; 138*43fd9ed3Sandy 139*43fd9ed3Sandy for (let i = keys.length - 1; i >= 0; i--) { 140*43fd9ed3Sandy const key = keys[i]; 141*43fd9ed3Sandy const item = lookup[key]; 142*43fd9ed3Sandy 143*43fd9ed3Sandy if (item && (item.status === 'settled' || item.status === 'pushing')) { 144*43fd9ed3Sandy startIndex = i; 145*43fd9ed3Sandy break; 146*43fd9ed3Sandy } 147*43fd9ed3Sandy } 148*43fd9ed3Sandy 149*43fd9ed3Sandy for (let i = startIndex; i > startIndex - amount; i--) { 150*43fd9ed3Sandy const key = keys[i]; 151af8fd012Sandy const item = lookup[key]; 152af8fd012Sandy 153af8fd012Sandy if (item) { 154*43fd9ed3Sandy if (item.status === 'pushing') { 155*43fd9ed3Sandy onPushEnd(key); 156*43fd9ed3Sandy } 157*43fd9ed3Sandy 158af8fd012Sandy item.status = 'popping'; 159af8fd012Sandy 160*43fd9ed3Sandy const promise = new Promise<StackItem<T>>((resolve) => { 161af8fd012Sandy popResolvers[key] = resolve; 162af8fd012Sandy }); 163af8fd012Sandy 164*43fd9ed3Sandy item.promise = promise; 165*43fd9ed3Sandy 166af8fd012Sandy emit('popstart', key); 167*43fd9ed3Sandy items.push(item); 168af8fd012Sandy } 169af8fd012Sandy } 170af8fd012Sandy 171*43fd9ed3Sandy return items; 172af8fd012Sandy } 173af8fd012Sandy 174af8fd012Sandy function onPopEnd(key: string) { 175*43fd9ed3Sandy const item = lookup[key]; 176af8fd012Sandy keys = keys.filter((k) => k !== key); 177af8fd012Sandy 178af8fd012Sandy const resolver = popResolvers[key]; 179af8fd012Sandy 180af8fd012Sandy if (resolver) { 181*43fd9ed3Sandy resolver(getItemByKey(key)); 182af8fd012Sandy delete popResolvers[key]; 183*43fd9ed3Sandy } 184af8fd012Sandy 185*43fd9ed3Sandy item.status = 'popped'; 186af8fd012Sandy emit('popend', key); 187*43fd9ed3Sandy 188*43fd9ed3Sandy return item; 189af8fd012Sandy } 190af8fd012Sandy 191*43fd9ed3Sandy function subscribe(listener: (state: StackEvent<T>) => void) { 192af8fd012Sandy listeners.push(listener); 193af8fd012Sandy 194af8fd012Sandy return () => { 195af8fd012Sandy listeners = listeners.filter((l) => l !== listener); 196af8fd012Sandy }; 197af8fd012Sandy } 198af8fd012Sandy 199*43fd9ed3Sandy function emit(action: StackAction, key: string) { 200af8fd012Sandy listeners.forEach((listener) => { 201af8fd012Sandy const state = getState(); 202*43fd9ed3Sandy const event = { key, action }; 203*43fd9ed3Sandy listener({ state, event }); 204af8fd012Sandy }); 205af8fd012Sandy } 206af8fd012Sandy 207af8fd012Sandy function getItemByKey(key: string) { 208af8fd012Sandy return lookup[key]; 209af8fd012Sandy } 210af8fd012Sandy 211*43fd9ed3Sandy function getState(): StackState { 212af8fd012Sandy const items = keys.map((key) => lookup[key]); 213af8fd012Sandy 214af8fd012Sandy return { 215af8fd012Sandy items, 216*43fd9ed3Sandy lookup, 217af8fd012Sandy getItemByKey, 218af8fd012Sandy }; 219af8fd012Sandy } 220af8fd012Sandy 221af8fd012Sandy return { 222af8fd012Sandy push, 223af8fd012Sandy pop, 224af8fd012Sandy subscribe, 225af8fd012Sandy getState, 226af8fd012Sandy }; 227af8fd012Sandy} 228af8fd012Sandy 229*43fd9ed3Sandyexport function useStackItems<T>(stack: Stack<T>) { 230*43fd9ed3Sandy const [items, setItems] = React.useState<StackItem<T>[]>(stack.getState().items); 231af8fd012Sandy 232af8fd012Sandy React.useEffect(() => { 233*43fd9ed3Sandy const unsubscribe = stack.subscribe(({ state }) => { 234*43fd9ed3Sandy setItems(state.items); 235af8fd012Sandy }); 236af8fd012Sandy 237*43fd9ed3Sandy return () => unsubscribe(); 238*43fd9ed3Sandy }, []); 239af8fd012Sandy 240af8fd012Sandy return items; 241af8fd012Sandy} 242