expo-dev-launcher
Version:
Pre-release version of the Expo development launcher package for testing.
241 lines (181 loc) • 4.99 kB
text/typescript
import * as React from 'react';
import { Animated } from 'react-native';
export type StackAction = 'pushstart' | 'pushend' | 'popstart' | 'popend';
export type Status = 'pushing' | 'popping' | 'settled' | 'popped';
export type StackItemComponent<T = any> = React.JSXElementConstructor<T>;
export type StackEvent<T = any> = {
state: StackState<T>;
event: {
action: StackAction;
key: string;
};
};
type AValue = Animated.Value;
export type StackItem<T = any> = {
key: string;
status: Status;
promise: Promise<StackItem<T>>;
pop: () => void;
onPushEnd: () => void;
onPopEnd: () => void;
data: T;
animatedValue: AValue;
};
export type StackState<T = any> = {
items: StackItem<T>[];
lookup: Record<string, StackItem<T>>;
getItemByKey: (key: string) => StackItem<T> | null;
};
export type Stack<T> = {
push: (data?: T | undefined) => StackItem<T>;
pop: (amount?: number, startIndex?: number) => StackItem[];
subscribe: (listener: (state: StackEvent<T>) => void) => () => void;
getState: () => StackState;
};
export function createAsyncStack<T = any>() {
let keys: string[] = [];
const lookup: Record<string, StackItem<T>> = {};
let count = 0;
const pushResolvers: Record<string, any> = {};
const popResolvers: Record<string, Function> = {};
let listeners: any[] = [];
function push(data?: T) {
count += 1;
const key = '' + count;
keys.push(key);
const promise = new Promise<StackItem<T>>((resolve) => {
pushResolvers[key] = resolve;
});
const item: StackItem<T> = {
key,
promise,
data,
status: 'pushing' as Status,
pop: () => pop(`${key}`),
onPushEnd: () => onPushEnd(key),
onPopEnd: () => onPopEnd(key),
animatedValue: new Animated.Value(0),
};
if (data) {
item.data = data;
}
lookup[key] = item;
emit('pushstart', key);
return item;
}
function onPushEnd(key: string) {
const item = lookup[key];
if (item.status === 'pushing') {
item.status = 'settled';
const resolver = pushResolvers[key];
if (resolver) {
resolver(getItemByKey(key));
delete pushResolvers[key];
}
emit('pushend', key);
}
return item;
}
function pop(amount: number | string = 1) {
const items: StackItem[] = [];
if (typeof amount === 'string') {
const key = amount;
const item = lookup[key];
if (item) {
if (item.status === 'pushing') {
onPushEnd(key);
}
item.status = 'popping';
const promise = new Promise<StackItem<T>>((resolve) => {
popResolvers[key] = resolve;
});
item.promise = promise;
emit('popstart', key);
items.push(item);
}
return items;
}
if (amount === -1) {
// pop them all
amount = keys.length;
}
let startIndex = keys.length - 1;
for (let i = keys.length - 1; i >= 0; i--) {
const key = keys[i];
const item = lookup[key];
if (item && (item.status === 'settled' || item.status === 'pushing')) {
startIndex = i;
break;
}
}
for (let i = startIndex; i > startIndex - amount; i--) {
const key = keys[i];
const item = lookup[key];
if (item) {
if (item.status === 'pushing') {
onPushEnd(key);
}
item.status = 'popping';
const promise = new Promise<StackItem<T>>((resolve) => {
popResolvers[key] = resolve;
});
item.promise = promise;
emit('popstart', key);
items.push(item);
}
}
return items;
}
function onPopEnd(key: string) {
const item = lookup[key];
keys = keys.filter((k) => k !== key);
const resolver = popResolvers[key];
if (resolver) {
resolver(getItemByKey(key));
delete popResolvers[key];
}
item.status = 'popped';
emit('popend', key);
return item;
}
function subscribe(listener: (state: StackEvent<T>) => void) {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
function emit(action: StackAction, key: string) {
listeners.forEach((listener) => {
const state = getState();
const event = { key, action };
listener({ state, event });
});
}
function getItemByKey(key: string) {
return lookup[key];
}
function getState(): StackState {
const items = keys.map((key) => lookup[key]);
return {
items,
lookup,
getItemByKey,
};
}
return {
push,
pop,
subscribe,
getState,
};
}
export function useStackItems<T>(stack: Stack<T>) {
const [items, setItems] = React.useState<StackItem<T>[]>(stack.getState().items);
React.useEffect(() => {
const unsubscribe = stack.subscribe(({ state }) => {
setItems(state.items);
});
return () => unsubscribe();
}, []);
return items;
}