react-native-xenon 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +13 -24
  2. package/lib/module/assets/icons/arrow-left.png +0 -0
  3. package/lib/module/assets/icons/beautify.png +0 -0
  4. package/lib/module/assets/icons/bug.png +0 -0
  5. package/lib/module/assets/icons/close.png +0 -0
  6. package/lib/module/assets/icons/delete.png +0 -0
  7. package/lib/module/assets/icons/hide.png +0 -0
  8. package/lib/module/assets/icons/move.png +0 -0
  9. package/lib/module/assets/icons/record.png +0 -0
  10. package/lib/module/assets/icons/search.png +0 -0
  11. package/lib/module/assets/icons/share.png +0 -0
  12. package/lib/module/core/constants.js +2 -0
  13. package/lib/module/core/constants.js.map +1 -1
  14. package/lib/module/core/refs.js +11 -1
  15. package/lib/module/core/refs.js.map +1 -1
  16. package/lib/module/core/utils.js +6 -1
  17. package/lib/module/core/utils.js.map +1 -1
  18. package/lib/module/hooks/useNetworkInterceptor.js +8 -3
  19. package/lib/module/hooks/useNetworkInterceptor.js.map +1 -1
  20. package/lib/module/ui/components/bubble/Bubble.js +1 -1
  21. package/lib/module/ui/components/bubble/Bubble.js.map +1 -1
  22. package/lib/module/ui/components/common/Empty.js +26 -0
  23. package/lib/module/ui/components/common/Empty.js.map +1 -0
  24. package/lib/module/ui/components/handle/Handle.js +63 -0
  25. package/lib/module/ui/components/handle/Handle.js.map +1 -0
  26. package/lib/module/ui/components/headers/DebuggerHeader.js +3 -2
  27. package/lib/module/ui/components/headers/DebuggerHeader.js.map +1 -1
  28. package/lib/module/ui/components/items/ConsolePanelItem.js +26 -12
  29. package/lib/module/ui/components/items/ConsolePanelItem.js.map +1 -1
  30. package/lib/module/ui/components/items/NetworkPanelItem.js +50 -38
  31. package/lib/module/ui/components/items/NetworkPanelItem.js.map +1 -1
  32. package/lib/module/ui/components/panels/ConsolePanel.js +15 -14
  33. package/lib/module/ui/components/panels/ConsolePanel.js.map +1 -1
  34. package/lib/module/ui/components/panels/NetworkPanel.js +13 -10
  35. package/lib/module/ui/components/panels/NetworkPanel.js.map +1 -1
  36. package/lib/module/ui/components/panels/Panel.js +25 -13
  37. package/lib/module/ui/components/panels/Panel.js.map +1 -1
  38. package/lib/module/ui/components/search-bar/SearchBar.js +5 -13
  39. package/lib/module/ui/components/search-bar/SearchBar.js.map +1 -1
  40. package/lib/typescript/src/core/constants.d.ts +2 -0
  41. package/lib/typescript/src/core/constants.d.ts.map +1 -1
  42. package/lib/typescript/src/core/refs.d.ts +2 -1
  43. package/lib/typescript/src/core/refs.d.ts.map +1 -1
  44. package/lib/typescript/src/core/utils.d.ts +2 -1
  45. package/lib/typescript/src/core/utils.d.ts.map +1 -1
  46. package/lib/typescript/src/hooks/useNetworkInterceptor.d.ts.map +1 -1
  47. package/lib/typescript/src/ui/components/common/Empty.d.ts +3 -0
  48. package/lib/typescript/src/ui/components/common/Empty.d.ts.map +1 -0
  49. package/lib/typescript/src/ui/components/handle/Handle.d.ts +2 -0
  50. package/lib/typescript/src/ui/components/handle/Handle.d.ts.map +1 -0
  51. package/lib/typescript/src/ui/components/headers/DebuggerHeader.d.ts.map +1 -1
  52. package/lib/typescript/src/ui/components/items/ConsolePanelItem.d.ts +2 -2
  53. package/lib/typescript/src/ui/components/items/ConsolePanelItem.d.ts.map +1 -1
  54. package/lib/typescript/src/ui/components/items/NetworkPanelItem.d.ts +2 -2
  55. package/lib/typescript/src/ui/components/items/NetworkPanelItem.d.ts.map +1 -1
  56. package/lib/typescript/src/ui/components/panels/ConsolePanel.d.ts.map +1 -1
  57. package/lib/typescript/src/ui/components/panels/NetworkPanel.d.ts.map +1 -1
  58. package/lib/typescript/src/ui/components/panels/Panel.d.ts.map +1 -1
  59. package/lib/typescript/src/ui/components/search-bar/SearchBar.d.ts.map +1 -1
  60. package/package.json +2 -1
  61. package/src/assets/icons/arrow-left.png +0 -0
  62. package/src/assets/icons/beautify.png +0 -0
  63. package/src/assets/icons/bug.png +0 -0
  64. package/src/assets/icons/close.png +0 -0
  65. package/src/assets/icons/delete.png +0 -0
  66. package/src/assets/icons/hide.png +0 -0
  67. package/src/assets/icons/move.png +0 -0
  68. package/src/assets/icons/record.png +0 -0
  69. package/src/assets/icons/search.png +0 -0
  70. package/src/assets/icons/share.png +0 -0
  71. package/src/core/constants.ts +2 -0
  72. package/src/core/global.d.ts +0 -26
  73. package/src/core/refs.ts +10 -1
  74. package/src/core/utils.ts +7 -1
  75. package/src/hooks/useNetworkInterceptor.ts +11 -5
  76. package/src/ui/components/bubble/Bubble.tsx +1 -1
  77. package/src/ui/components/common/Empty.tsx +15 -0
  78. package/src/ui/components/handle/Handle.tsx +60 -0
  79. package/src/ui/components/headers/DebuggerHeader.tsx +3 -2
  80. package/src/ui/components/items/ConsolePanelItem.tsx +28 -14
  81. package/src/ui/components/items/NetworkPanelItem.tsx +70 -57
  82. package/src/ui/components/panels/ConsolePanel.tsx +16 -13
  83. package/src/ui/components/panels/NetworkPanel.tsx +17 -8
  84. package/src/ui/components/panels/Panel.tsx +28 -9
  85. package/src/ui/components/search-bar/SearchBar.tsx +9 -13
@@ -7,6 +7,6 @@ interface NetworkPanelItemProps {
7
7
  status?: NetworkRequest['status'];
8
8
  onPress: () => void;
9
9
  }
10
- export default function NetworkPanelItem({ method, name, startTime, endTime, status, onPress, }: NetworkPanelItemProps): import("react/jsx-runtime").JSX.Element;
11
- export {};
10
+ declare const NetworkPanelItem: import("react").NamedExoticComponent<NetworkPanelItemProps>;
11
+ export default NetworkPanelItem;
12
12
  //# sourceMappingURL=NetworkPanelItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkPanelItem.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/items/NetworkPanelItem.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGlE,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAsBD,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAO,EACP,MAAM,EACN,OAAO,GACR,EAAE,qBAAqB,2CAuDvB"}
1
+ {"version":3,"file":"NetworkPanelItem.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/items/NetworkPanelItem.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIlE,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAsBD,QAAA,MAAM,gBAAgB,6DAqErB,CAAC;AAwCF,eAAe,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ConsolePanel.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/panels/ConsolePanel.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAIR,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAStB,QAAA,MAAM,YAAY;YAAkC,SAAS,CAAC,SAAS,CAAC;iDAsDtE,CAAC;AAcH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"ConsolePanel.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/panels/ConsolePanel.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAGR,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAQtB,QAAA,MAAM,YAAY;YAAkC,SAAS,CAAC,SAAS,CAAC;iDA8DtE,CAAC;AAWH,eAAe,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkPanel.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/panels/NetworkPanel.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAGR,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAetB,QAAA,MAAM,YAAY;YAAkC,SAAS,CAAC,SAAS,CAAC;iDAyDtE,CAAC;AASH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"NetworkPanel.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/panels/NetworkPanel.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAGR,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAatB,QAAA,MAAM,YAAY;YAAkC,SAAS,CAAC,SAAS,CAAC;iDAoEtE,CAAC;AASH,eAAe,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/panels/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAwB,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAc1E,UAAU,UAAW,SAAQ,SAAS;CAAG;AAEzC,QAAA,MAAM,KAAK,6FA6BT,CAAC;AAgBH,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/panels/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkC,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAgBpF,UAAU,UAAW,SAAQ,SAAS;CAAG;AAEzC,QAAA,MAAM,KAAK,6FA8CT,CAAC;AAgBH,eAAe,KAAK,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SearchBar.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/search-bar/SearchBar.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,IAAI,EAGJ,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAUtB,UAAU,cAAe,SAAQ,SAAS;CAAG;AAE7C,QAAA,MAAM,SAAS,iGA+Db,CAAC;AA2CH,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"SearchBar.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/search-bar/SearchBar.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,IAAI,EAGJ,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAUtB,UAAU,cAAe,SAAQ,SAAS;CAAG;AAI7C,QAAA,MAAM,SAAS,iGAiEb,CAAC;AAmCH,eAAe,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-xenon",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "A powerful in-app debugging tool for React Native.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -47,6 +47,7 @@
47
47
  "xenon",
48
48
  "inspector",
49
49
  "debugger",
50
+ "devtools",
50
51
  "https",
51
52
  "network",
52
53
  "nsurlsession",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1 +1,3 @@
1
1
  export const NETWORK_REQUEST_HEADER = 'X-React-Native-Xenon';
2
+ export const NETWORK_ITEM_HEIGHT = 36;
3
+ export const CONSOLE_ITEM_HEIGHT = 36;
@@ -1,29 +1,3 @@
1
1
  interface XMLHttpRequest {
2
2
  _interceptionId?: string;
3
3
  }
4
-
5
- declare module 'react-native/Libraries/WebSocket/NativeWebSocketModule' {
6
- interface NativeWebSocketModule extends NativeModule {
7
- connect(
8
- url: string,
9
- protocols?: string | string[],
10
- options?: {
11
- headers: { [headerName: string]: string };
12
- [optionName: string]: any;
13
- },
14
- socketId: number,
15
- ): void;
16
-
17
- send(data: string, socketId: number): void;
18
-
19
- sendBinary(base64String: string, socketId: number): void;
20
-
21
- close(code: number, reason: string, socketId: number): void;
22
-
23
- addListener: (eventType: string) => void;
24
- removeListeners: (count: number) => void;
25
- }
26
-
27
- const NativeWebSocketModule: NativeWebSocketModule;
28
- export default NativeWebSocketModule;
29
- }
package/src/core/refs.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRef } from 'react';
2
2
  import type { IndexedStackMethods } from '../ui/components/common/IndexedStack';
3
- import type { TextInput } from 'react-native';
3
+ import { Animated, Dimensions, type TextInput } from 'react-native';
4
4
 
5
5
  export enum DebuggerVisibility {
6
6
  Hidden = -1,
@@ -27,6 +27,15 @@ const refs = {
27
27
  panel: createRef<IndexedStackMethods<PanelState>>(),
28
28
  header: createRef<IndexedStackMethods<HeaderState>>(),
29
29
  searchInput: createRef<TextInput>(),
30
+ panelSize: (() => {
31
+ const ref = createRef<Animated.ValueXY>();
32
+ const dimensions = Dimensions.get('window');
33
+ ref.current = new Animated.ValueXY({
34
+ x: dimensions.width,
35
+ y: Math.min(dimensions.width, dimensions.height) * 0.75,
36
+ });
37
+ return ref;
38
+ })(),
30
39
  };
31
40
 
32
41
  export default refs;
package/src/core/utils.ts CHANGED
@@ -79,7 +79,7 @@ export const getConsoleTypeColor = (type: LogMessage['type']) => {
79
79
  //#region metrics
80
80
  export const getVerticalSafeMargin = (screenHeight: number) => screenHeight / 8;
81
81
 
82
- export const clamp = (value: number, min: number, max: number) =>
82
+ export const clamp = (min: number, max: number, value: number) =>
83
83
  Math.max(min, Math.min(max, value));
84
84
 
85
85
  export const getHttpInterceptorId = () => {
@@ -152,6 +152,12 @@ export const convertToCurl = (
152
152
 
153
153
  return curlCommand;
154
154
  };
155
+
156
+ export const formatCount = (count: number) => {
157
+ if (count < 1000) return count.toString();
158
+ if (count < 1000000) return `${(count / 1000).toFixed(1)}K`;
159
+ return `${(count / 1000000).toFixed(1)}M`;
160
+ };
155
161
  //#endregion
156
162
 
157
163
  //#region decorators
@@ -43,14 +43,17 @@ export default function useNetworkInterceptor({
43
43
  setNetworkRequests(initRequests);
44
44
  };
45
45
 
46
+ const isMatchedDomain = (url: string) => {
47
+ if (!includeDomains?.length) return true;
48
+
49
+ return includeDomains.some(domain => url.includes(domain));
50
+ };
51
+
46
52
  const enableHttpInterceptions = useCallback(() => {
47
53
  const openCallback: HttpHandlers['open'] = (id, type, method, url) => {
48
54
  if (!id) return;
49
55
 
50
- const isNotMatchedDomain =
51
- !!joinedIncludeDomains.length && !includeDomains?.some(domain => url.includes(domain));
52
-
53
- if (isNotMatchedDomain) return;
56
+ if (!isMatchedDomain(url)) return;
54
57
 
55
58
  setNetworkRequests((draft: NetworkRequests<HttpRequest>) => {
56
59
  draft.set(id, { type, method, url });
@@ -162,6 +165,8 @@ export default function useNetworkInterceptor({
162
165
  ) => {
163
166
  if (typeof socketId !== 'number') return;
164
167
 
168
+ if (!isMatchedDomain(url)) return;
169
+
165
170
  setNetworkRequests((draft: NetworkRequests<WebSocketRequest>) => {
166
171
  draft.set(`${socketId}`, {
167
172
  startTime,
@@ -245,7 +250,8 @@ export default function useNetworkInterceptor({
245
250
  .set('onError', onErrorCallback)
246
251
  .set('onClose', onCloseCallback)
247
252
  .enableInterception();
248
- }, [setNetworkRequests]);
253
+ // eslint-disable-next-line react-hooks/exhaustive-deps
254
+ }, [joinedIncludeDomains, setNetworkRequests]);
249
255
 
250
256
  const enableInterception = useCallback(() => {
251
257
  if (isEnabled()) return;
@@ -80,9 +80,9 @@ const Bubble = forwardRef<View, BubbleProps>(
80
80
  const verticalSafeMargin = getVerticalSafeMargin(screenHeight);
81
81
 
82
82
  const finalY = clamp(
83
- gesture.moveY,
84
83
  verticalSafeMargin,
85
84
  screenHeight - verticalSafeMargin - bubbleSize,
85
+ gesture.moveY,
86
86
  );
87
87
 
88
88
  Animated.spring(pan.current, {
@@ -0,0 +1,15 @@
1
+ import type { PropsWithChildren } from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+
4
+ export default function Empty({ children }: PropsWithChildren) {
5
+ return (
6
+ <View style={styles.container}>
7
+ <Text style={styles.text}>{children}</Text>
8
+ </View>
9
+ );
10
+ }
11
+
12
+ const styles = StyleSheet.create({
13
+ container: { padding: 16, alignItems: 'center', justifyContent: 'center' },
14
+ text: { textAlign: 'center' },
15
+ });
@@ -0,0 +1,60 @@
1
+ import { useContext, useMemo, useRef } from 'react';
2
+ import { Animated, PanResponder, StyleSheet, View } from 'react-native';
3
+ import { MainContext } from '../../../contexts';
4
+ import refs from '../../../core/refs';
5
+ import { clamp } from '../../../core/utils';
6
+ import colors from '../../../theme/colors';
7
+
8
+ export default function Handle() {
9
+ const pan = useRef(new Animated.Value(0));
10
+ const {
11
+ dimensions,
12
+ debuggerState: { position },
13
+ } = useContext(MainContext)!;
14
+
15
+ const panResponder = useMemo(() => {
16
+ const minPanelHeight = dimensions.height * 0.25;
17
+ const maxPanelHeight = dimensions.height * 0.9;
18
+
19
+ return PanResponder.create({
20
+ onStartShouldSetPanResponder: () => true,
21
+ onMoveShouldSetPanResponder: () => true,
22
+ onPanResponderMove: (event, gestureState) => {
23
+ const value =
24
+ position === 'bottom' ? dimensions.height - gestureState.moveY : gestureState.moveY;
25
+
26
+ refs.panelSize.current?.setValue({
27
+ x: dimensions.width,
28
+ y: clamp(minPanelHeight, maxPanelHeight, value),
29
+ });
30
+
31
+ return Animated.event([null, { dy: pan.current }], {
32
+ useNativeDriver: false,
33
+ })(event, gestureState);
34
+ },
35
+ });
36
+ }, [dimensions.height, dimensions.width, position]);
37
+
38
+ return (
39
+ <View style={styles.handleContainer}>
40
+ <Animated.View {...panResponder.panHandlers} style={styles.handle} hitSlop={16} />
41
+ </View>
42
+ );
43
+ }
44
+
45
+ const styles = StyleSheet.create({
46
+ handleContainer: {
47
+ alignItems: 'center',
48
+ justifyContent: 'center',
49
+ backgroundColor: colors.lightGray,
50
+ borderTopColor: colors.gray,
51
+ borderTopWidth: StyleSheet.hairlineWidth,
52
+ },
53
+ handle: {
54
+ width: 48,
55
+ height: 4,
56
+ marginVertical: 8,
57
+ borderRadius: 2,
58
+ backgroundColor: colors.gray,
59
+ },
60
+ });
@@ -6,6 +6,7 @@ import icons from '../../../theme/icons';
6
6
  import Divider from '../common/Divider';
7
7
  import DebuggerHeaderItem from '../items/DebuggerHeaderItem';
8
8
  import HeaderComponents from './HeaderComponents';
9
+ import { formatCount } from '../../../core/utils';
9
10
 
10
11
  interface DebuggerHeaderProps {
11
12
  selectedPanel: PanelState;
@@ -54,7 +55,7 @@ const DebuggerHeader = forwardRef<ScrollView, DebuggerHeaderProps>(
54
55
  <DebuggerHeaderItem
55
56
  isLabel
56
57
  isActive={selectedPanel === PanelState.Network}
57
- content="Network Panel"
58
+ content={`Network (${formatCount(networkInterceptor.networkRequests.size)})`}
58
59
  onPress={() => switchTo(PanelState.Network)}
59
60
  />
60
61
 
@@ -74,7 +75,7 @@ const DebuggerHeader = forwardRef<ScrollView, DebuggerHeaderProps>(
74
75
  <DebuggerHeaderItem
75
76
  isLabel
76
77
  isActive={selectedPanel === PanelState.Console}
77
- content="Console Panel"
78
+ content={`Console (${formatCount(consoleInterceptor.logMessages.length)})`}
78
79
  onPress={() => switchTo(PanelState.Console)}
79
80
  />
80
81
 
@@ -1,4 +1,6 @@
1
- import { StyleSheet, Text } from 'react-native';
1
+ import { memo } from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+ import { CONSOLE_ITEM_HEIGHT } from '../../../core/constants';
2
4
  import { formatLogMessage, getConsoleTypeColor } from '../../../core/utils';
3
5
  import colors from '../../../theme/colors';
4
6
  import type { LogMessage } from '../../../types';
@@ -8,23 +10,33 @@ interface ConsolePanelItemProps extends LogMessage {
8
10
  onPress: () => void;
9
11
  }
10
12
 
11
- export default function ConsolePanelItem({ type, values, onPress }: ConsolePanelItemProps) {
12
- return (
13
- <Touchable
14
- onPress={onPress}
15
- style={[styles.container, { backgroundColor: getConsoleTypeColor(type) }]}
16
- >
17
- <Text numberOfLines={1} style={styles.text}>
18
- {formatLogMessage(values)}
19
- </Text>
20
- </Touchable>
21
- );
22
- }
13
+ const ConsolePanelItem = memo<ConsolePanelItemProps>(
14
+ ({ type, values, onPress }) => {
15
+ return (
16
+ <Touchable onPress={onPress} style={styles.container}>
17
+ <View style={[styles.wrapper, { backgroundColor: getConsoleTypeColor(type) }]}>
18
+ <Text numberOfLines={1} style={styles.text}>
19
+ {formatLogMessage(values)}
20
+ </Text>
21
+ </View>
22
+ </Touchable>
23
+ );
24
+ },
25
+ (prevProps, nextProps) => {
26
+ return prevProps.type === nextProps.type && prevProps.values === nextProps.values;
27
+ },
28
+ );
23
29
 
24
30
  const styles = StyleSheet.create({
25
31
  container: {
26
32
  flex: 1,
27
- padding: 8,
33
+ paddingBottom: 4,
34
+ height: CONSOLE_ITEM_HEIGHT,
35
+ },
36
+ wrapper: {
37
+ flex: 1,
38
+ paddingHorizontal: 8,
39
+ justifyContent: 'center',
28
40
  borderRadius: 8,
29
41
  },
30
42
  text: {
@@ -32,3 +44,5 @@ const styles = StyleSheet.create({
32
44
  fontSize: 14,
33
45
  },
34
46
  });
47
+
48
+ export default ConsolePanelItem;
@@ -1,6 +1,7 @@
1
- import { useMemo } from 'react';
1
+ import { memo, useMemo } from 'react';
2
2
  import { StyleSheet, Text, View } from 'react-native';
3
3
  import { URL } from 'react-native-url-polyfill';
4
+ import { NETWORK_ITEM_HEIGHT } from '../../../core/constants';
4
5
  import {
5
6
  formatRequestDuration,
6
7
  formatRequestMethod,
@@ -8,6 +9,7 @@ import {
8
9
  } from '../../../core/utils';
9
10
  import colors from '../../../theme/colors';
10
11
  import type { HttpRequest, NetworkRequest } from '../../../types';
12
+ import Divider from '../common/Divider';
11
13
  import Touchable from '../common/Touchable';
12
14
 
13
15
  interface NetworkPanelItemProps {
@@ -39,76 +41,85 @@ const getMethodColor = (method: string) => {
39
41
  }
40
42
  };
41
43
 
42
- export default function NetworkPanelItem({
43
- method,
44
- name,
45
- startTime,
46
- endTime,
47
- status,
48
- onPress,
49
- }: NetworkPanelItemProps) {
50
- const duration = formatRequestDuration(startTime, endTime);
51
- const requestMethod = formatRequestMethod(method);
52
- const requestStatusCode = formatRequestStatusCode(status);
53
- const isRequestFailed = Number.isInteger(status) && status! >= 400 && status! < 600;
54
- const textStyle = [styles.text, isRequestFailed && styles.failedText];
44
+ const NetworkPanelItem = memo<NetworkPanelItemProps>(
45
+ ({ method, name, startTime, endTime, status, onPress }) => {
46
+ const duration = formatRequestDuration(startTime, endTime);
47
+ const requestMethod = formatRequestMethod(method);
48
+ const requestStatusCode = formatRequestStatusCode(status);
49
+ const isRequestFailed = Number.isInteger(status) && status! >= 400 && status! < 600;
50
+ const textStyle = [styles.text, isRequestFailed && styles.failedText];
55
51
 
56
- const requestPath = useMemo(() => {
57
- if (!name) return '[failed]';
52
+ const requestPath = useMemo(() => {
53
+ if (!name) return '[failed]';
58
54
 
59
- try {
60
- const url = new URL(name);
61
- const suffixUrl = url.pathname + url.search;
55
+ try {
56
+ const url = new URL(name);
57
+ const suffixUrl = url.pathname + url.search;
62
58
 
63
- if (suffixUrl === '/') return url.host;
64
- return suffixUrl;
65
- } catch (error) {
66
- return name;
67
- }
68
- }, [name]);
59
+ if (suffixUrl === '/') return url.host;
60
+ return suffixUrl;
61
+ } catch (error) {
62
+ return name;
63
+ }
64
+ }, [name]);
69
65
 
70
- return (
71
- <Touchable onPress={onPress} style={styles.container}>
72
- <View style={styles.column}>
73
- <Text
74
- numberOfLines={1}
75
- style={[
76
- styles.text,
77
- styles.methodText,
78
- { backgroundColor: getMethodColor(requestMethod) },
79
- ]}
80
- >
81
- {requestMethod}
82
- </Text>
83
- </View>
66
+ return (
67
+ <View style={styles.container}>
68
+ <Touchable onPress={onPress} style={styles.wrapper}>
69
+ <View style={styles.column}>
70
+ <Text
71
+ numberOfLines={1}
72
+ style={[
73
+ styles.text,
74
+ styles.methodText,
75
+ { backgroundColor: getMethodColor(requestMethod) },
76
+ ]}
77
+ >
78
+ {requestMethod}
79
+ </Text>
80
+ </View>
84
81
 
85
- <View style={[styles.column, styles.pathColumn]}>
86
- <Text numberOfLines={1} style={textStyle}>
87
- {requestPath}
88
- </Text>
89
- </View>
82
+ <View style={[styles.column, styles.pathColumn]}>
83
+ <Text numberOfLines={1} style={textStyle}>
84
+ {requestPath}
85
+ </Text>
86
+ </View>
90
87
 
91
- <View style={[styles.column, styles.durationColumn]}>
92
- <Text numberOfLines={1} style={textStyle}>
93
- {duration}
94
- </Text>
95
- </View>
88
+ <View style={[styles.column, styles.durationColumn]}>
89
+ <Text numberOfLines={1} style={textStyle}>
90
+ {duration}
91
+ </Text>
92
+ </View>
96
93
 
97
- <View style={styles.column}>
98
- <Text numberOfLines={1} style={textStyle}>
99
- {requestStatusCode}
100
- </Text>
94
+ <View style={styles.column}>
95
+ <Text numberOfLines={1} style={textStyle}>
96
+ {requestStatusCode}
97
+ </Text>
98
+ </View>
99
+ </Touchable>
100
+ <Divider type="horizontal" />
101
101
  </View>
102
- </Touchable>
103
- );
104
- }
102
+ );
103
+ },
104
+ (prevProps, nextProps) => {
105
+ return (
106
+ prevProps.method === nextProps.method &&
107
+ prevProps.name === nextProps.name &&
108
+ prevProps.startTime === nextProps.startTime &&
109
+ prevProps.endTime === nextProps.endTime &&
110
+ prevProps.status === nextProps.status
111
+ );
112
+ },
113
+ );
105
114
 
106
115
  const styles = StyleSheet.create({
107
116
  container: {
108
117
  flex: 1,
118
+ },
119
+ wrapper: {
109
120
  flexDirection: 'row',
110
121
  alignItems: 'center',
111
- paddingVertical: 8,
122
+ height: NETWORK_ITEM_HEIGHT,
112
123
  columnGap: 8,
113
124
  },
114
125
  pathColumn: {
@@ -138,3 +149,5 @@ const styles = StyleSheet.create({
138
149
  fontWeight: '600',
139
150
  },
140
151
  });
152
+
153
+ export default NetworkPanelItem;
@@ -2,18 +2,16 @@ import { forwardRef, useCallback, useContext, useMemo } from 'react';
2
2
  import {
3
3
  FlatList,
4
4
  StyleSheet,
5
- View,
6
5
  type ListRenderItem,
7
6
  type StyleProp,
8
7
  type ViewStyle,
9
8
  } from 'react-native';
10
9
  import { MainContext } from '../../../contexts';
10
+ import { CONSOLE_ITEM_HEIGHT } from '../../../core/constants';
11
+ import { formatLogMessage } from '../../../core/utils';
11
12
  import { DebuggerPanel, type LogMessage } from '../../../types';
13
+ import Empty from '../common/Empty';
12
14
  import ConsolePanelItem from '../items/ConsolePanelItem';
13
- import refs, { HeaderState, PanelState } from '../../../core/refs';
14
- import { formatLogMessage } from '../../../core/utils';
15
-
16
- const Separator = () => <View style={styles.divider} />;
17
15
 
18
16
  const ConsolePanel = forwardRef<FlatList, { style?: StyleProp<ViewStyle> }>(({ style }, ref) => {
19
17
  const {
@@ -41,8 +39,6 @@ const ConsolePanel = forwardRef<FlatList, { style?: StyleProp<ViewStyle> }>(({ s
41
39
  <ConsolePanelItem
42
40
  {...item}
43
41
  onPress={() => {
44
- refs.header.current?.setCurrentIndex(HeaderState.Console);
45
- refs.panel.current?.setCurrentIndex(PanelState.ConsoleDetail);
46
42
  setDebuggerState(draft => {
47
43
  draft.detailsData = {
48
44
  type: DebuggerPanel.Console,
@@ -57,16 +53,26 @@ const ConsolePanel = forwardRef<FlatList, { style?: StyleProp<ViewStyle> }>(({ s
57
53
  [setDebuggerState],
58
54
  );
59
55
 
56
+ const getItemLayout = useCallback(
57
+ (_: ArrayLike<LogMessage> | null | undefined, index: number) => ({
58
+ length: CONSOLE_ITEM_HEIGHT,
59
+ offset: CONSOLE_ITEM_HEIGHT * index,
60
+ index,
61
+ }),
62
+ [],
63
+ );
64
+
60
65
  return (
61
66
  <FlatList
62
67
  ref={ref}
63
- inverted
68
+ inverted={!!data.length}
64
69
  data={data}
65
70
  renderItem={renderItem}
66
71
  keyExtractor={(_, index) => index.toString()}
67
- ItemSeparatorComponent={Separator}
68
72
  style={[styles.container, style]}
69
- contentContainerStyle={styles.contentContainer}
73
+ contentContainerStyle={data.length ? styles.contentContainer : undefined}
74
+ ListEmptyComponent={<Empty>No logs yet</Empty>}
75
+ getItemLayout={getItemLayout}
70
76
  />
71
77
  );
72
78
  });
@@ -78,9 +84,6 @@ const styles = StyleSheet.create({
78
84
  contentContainer: {
79
85
  padding: 8,
80
86
  },
81
- divider: {
82
- height: 4,
83
- },
84
87
  });
85
88
 
86
89
  export default ConsolePanel;