create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
421 lines (382 loc) • 11.6 kB
text/typescript
/* global _WORKLET _getCurrentTime _frameTimestamp _eventTimestamp, _setGlobalConsole */
import NativeReanimatedModule from './NativeReanimated';
import { Platform } from 'react-native';
import { nativeShouldBeMock, shouldBeUseWeb, isWeb } from './PlatformChecker';
import {
BasicWorkletFunction,
WorkletFunction,
ComplexWorkletFunction,
SharedValue,
} from './commonTypes';
import {
AnimationObject,
AnimatableValue,
Timestamp,
} from './animation/commonTypes';
import { Descriptor } from './hook/commonTypes';
import JSReanimated from './js-reanimated/JSReanimated';
global.__reanimatedWorkletInit = function (worklet: WorkletFunction) {
worklet.__worklet = true;
};
if (global._setGlobalConsole === undefined) {
// it can happen when Reanimated plugin wasn't added, but the user uses the only API from version 1
global._setGlobalConsole = () => {
// noop
};
}
export type ReanimatedConsole = Pick<
Console,
'debug' | 'log' | 'warn' | 'info' | 'error'
>;
export type WorkletValue =
| (() => AnimationObject)
| AnimationObject
| AnimatableValue
| Descriptor;
interface WorkletValueSetterContext {
_animation?: AnimationObject | null;
_value?: AnimatableValue | Descriptor;
value?: AnimatableValue;
_setValue?: (val: AnimatableValue | Descriptor) => void;
}
const testWorklet: BasicWorkletFunction<void> = () => {
'worklet';
};
export const checkPluginState: (throwError: boolean) => boolean = (
throwError = true
) => {
if (!testWorklet.__workletHash && !shouldBeUseWeb()) {
if (throwError) {
throw new Error(
"Reanimated 2 failed to create a worklet, maybe you forgot to add Reanimated's babel plugin?"
);
}
return false;
}
return true;
};
export const isConfigured: (throwError?: boolean) => boolean = (
throwError = false
) => {
return checkPluginState(throwError) && !NativeReanimatedModule.useOnlyV1;
};
export const isConfiguredCheck: () => void = () => {
if (!isConfigured(true)) {
throw new Error(
'If you want to use Reanimated 2 then go through our installation steps https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation'
);
}
};
function _toArrayReanimated<T>(object: Iterable<T> | ArrayLike<T> | T[]): T[] {
'worklet';
if (Array.isArray(object)) {
return object;
}
if (
typeof Symbol !== 'undefined' &&
(typeof Symbol === 'function' ? Symbol.iterator : '@@iterator') in
Object(object)
) {
return Array.from(object);
}
throw new Error(
'Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.'
);
}
function _mergeObjectsReanimated(): unknown {
'worklet';
// we can't use rest parameters in worklets at the moment
// eslint-disable-next-line prefer-rest-params
const arr = Array.from(arguments);
return Object.assign.apply(null, [arr[0], ...arr.slice(1)]);
}
global.__reanimatedWorkletInit = function (worklet: WorkletFunction) {
worklet.__worklet = true;
if (worklet._closure) {
const closure = worklet._closure;
Object.keys(closure).forEach((key) => {
if (key === '_toConsumableArray') {
closure[key] = _toArrayReanimated;
}
if (key === '_objectSpread') {
closure[key] = _mergeObjectsReanimated;
}
});
}
};
function pushFrame(frame: (timestamp: Timestamp) => void): void {
(NativeReanimatedModule as JSReanimated).pushFrame(frame);
}
export function requestFrame(frame: (timestamp: Timestamp) => void): void {
'worklet';
if (NativeReanimatedModule.native) {
requestAnimationFrame(frame);
} else {
pushFrame(frame);
}
}
global._WORKLET = false;
global._log = function (s: string) {
console.log(s);
};
export function runOnUI<A extends any[], R>(
worklet: ComplexWorkletFunction<A, R>
): (...args: A) => void {
return makeShareable(worklet);
}
export function makeShareable<T>(value: T): T {
isConfiguredCheck();
return NativeReanimatedModule.makeShareable(value);
}
export function getViewProp<T>(viewTag: string, propName: string): Promise<T> {
return new Promise((resolve, reject) => {
return NativeReanimatedModule.getViewProp(
viewTag,
propName,
(result: T) => {
if (typeof result === 'string' && result.substr(0, 6) === 'error:') {
reject(result);
} else {
resolve(result);
}
}
);
});
}
let _getTimestamp: () => number;
if (nativeShouldBeMock()) {
_getTimestamp = () => {
return (NativeReanimatedModule as JSReanimated).getTimestamp();
};
} else {
_getTimestamp = () => {
'worklet';
if (_frameTimestamp) {
return _frameTimestamp;
}
if (_eventTimestamp) {
return _eventTimestamp;
}
return _getCurrentTime();
};
}
export function getTimestamp(): number {
'worklet';
if (Platform.OS === 'web') {
return (NativeReanimatedModule as JSReanimated).getTimestamp();
}
return _getTimestamp();
}
function workletValueSetter<T extends WorkletValue>(
this: WorkletValueSetterContext,
value: T
): void {
'worklet';
const previousAnimation = this._animation;
if (previousAnimation) {
previousAnimation.cancelled = true;
this._animation = null;
}
if (
typeof value === 'function' ||
(value !== null &&
typeof value === 'object' &&
(value as AnimationObject).onFrame !== undefined)
) {
const animation: AnimationObject =
typeof value === 'function'
? (value as () => AnimationObject)()
: (value as AnimationObject);
// prevent setting again to the same value
// and triggering the mappers that treat this value as an input
// this happens when the animation's target value(stored in animation.current until animation.onStart is called) is set to the same value as a current one(this._value)
// built in animations that are not higher order(withTiming, withSpring) hold target value in .current
if (this._value === animation.current && !animation.isHigherOrder) {
animation.callback && animation.callback(true);
return;
}
// animated set
const initializeAnimation = (timestamp: number) => {
animation.onStart(animation, this.value, timestamp, previousAnimation);
};
initializeAnimation(getTimestamp());
const step = (timestamp: number) => {
if (animation.cancelled) {
animation.callback && animation.callback(false /* finished */);
return;
}
const finished = animation.onFrame(animation, timestamp);
animation.finished = true;
animation.timestamp = timestamp;
this._value = animation.current;
if (finished) {
animation.callback && animation.callback(true /* finished */);
} else {
requestAnimationFrame(step);
}
};
this._animation = animation;
if (_frameTimestamp) {
// frame
step(_frameTimestamp);
} else {
requestAnimationFrame(step);
}
} else {
// prevent setting again to the same value
// and triggering the mappers that treat this value as an input
if (this._value === value) {
return;
}
this._value = value as Descriptor | AnimatableValue;
}
}
// We cannot use pushFrame
// so we use own implementation for js
function workletValueSetterJS<T extends WorkletValue>(
this: WorkletValueSetterContext,
value: T
): void {
const previousAnimation = this._animation;
if (previousAnimation) {
previousAnimation.cancelled = true;
this._animation = null;
}
if (
typeof value === 'function' ||
(value !== null &&
typeof value === 'object' &&
(value as AnimationObject).onFrame)
) {
// animated set
const animation: AnimationObject =
typeof value === 'function'
? (value as () => AnimationObject)()
: (value as AnimationObject);
let initializeAnimation: ((timestamp: number) => void) | null = (
timestamp: number
) => {
animation.onStart(animation, this.value, timestamp, previousAnimation);
};
const step = (timestamp: number) => {
if (animation.cancelled) {
animation.callback && animation.callback(false /* finished */);
return;
}
if (initializeAnimation) {
initializeAnimation(timestamp);
initializeAnimation = null; // prevent closure from keeping ref to previous animation
}
const finished = animation.onFrame(animation, timestamp);
animation.timestamp = timestamp;
this._setValue && this._setValue(animation.current as AnimatableValue);
if (finished) {
animation.callback && animation.callback(true /* finished */);
} else {
requestFrame(step);
}
};
this._animation = animation;
requestFrame(step);
} else {
this._setValue && this._setValue(value as AnimatableValue | Descriptor);
}
}
export function makeMutable<T>(value: T): SharedValue<T> {
isConfiguredCheck();
return NativeReanimatedModule.makeMutable(value);
}
export function makeRemote<T>(object = {}): T {
isConfiguredCheck();
return NativeReanimatedModule.makeRemote(object);
}
export function startMapper(
mapper: () => void,
inputs: any[] = [],
outputs: any[] = [],
updater: () => void = () => {
// noop
},
viewDescriptors: Descriptor[] | SharedValue<Descriptor[]> = []
): number {
isConfiguredCheck();
return NativeReanimatedModule.startMapper(
mapper,
inputs,
outputs,
updater,
viewDescriptors
);
}
export function stopMapper(mapperId: number): void {
NativeReanimatedModule.stopMapper(mapperId);
}
export interface RunOnJSFunction<A extends any[], R> {
__callAsync?: (...args: A) => void;
(...args: A): R;
}
export function runOnJS<A extends any[], R>(
fun: RunOnJSFunction<A, R>
): () => void {
'worklet';
if (!_WORKLET) {
return fun;
}
if (!fun.__callAsync) {
throw new Error(
"Attempting to call runOnJS with an object that is not a host function. Using runOnJS is only possible with methods that are defined on the main React-Native Javascript thread and that aren't marked as worklets"
);
} else {
return fun.__callAsync;
}
}
if (!NativeReanimatedModule.useOnlyV1) {
NativeReanimatedModule.installCoreFunctions(
NativeReanimatedModule.native
? (workletValueSetter as <T>(value: T) => void)
: (workletValueSetterJS as <T>(value: T) => void)
);
if (!isWeb() && isConfigured()) {
const capturableConsole = console;
runOnUI(() => {
'worklet';
const console = {
debug: runOnJS(capturableConsole.debug),
log: runOnJS(capturableConsole.log),
warn: runOnJS(capturableConsole.warn),
error: runOnJS(capturableConsole.error),
info: runOnJS(capturableConsole.info),
};
_setGlobalConsole(console);
global.performance = {
now: global._chronoNow,
};
})();
}
}
type FeaturesConfig = {
enableLayoutAnimations: boolean;
setByUser: boolean;
};
let featuresConfig: FeaturesConfig = {
enableLayoutAnimations: false,
setByUser: false,
};
export function enableLayoutAnimations(
flag: boolean,
isCallByUser = true
): void {
if (isCallByUser) {
featuresConfig = {
enableLayoutAnimations: flag,
setByUser: true,
};
NativeReanimatedModule.enableLayoutAnimations(flag);
} else if (
!featuresConfig.setByUser &&
featuresConfig.enableLayoutAnimations !== flag
) {
featuresConfig.enableLayoutAnimations = flag;
NativeReanimatedModule.enableLayoutAnimations(flag);
}
}