Skip to content

Commit

Permalink
fix api
Browse files Browse the repository at this point in the history
  • Loading branch information
RobPruzan committed Nov 27, 2024
1 parent 40a7f48 commit 6fad4f6
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 97 deletions.
1 change: 1 addition & 0 deletions src/core/instrumentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const instrument = ({
onCommitStart();

const handleFiber = (fiber: Fiber, trigger: boolean) => {
// to-do, don't traverse fibers part from react-scan, get all components from no-traverse subtree and put it in a weakset
const type = getType(fiber.type);
if (!type) return null;
if (!didFiberRender(fiber)) return null;
Expand Down
233 changes: 138 additions & 95 deletions src/core/native/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import {
Text,
} from '@shopify/react-native-skia';
import React, {
createContext,
useContext,
useEffect,
useRef,
useState,
useSyncExternalStore,
} from 'react';
// import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
import { ReactScanInternals } from '..';
import { useSharedValue, withTiming } from 'react-native-reanimated';
import { assertNative, instrumentNative } from './instrument';

// can't use useSyncExternalStore for compat
// can't use useSyncExternalStore for back compat
const useIsPaused = () => {
const [isPaused, setIsPaused] = useState(ReactScanInternals.isPaused);
useEffect(() => {
Expand All @@ -36,18 +37,79 @@ const useIsPaused = () => {
return isPaused;
};

export const ReactNativeScanEntryPoint = () => {
if (ReactScanInternals.isProd) {
return null; // todo: better no-op
}
interface Options {
/**
* Controls the animation of the re-render overlay.
* When set to "fade-out", the overlay will fade out after appearing.
* When false, no animation will be applied.
* Note: Enabling animations may impact performance.
* @default false */
animationWhenFlashing?: 'fade-out' | false;
}

export type ReactNativeScanOptions =Options &
Omit<
typeof ReactScanInternals.options,
'playSound' | 'runInProduction' | 'includeChildren'
>;

const OptionsContext = createContext<ReactNativeScanOptions & Required<Options>>({
animationWhenFlashing: false,
});

export const ReactScan = ({
children,
...options
}: {
children: React.ReactNode;
} & ReactNativeScanOptions) => {
useEffect(() => {
if (!ReactScanInternals.isProd) {
instrumentNative(); // cleanup?
}
ReactScanInternals.options = options;
instrumentNative();
}, []);
const isPaused = useIsPaused();

useEffect(() => {
const interval = setInterval(() => {
if (isPaused) return;

const newActive = ReactScanInternals.activeOutlines.filter(
(x) => Date.now() - x.updatedAt < 500,
);
if (newActive.length !== ReactScanInternals.activeOutlines.length) {
ReactScanInternals.set('activeOutlines', newActive);
}
}, 200);
return () => {
clearInterval(interval);
};
}, [isPaused]);

return (
<>
{children}
<OptionsContext.Provider value={{
...options,
animationWhenFlashing: options.animationWhenFlashing ?? false
}}>
{!isPaused && <ReactScanCanvas scanTag="react-scan-no-traverse" />}
{options.showToolbar && (
<ReactScanToolbar
scanTag="react-scan-no-traverse"
isPaused={isPaused}
/>
)}
</OptionsContext.Provider>
</>
);
};

const ReactScanToolbar = ({
isPaused,
}: {
isPaused: boolean;
scanTag: string;
}) => {
const pan = useRef(new Animated.ValueXY()).current;

const panResponder = useRef(
Expand All @@ -70,77 +132,57 @@ export const ReactNativeScanEntryPoint = () => {
}),
).current;

useEffect(() => {
const interval = setInterval(() => {
if (isPaused) return;

const newActive = ReactScanInternals.activeOutlines.filter(
(x) => Date.now() - x.updatedAt < 500,
);
if (newActive.length !== ReactScanInternals.activeOutlines.length) {
ReactScanInternals.set('activeOutlines', newActive);
}
}, 200);
return () => {
clearInterval(interval);
};
}, [isPaused]);

return (
<>
{!isPaused && <ReactNativeScan id="react-scan-no-traverse" />}

<Animated.View
id="react-scan-no-traverse"
<Animated.View
id="react-scan-no-traverse"
style={{
position: 'absolute',
bottom: 20,
right: 20,
zIndex: 999999,
transform: pan.getTranslateTransform(),
}}
{...panResponder.panHandlers}
>
<Pressable
onPress={() =>
(ReactScanInternals.isPaused = !ReactScanInternals.isPaused)
}
style={{
position: 'absolute',
bottom: 20,
right: 20,
zIndex: 999999,
transform: pan.getTranslateTransform(),
backgroundColor: !isPaused
? 'rgba(88, 82, 185, 0.9)'
: 'rgba(88, 82, 185, 0.5)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 4,
flexDirection: 'row',
alignItems: 'center',
gap: 8,
}}
{...panResponder.panHandlers}
>
<Pressable
onPress={() =>
(ReactScanInternals.isPaused = !ReactScanInternals.isPaused)
}
<View
style={{
backgroundColor: !isPaused
? 'rgba(88, 82, 185, 0.9)'
: 'rgba(88, 82, 185, 0.5)',
paddingHorizontal: 12,
paddingVertical: 6,
width: 8,
height: 8,
borderRadius: 4,
flexDirection: 'row',
alignItems: 'center',
gap: 8,
backgroundColor: !isPaused ? '#4ADE80' : '#666',
}}
/>
<RNText
style={{
color: 'white',
fontSize: 14,
fontWeight: 'bold',
fontFamily: Platform.select({
ios: 'Courier',
default: 'monospace',
}),
}}
>
<View
style={{
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: !isPaused ? '#4ADE80' : '#666',
}}
/>
<RNText
style={{
color: 'white',
fontSize: 14,
fontWeight: 'bold',
fontFamily: Platform.select({
ios: 'Courier',
default: 'monospace',
}),
}}
>
React Scan
</RNText>
</Pressable>
</Animated.View>
</>
React Scan
</RNText>
</Pressable>
</Animated.View>
);
};
const dimensions = Dimensions.get('window');
Expand All @@ -155,24 +197,30 @@ const font = matchFont({
const getTextWidth = (text: string) => {
return (text || 'unknown').length * 7;
};
const ReactNativeScan = ({ id: _ }: { id: string }) => {
// const opacity = useSharedValue(1);
// todo: polly fill
const outlines = useSyncExternalStore(
(listener) =>
ReactScanInternals.subscribe('activeOutlines', (_) => {
// animations destroy UI thread on heavy updates, probably not worth it
// opacity.value = 1;
// opacity.value = withTiming(0, {
// duration: 500
// })
listener();
}),
() => ReactScanInternals.activeOutlines,
);
// );
// const animatedOpacity = useDerivedValue(() => opacity.value);

const useOutlines = (opacity: { value: number }) => {
const [outlines, setOutlines] = useState<
(typeof ReactScanInternals)['activeOutlines']
>([]);
const options = useContext(OptionsContext);
// cannot use useSyncExternalStore for back compat
useEffect(() => {
ReactScanInternals.subscribe('activeOutlines', (activeOutlines) => {
setOutlines(activeOutlines);
if (options.animationWhenFlashing !== false) {
// we only support fade-out for now
opacity.value = 1;
opacity.value = withTiming(0, {
duration: 500,
});
}
});
}, []);
return outlines;
};
const ReactScanCanvas = (_: { scanTag: string }) => {
const opacity = useSharedValue(1);
const outlines = useOutlines(opacity);
return (
<Canvas
style={{
Expand Down Expand Up @@ -255,8 +303,3 @@ const ReactNativeScan = ({ id: _ }: { id: string }) => {
</Canvas>
);
};





3 changes: 1 addition & 2 deletions src/core/native/plugins/metro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ interface TransformResult {
map?: any;
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

const defaultConfig = getDefaultConfig(__dirname);
Expand All @@ -22,7 +21,7 @@ const config = {

if (filename.includes('node_modules/react-scan/dist/core/native/')) {
return {
code: `export const ReactNativeScanEntryPoint = () => null;`,
code: `export const ReactScan = () => null;`,
};
}

Expand Down

0 comments on commit 6fad4f6

Please sign in to comment.