react-native-network-lens 0.1.0 → 0.2.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.
- package/README.md +254 -97
- package/lib/module/components/BodyViewer.js +31 -19
- package/lib/module/components/BodyViewer.js.map +1 -1
- package/lib/module/components/JsonTree/JsonTreeView.js +10 -6
- package/lib/module/components/JsonTree/JsonTreeView.js.map +1 -1
- package/lib/module/components/JsonTree/model.js +2 -0
- package/lib/module/components/JsonTree/model.js.map +1 -1
- package/lib/module/components/NetworkLens.js +4 -8
- package/lib/module/components/NetworkLens.js.map +1 -1
- package/lib/module/components/NetworkLensOverlay.js +67 -22
- package/lib/module/components/NetworkLensOverlay.js.map +1 -1
- package/lib/module/components/RawTextView.js +10 -6
- package/lib/module/components/RawTextView.js.map +1 -1
- package/lib/module/components/RequestDetails/RequestDetails.js +44 -21
- package/lib/module/components/RequestDetails/RequestDetails.js.map +1 -1
- package/lib/module/components/RequestList/FilterBar.js +3 -3
- package/lib/module/components/RequestList/FilterBar.js.map +1 -1
- package/lib/module/components/RequestList/RequestList.js +7 -8
- package/lib/module/components/RequestList/RequestList.js.map +1 -1
- package/lib/module/components/ui/Button.js +21 -5
- package/lib/module/components/ui/Button.js.map +1 -1
- package/lib/module/components/ui/Pill.js +13 -6
- package/lib/module/components/ui/Pill.js.map +1 -1
- package/lib/module/settings.js +48 -0
- package/lib/module/settings.js.map +1 -0
- package/lib/module/theme.js +4 -4
- package/lib/typescript/src/components/BodyViewer.d.ts.map +1 -1
- package/lib/typescript/src/components/JsonTree/JsonTreeView.d.ts.map +1 -1
- package/lib/typescript/src/components/JsonTree/model.d.ts.map +1 -1
- package/lib/typescript/src/components/NetworkLens.d.ts +0 -3
- package/lib/typescript/src/components/NetworkLens.d.ts.map +1 -1
- package/lib/typescript/src/components/NetworkLensOverlay.d.ts +1 -1
- package/lib/typescript/src/components/NetworkLensOverlay.d.ts.map +1 -1
- package/lib/typescript/src/components/RawTextView.d.ts.map +1 -1
- package/lib/typescript/src/components/RequestDetails/RequestDetails.d.ts.map +1 -1
- package/lib/typescript/src/components/RequestList/RequestList.d.ts +1 -4
- package/lib/typescript/src/components/RequestList/RequestList.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Button.d.ts +3 -1
- package/lib/typescript/src/components/ui/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/ui/Pill.d.ts +1 -0
- package/lib/typescript/src/components/ui/Pill.d.ts.map +1 -1
- package/lib/typescript/src/settings.d.ts +18 -0
- package/lib/typescript/src/settings.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/components/BodyViewer.tsx +37 -19
- package/src/components/JsonTree/JsonTreeView.tsx +11 -5
- package/src/components/JsonTree/model.ts +2 -0
- package/src/components/NetworkLens.tsx +2 -11
- package/src/components/NetworkLensOverlay.tsx +54 -23
- package/src/components/RawTextView.tsx +11 -6
- package/src/components/RequestDetails/RequestDetails.tsx +31 -19
- package/src/components/RequestList/FilterBar.tsx +2 -2
- package/src/components/RequestList/RequestList.tsx +3 -7
- package/src/components/ui/Button.tsx +23 -7
- package/src/components/ui/Pill.tsx +14 -5
- package/src/settings.ts +55 -0
- package/src/theme.ts +2 -2
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
1
|
import type NetworkRequest from '../../core/NetworkRequest.js';
|
|
3
2
|
import { type ActionItem } from '../ui/index.js';
|
|
4
3
|
interface Props {
|
|
@@ -10,9 +9,7 @@ interface Props {
|
|
|
10
9
|
loggingEnabled: boolean;
|
|
11
10
|
onPressItem: (id: string) => void;
|
|
12
11
|
menuActions: ActionItem[];
|
|
13
|
-
/** Optional row rendered above the search bar (used by the overlay for title + close). */
|
|
14
|
-
header?: ReactNode;
|
|
15
12
|
}
|
|
16
|
-
declare const RequestList: ({ requests, sort, compact, maxRows, paused, loggingEnabled, onPressItem, menuActions,
|
|
13
|
+
declare const RequestList: ({ requests, sort, compact, maxRows, paused, loggingEnabled, onPressItem, menuActions, }: Props) => import("react").JSX.Element;
|
|
17
14
|
export default RequestList;
|
|
18
15
|
//# sourceMappingURL=RequestList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestList.d.ts","sourceRoot":"","sources":["../../../../../src/components/RequestList/RequestList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RequestList.d.ts","sourceRoot":"","sources":["../../../../../src/components/RequestList/RequestList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,cAAc,MAAM,8BAA2B,CAAC;AAG5D,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,gBAAO,CAAC;AAYf,UAAU,KAAK;IACb,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAWD,QAAA,MAAM,WAAW,GAAI,yFASlB,KAAK,gCAwJP,CAAC;AAiBF,eAAe,WAAW,CAAC"}
|
|
@@ -4,10 +4,12 @@ interface TextButtonProps {
|
|
|
4
4
|
onPress?: () => void;
|
|
5
5
|
disabled?: boolean;
|
|
6
6
|
active?: boolean;
|
|
7
|
+
/** Text-only button in the primary colour, for navigation actions like Back. */
|
|
8
|
+
plain?: boolean;
|
|
7
9
|
style?: StyleProp<ViewStyle>;
|
|
8
10
|
textStyle?: StyleProp<TextStyle>;
|
|
9
11
|
}
|
|
10
|
-
export declare const TextButton: ({ label, onPress, disabled, active, style, textStyle, }: TextButtonProps) => import("react").JSX.Element;
|
|
12
|
+
export declare const TextButton: ({ label, onPress, disabled, active, plain, style, textStyle, }: TextButtonProps) => import("react").JSX.Element;
|
|
11
13
|
interface IconButtonProps {
|
|
12
14
|
/** A unicode glyph; the library ships no image assets. */
|
|
13
15
|
glyph: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAOtB,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,UAAU,GAAI,gEAQxB,eAAe,gCAsCjB,CAAC;AAEF,UAAU,eAAe;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,UAAU,GAAI,2DAMxB,eAAe,gCAyBjB,CAAC"}
|
|
@@ -6,6 +6,7 @@ interface Props {
|
|
|
6
6
|
small?: boolean;
|
|
7
7
|
style?: StyleProp<ViewStyle>;
|
|
8
8
|
}
|
|
9
|
+
/** Fixed-height badge so filled and outlined variants align on one row. */
|
|
9
10
|
export declare const Pill: ({ label, color, filled, small, style }: Props) => import("react").JSX.Element;
|
|
10
11
|
export {};
|
|
11
12
|
//# sourceMappingURL=Pill.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pill.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/Pill.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,eAAO,MAAM,IAAI,GAAI,wCAAwC,KAAK,gCAqBjE,CAAC"}
|
|
1
|
+
{"version":3,"file":"Pill.d.ts","sourceRoot":"","sources":["../../../../../src/components/ui/Pill.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAEtB,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,2EAA2E;AAC3E,eAAO,MAAM,IAAI,GAAI,wCAAwC,KAAK,gCAqBjE,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Theme } from './theme.js';
|
|
2
|
+
/**
|
|
3
|
+
* Tiny in-memory preferences shared by every viewer in the session, e.g. the
|
|
4
|
+
* code font scale the user picks with the "Aa" button.
|
|
5
|
+
*/
|
|
6
|
+
export type CodeFontScale = 'small' | 'medium' | 'large';
|
|
7
|
+
export declare const setCodeFontScale: (next: CodeFontScale) => void;
|
|
8
|
+
export declare const cycleCodeFontScale: () => void;
|
|
9
|
+
export declare const useCodeFontScale: () => CodeFontScale;
|
|
10
|
+
/** Font metrics for code views: the theme's base size times the chosen scale. */
|
|
11
|
+
export declare const useCodeFont: (theme: Theme) => {
|
|
12
|
+
scale: CodeFontScale;
|
|
13
|
+
fontSize: number;
|
|
14
|
+
lineHeight: number;
|
|
15
|
+
/** Average glyph advance for monospace fonts; used to size horizontal scroll areas. */
|
|
16
|
+
charWidth: number;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAS,CAAC;AAErC;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAoBzD,eAAO,MAAM,gBAAgB,GAAI,MAAM,aAAa,SAInD,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAI9B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,aACiB,CAAC;AAEtD,iFAAiF;AACjF,eAAO,MAAM,WAAW,GAAI,OAAO,KAAK;;;;IAQpC,uFAAuF;;CAG1F,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-network-lens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "In-app HTTP network inspector for React Native with a tabbed request viewer, collapsible JSON tree, search and zero dependencies",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"typescript": "^6.0.3"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
|
-
"react": ">=
|
|
92
|
-
"react-native": ">=0.
|
|
91
|
+
"react": ">=18.0.0",
|
|
92
|
+
"react-native": ">=0.69.0"
|
|
93
93
|
},
|
|
94
94
|
"workspaces": [
|
|
95
95
|
"example"
|
|
@@ -6,12 +6,13 @@ import { bodySize, stringifyJson } from '../utils/body';
|
|
|
6
6
|
import { formatBytes } from '../utils/format';
|
|
7
7
|
import JsonTreeView, { type JsonTreeHandle } from './JsonTree/JsonTreeView';
|
|
8
8
|
import RawTextView from './RawTextView';
|
|
9
|
+
import { cycleCodeFontScale, useCodeFontScale } from '../settings';
|
|
9
10
|
import {
|
|
10
11
|
ActionSheet,
|
|
11
12
|
EmptyState,
|
|
13
|
+
IconButton,
|
|
12
14
|
SearchInput,
|
|
13
15
|
SegmentedControl,
|
|
14
|
-
TextButton,
|
|
15
16
|
} from './ui';
|
|
16
17
|
|
|
17
18
|
type ViewMode = 'tree' | 'raw';
|
|
@@ -60,6 +61,8 @@ const BodyViewer = ({
|
|
|
60
61
|
value: unknown;
|
|
61
62
|
} | null>(null);
|
|
62
63
|
const treeRef = useRef<JsonTreeHandle>(null);
|
|
64
|
+
const [menuVisible, setMenuVisible] = useState(false);
|
|
65
|
+
const fontScale = useCodeFontScale();
|
|
63
66
|
|
|
64
67
|
const effectiveMode: ViewMode = structured ? mode : 'raw';
|
|
65
68
|
|
|
@@ -122,22 +125,16 @@ const BodyViewer = ({
|
|
|
122
125
|
</Text>
|
|
123
126
|
)}
|
|
124
127
|
<View style={styles.spacer} />
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
</>
|
|
136
|
-
)}
|
|
137
|
-
{copyToClipboard && body.kind !== 'binary' && (
|
|
138
|
-
<TextButton label="Copy" onPress={() => copyToClipboard(body.text)} />
|
|
139
|
-
)}
|
|
140
|
-
<TextButton label="Share" onPress={() => share(body.text)} />
|
|
128
|
+
<IconButton
|
|
129
|
+
glyph="Aa"
|
|
130
|
+
accessibilityLabel={`Text size: ${fontScale}. Tap to change`}
|
|
131
|
+
onPress={cycleCodeFontScale}
|
|
132
|
+
/>
|
|
133
|
+
<IconButton
|
|
134
|
+
glyph="⋮"
|
|
135
|
+
accessibilityLabel="More options"
|
|
136
|
+
onPress={() => setMenuVisible(true)}
|
|
137
|
+
/>
|
|
141
138
|
</View>
|
|
142
139
|
{body.kind !== 'binary' && (
|
|
143
140
|
<SearchInput
|
|
@@ -183,6 +180,28 @@ const BodyViewer = ({
|
|
|
183
180
|
/>
|
|
184
181
|
)}
|
|
185
182
|
</View>
|
|
183
|
+
<ActionSheet
|
|
184
|
+
visible={menuVisible}
|
|
185
|
+
onClose={() => setMenuVisible(false)}
|
|
186
|
+
actions={[
|
|
187
|
+
...(effectiveMode === 'tree'
|
|
188
|
+
? [
|
|
189
|
+
{
|
|
190
|
+
label: 'Expand all',
|
|
191
|
+
onPress: () => treeRef.current?.expandAll(),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
label: 'Collapse all',
|
|
195
|
+
onPress: () => treeRef.current?.collapseAll(),
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
: []),
|
|
199
|
+
...(copyToClipboard && body.kind !== 'binary'
|
|
200
|
+
? [{ label: 'Copy', onPress: () => copyToClipboard(body.text) }]
|
|
201
|
+
: []),
|
|
202
|
+
{ label: 'Share', onPress: () => share(body.text) },
|
|
203
|
+
]}
|
|
204
|
+
/>
|
|
186
205
|
<ActionSheet
|
|
187
206
|
visible={nodeMenu !== null}
|
|
188
207
|
onClose={() => setNodeMenu(null)}
|
|
@@ -227,9 +246,8 @@ const styles = StyleSheet.create({
|
|
|
227
246
|
alignItems: 'center',
|
|
228
247
|
paddingHorizontal: 12,
|
|
229
248
|
paddingTop: 10,
|
|
230
|
-
gap: 6,
|
|
231
249
|
},
|
|
232
|
-
modeSwitch: { width:
|
|
250
|
+
modeSwitch: { width: 140 },
|
|
233
251
|
spacer: { flex: 1 },
|
|
234
252
|
search: { marginHorizontal: 12, marginTop: 8 },
|
|
235
253
|
meta: { fontSize: 12, flexShrink: 1 },
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useState,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { Pressable, StyleSheet, Text } from 'react-native';
|
|
11
|
+
import { useCodeFont } from '../../settings';
|
|
11
12
|
import { useTheme, type Theme } from '../../theme';
|
|
12
13
|
import { splitHighlights } from '../../utils/search';
|
|
13
14
|
import { CodeList } from '../CodeList';
|
|
@@ -58,6 +59,7 @@ const JsonTreeView = forwardRef<JsonTreeHandle, Props>(
|
|
|
58
59
|
ref
|
|
59
60
|
) {
|
|
60
61
|
const theme = useTheme();
|
|
62
|
+
const font = useCodeFont(theme);
|
|
61
63
|
const [expanded, setExpanded] = useState<Set<string>>(() =>
|
|
62
64
|
pathsToDepth(value, initialDepth)
|
|
63
65
|
);
|
|
@@ -118,8 +120,8 @@ const JsonTreeView = forwardRef<JsonTreeHandle, Props>(
|
|
|
118
120
|
let maxChars = 0;
|
|
119
121
|
for (const row of rows)
|
|
120
122
|
maxChars = Math.max(maxChars, rowCharCount(row, INDENT_CHARS));
|
|
121
|
-
return maxChars *
|
|
122
|
-
}, [rows,
|
|
123
|
+
return maxChars * font.charWidth + 40;
|
|
124
|
+
}, [rows, font.charWidth]);
|
|
123
125
|
|
|
124
126
|
const toggle = useCallback((row: JsonRow) => {
|
|
125
127
|
setExpanded((prev) => {
|
|
@@ -135,13 +137,14 @@ const JsonTreeView = forwardRef<JsonTreeHandle, Props>(
|
|
|
135
137
|
});
|
|
136
138
|
}, []);
|
|
137
139
|
|
|
138
|
-
const rowHeight =
|
|
140
|
+
const rowHeight = font.lineHeight + 4;
|
|
139
141
|
|
|
140
142
|
const renderItem = useCallback(
|
|
141
143
|
({ item }: { item: JsonRow }) => (
|
|
142
144
|
<TreeRow
|
|
143
145
|
row={item}
|
|
144
146
|
theme={theme}
|
|
147
|
+
font={font}
|
|
145
148
|
query={query}
|
|
146
149
|
matched={matchedPaths.has(item.path)}
|
|
147
150
|
active={item.path === activePath}
|
|
@@ -152,6 +155,7 @@ const JsonTreeView = forwardRef<JsonTreeHandle, Props>(
|
|
|
152
155
|
),
|
|
153
156
|
[
|
|
154
157
|
theme,
|
|
158
|
+
font,
|
|
155
159
|
query,
|
|
156
160
|
matchedPaths,
|
|
157
161
|
activePath,
|
|
@@ -180,6 +184,7 @@ const keyExtractor = (row: JsonRow) => row.path;
|
|
|
180
184
|
const TreeRow = memo(function TreeRowInner({
|
|
181
185
|
row,
|
|
182
186
|
theme,
|
|
187
|
+
font,
|
|
183
188
|
query,
|
|
184
189
|
matched,
|
|
185
190
|
active,
|
|
@@ -189,6 +194,7 @@ const TreeRow = memo(function TreeRowInner({
|
|
|
189
194
|
}: {
|
|
190
195
|
row: JsonRow;
|
|
191
196
|
theme: Theme;
|
|
197
|
+
font: ReturnType<typeof useCodeFont>;
|
|
192
198
|
query: string;
|
|
193
199
|
matched: boolean;
|
|
194
200
|
active: boolean;
|
|
@@ -198,8 +204,8 @@ const TreeRow = memo(function TreeRowInner({
|
|
|
198
204
|
}) {
|
|
199
205
|
const codeStyle = {
|
|
200
206
|
fontFamily: theme.fonts.mono,
|
|
201
|
-
fontSize:
|
|
202
|
-
lineHeight:
|
|
207
|
+
fontSize: font.fontSize,
|
|
208
|
+
lineHeight: font.lineHeight,
|
|
203
209
|
};
|
|
204
210
|
const valueColor = (() => {
|
|
205
211
|
switch (row.type) {
|
|
@@ -260,5 +260,7 @@ export const MAX_VALUE_CHARS = 400;
|
|
|
260
260
|
|
|
261
261
|
export const displayValue = (value: unknown): string => {
|
|
262
262
|
if (typeof value === 'string') return `"${value}"`;
|
|
263
|
+
if (Array.isArray(value)) return '[]'; // only empty arrays reach here
|
|
264
|
+
if (value && typeof value === 'object') return '{}'; // only empty objects reach here
|
|
263
265
|
return primitiveText(value);
|
|
264
266
|
};
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
useImperativeHandle,
|
|
6
6
|
useMemo,
|
|
7
7
|
useState,
|
|
8
|
-
type ReactNode,
|
|
9
8
|
} from 'react';
|
|
10
9
|
import { BackHandler, Share, StyleSheet, View } from 'react-native';
|
|
11
10
|
import { setLensBackHandler } from '../backHandler';
|
|
@@ -40,8 +39,6 @@ export interface NetworkLensProps {
|
|
|
40
39
|
* `copyToClipboard={Clipboard.setString}`.
|
|
41
40
|
*/
|
|
42
41
|
copyToClipboard?: (text: string) => void;
|
|
43
|
-
/** Rendered above the request list's search bar (the overlay uses it for its title bar). */
|
|
44
|
-
header?: ReactNode;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
export interface NetworkLensHandle {
|
|
@@ -58,7 +55,6 @@ const NetworkLens = forwardRef<NetworkLensHandle, NetworkLensProps>(
|
|
|
58
55
|
compact = false,
|
|
59
56
|
maxRows,
|
|
60
57
|
copyToClipboard,
|
|
61
|
-
header,
|
|
62
58
|
},
|
|
63
59
|
ref
|
|
64
60
|
) {
|
|
@@ -71,7 +67,6 @@ const NetworkLens = forwardRef<NetworkLensHandle, NetworkLensProps>(
|
|
|
71
67
|
compact={compact}
|
|
72
68
|
maxRows={maxRows}
|
|
73
69
|
copyToClipboard={copyToClipboard}
|
|
74
|
-
header={header}
|
|
75
70
|
/>
|
|
76
71
|
</ThemeContext.Provider>
|
|
77
72
|
);
|
|
@@ -79,13 +74,10 @@ const NetworkLens = forwardRef<NetworkLensHandle, NetworkLensProps>(
|
|
|
79
74
|
);
|
|
80
75
|
|
|
81
76
|
type InspectorProps = Required<Pick<NetworkLensProps, 'sort' | 'compact'>> &
|
|
82
|
-
Pick<NetworkLensProps, 'maxRows' | 'copyToClipboard'
|
|
77
|
+
Pick<NetworkLensProps, 'maxRows' | 'copyToClipboard'>;
|
|
83
78
|
|
|
84
79
|
const Inspector = forwardRef<NetworkLensHandle, InspectorProps>(
|
|
85
|
-
function InspectorInner(
|
|
86
|
-
{ sort, compact, maxRows, copyToClipboard, header },
|
|
87
|
-
ref
|
|
88
|
-
) {
|
|
80
|
+
function InspectorInner({ sort, compact, maxRows, copyToClipboard }, ref) {
|
|
89
81
|
const theme = useTheme();
|
|
90
82
|
const requests = useNetworkRequests();
|
|
91
83
|
const loggingEnabled = useLoggerEnabled();
|
|
@@ -163,7 +155,6 @@ const Inspector = forwardRef<NetworkLensHandle, InspectorProps>(
|
|
|
163
155
|
loggingEnabled={loggingEnabled}
|
|
164
156
|
onPressItem={setSelectedId}
|
|
165
157
|
menuActions={menuActions}
|
|
166
|
-
header={header}
|
|
167
158
|
/>
|
|
168
159
|
)}
|
|
169
160
|
</View>
|
|
@@ -11,15 +11,16 @@ import {
|
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import { useLoggerEnabled, useNetworkRequests } from '../hooks';
|
|
13
13
|
import { ThemeContext, useTheme, type ThemeInput } from '../theme';
|
|
14
|
+
|
|
15
|
+
const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 };
|
|
14
16
|
import NetworkLens, {
|
|
15
17
|
type NetworkLensHandle,
|
|
16
18
|
type NetworkLensProps,
|
|
17
19
|
} from './NetworkLens';
|
|
18
|
-
import { IconButton } from './ui';
|
|
19
20
|
|
|
20
21
|
export interface NetworkLensOverlayProps extends Omit<
|
|
21
22
|
NetworkLensProps,
|
|
22
|
-
'enabled' | 'theme'
|
|
23
|
+
'enabled' | 'theme'
|
|
23
24
|
> {
|
|
24
25
|
/**
|
|
25
26
|
* Render nothing when `false`, e.g.
|
|
@@ -88,17 +89,6 @@ const Overlay = ({
|
|
|
88
89
|
[position.endsWith('right') ? 'right' : 'left']: offset?.x ?? 16,
|
|
89
90
|
};
|
|
90
91
|
|
|
91
|
-
const header = (
|
|
92
|
-
<View style={styles.header}>
|
|
93
|
-
<Text style={[styles.title, { color: t.colors.text }]}>{title}</Text>
|
|
94
|
-
<IconButton
|
|
95
|
-
glyph="✕"
|
|
96
|
-
accessibilityLabel="Close network inspector"
|
|
97
|
-
onPress={() => setOpen(false)}
|
|
98
|
-
/>
|
|
99
|
-
</View>
|
|
100
|
-
);
|
|
101
|
-
|
|
102
92
|
return (
|
|
103
93
|
<>
|
|
104
94
|
{!hideButton && (
|
|
@@ -135,12 +125,43 @@ const Overlay = ({
|
|
|
135
125
|
}}
|
|
136
126
|
>
|
|
137
127
|
<View style={[styles.sheet, { backgroundColor: t.colors.surface }]}>
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
128
|
+
<View
|
|
129
|
+
style={[
|
|
130
|
+
styles.header,
|
|
131
|
+
{
|
|
132
|
+
backgroundColor: t.colors.surface,
|
|
133
|
+
borderBottomColor: t.colors.border,
|
|
134
|
+
},
|
|
135
|
+
]}
|
|
136
|
+
>
|
|
137
|
+
<View style={styles.headerSide} />
|
|
138
|
+
<Text
|
|
139
|
+
style={[styles.title, { color: t.colors.text }]}
|
|
140
|
+
numberOfLines={1}
|
|
141
|
+
>
|
|
142
|
+
{title}
|
|
143
|
+
</Text>
|
|
144
|
+
<View style={styles.headerSide}>
|
|
145
|
+
<Pressable
|
|
146
|
+
accessibilityRole="button"
|
|
147
|
+
accessibilityLabel="Close network inspector"
|
|
148
|
+
onPress={() => setOpen(false)}
|
|
149
|
+
hitSlop={HIT_SLOP}
|
|
150
|
+
style={({ pressed }) => [
|
|
151
|
+
styles.close,
|
|
152
|
+
{ backgroundColor: t.colors.surfaceAlt },
|
|
153
|
+
pressed && styles.pressed,
|
|
154
|
+
]}
|
|
155
|
+
>
|
|
156
|
+
<Text
|
|
157
|
+
style={[styles.closeGlyph, { color: t.colors.textMuted }]}
|
|
158
|
+
>
|
|
159
|
+
✕
|
|
160
|
+
</Text>
|
|
161
|
+
</Pressable>
|
|
162
|
+
</View>
|
|
163
|
+
</View>
|
|
164
|
+
<NetworkLens ref={lensRef} {...lensProps} theme={theme} />
|
|
144
165
|
</View>
|
|
145
166
|
</Modal>
|
|
146
167
|
</>
|
|
@@ -175,13 +196,23 @@ const styles = StyleSheet.create({
|
|
|
175
196
|
paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0,
|
|
176
197
|
},
|
|
177
198
|
header: {
|
|
199
|
+
height: 52,
|
|
178
200
|
flexDirection: 'row',
|
|
179
201
|
alignItems: 'center',
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
paddingHorizontal: 12,
|
|
203
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
204
|
+
},
|
|
205
|
+
headerSide: { width: 44, alignItems: 'flex-end', justifyContent: 'center' },
|
|
206
|
+
title: { flex: 1, textAlign: 'center', fontSize: 17, fontWeight: '600' },
|
|
207
|
+
close: {
|
|
208
|
+
width: 30,
|
|
209
|
+
height: 30,
|
|
210
|
+
borderRadius: 15,
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
justifyContent: 'center',
|
|
183
213
|
},
|
|
184
|
-
|
|
214
|
+
closeGlyph: { fontSize: 15, fontWeight: '700' },
|
|
215
|
+
pressed: { opacity: 0.6 },
|
|
185
216
|
});
|
|
186
217
|
|
|
187
218
|
export default NetworkLensOverlay;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import { useCodeFont } from '../settings';
|
|
3
4
|
import { useTheme, type Theme } from '../theme';
|
|
4
5
|
import { findLineMatches, splitHighlights, toLines } from '../utils/search';
|
|
5
6
|
import { CodeList } from './CodeList';
|
|
@@ -22,6 +23,7 @@ const RawTextView = ({
|
|
|
22
23
|
onMatchCountChange,
|
|
23
24
|
}: Props) => {
|
|
24
25
|
const theme = useTheme();
|
|
26
|
+
const font = useCodeFont(theme);
|
|
25
27
|
const lines = useMemo(() => toLines(text), [text]);
|
|
26
28
|
const matches = useMemo(() => findLineMatches(lines, query), [lines, query]);
|
|
27
29
|
|
|
@@ -37,14 +39,14 @@ const RawTextView = ({
|
|
|
37
39
|
matches[Math.min(activeMatch, Math.max(matches.length - 1, 0))]?.line;
|
|
38
40
|
|
|
39
41
|
const gutterChars = String(lines.length).length;
|
|
40
|
-
const charWidth =
|
|
42
|
+
const charWidth = font.charWidth;
|
|
41
43
|
const contentWidth = useMemo(() => {
|
|
42
44
|
let max = 0;
|
|
43
45
|
for (const line of lines) if (line.length > max) max = line.length;
|
|
44
46
|
return (max + gutterChars + 3) * charWidth + 32;
|
|
45
47
|
}, [lines, gutterChars, charWidth]);
|
|
46
48
|
|
|
47
|
-
const rowHeight =
|
|
49
|
+
const rowHeight = font.lineHeight;
|
|
48
50
|
|
|
49
51
|
const renderItem = useCallback(
|
|
50
52
|
({ item, index }: { item: string; index: number }) => (
|
|
@@ -53,13 +55,14 @@ const RawTextView = ({
|
|
|
53
55
|
index={index}
|
|
54
56
|
gutterChars={gutterChars}
|
|
55
57
|
theme={theme}
|
|
58
|
+
font={font}
|
|
56
59
|
query={query}
|
|
57
60
|
matched={matchedLines.has(index)}
|
|
58
61
|
active={index === activeLine}
|
|
59
62
|
height={rowHeight}
|
|
60
63
|
/>
|
|
61
64
|
),
|
|
62
|
-
[gutterChars, theme, query, matchedLines, activeLine, rowHeight]
|
|
65
|
+
[gutterChars, theme, font, query, matchedLines, activeLine, rowHeight]
|
|
63
66
|
);
|
|
64
67
|
|
|
65
68
|
return (
|
|
@@ -82,6 +85,7 @@ const Line = React.memo(function LineInner({
|
|
|
82
85
|
index,
|
|
83
86
|
gutterChars,
|
|
84
87
|
theme,
|
|
88
|
+
font,
|
|
85
89
|
query,
|
|
86
90
|
matched,
|
|
87
91
|
active,
|
|
@@ -91,6 +95,7 @@ const Line = React.memo(function LineInner({
|
|
|
91
95
|
index: number;
|
|
92
96
|
gutterChars: number;
|
|
93
97
|
theme: Theme;
|
|
98
|
+
font: ReturnType<typeof useCodeFont>;
|
|
94
99
|
query: string;
|
|
95
100
|
matched: boolean;
|
|
96
101
|
active: boolean;
|
|
@@ -98,8 +103,8 @@ const Line = React.memo(function LineInner({
|
|
|
98
103
|
}) {
|
|
99
104
|
const codeStyle = {
|
|
100
105
|
fontFamily: theme.fonts.mono,
|
|
101
|
-
fontSize:
|
|
102
|
-
lineHeight:
|
|
106
|
+
fontSize: font.fontSize,
|
|
107
|
+
lineHeight: font.lineHeight,
|
|
103
108
|
};
|
|
104
109
|
const segments = matched ? splitHighlights(text, query) : null;
|
|
105
110
|
return (
|
|
@@ -120,7 +125,7 @@ const Line = React.memo(function LineInner({
|
|
|
120
125
|
styles.gutter,
|
|
121
126
|
{
|
|
122
127
|
color: theme.colors.textMuted,
|
|
123
|
-
width: (gutterChars + 1) *
|
|
128
|
+
width: (gutterChars + 1) * font.charWidth + 8,
|
|
124
129
|
},
|
|
125
130
|
]}
|
|
126
131
|
>
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
} from '../../utils/format';
|
|
19
19
|
import BodyViewer from '../BodyViewer';
|
|
20
20
|
import {
|
|
21
|
-
IconButton,
|
|
22
21
|
KeyValueList,
|
|
23
22
|
Pill,
|
|
24
23
|
Section,
|
|
@@ -76,27 +75,32 @@ const RequestDetails = ({ request, onClose, copyToClipboard }: Props) => {
|
|
|
76
75
|
},
|
|
77
76
|
]}
|
|
78
77
|
>
|
|
79
|
-
<View style={styles.
|
|
78
|
+
<View style={styles.navRow}>
|
|
79
|
+
<TextButton
|
|
80
|
+
label="‹ Back"
|
|
81
|
+
onPress={onClose}
|
|
82
|
+
plain
|
|
83
|
+
style={styles.back}
|
|
84
|
+
textStyle={styles.backLabel}
|
|
85
|
+
/>
|
|
86
|
+
<Text
|
|
87
|
+
style={[styles.summary, { color: theme.colors.textMuted }]}
|
|
88
|
+
numberOfLines={1}
|
|
89
|
+
>
|
|
90
|
+
{summary}
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
<View style={styles.pillRow}>
|
|
80
94
|
<Pill
|
|
81
95
|
label={request.method}
|
|
82
96
|
color={methodColor(theme, request.method)}
|
|
83
97
|
filled
|
|
98
|
+
style={styles.headerPill}
|
|
84
99
|
/>
|
|
85
100
|
<Pill
|
|
86
101
|
label={statusLabel(request)}
|
|
87
102
|
color={statusColor(theme, request.status, failed)}
|
|
88
103
|
/>
|
|
89
|
-
<Text
|
|
90
|
-
style={[styles.summary, { color: theme.colors.textMuted }]}
|
|
91
|
-
numberOfLines={1}
|
|
92
|
-
>
|
|
93
|
-
{summary}
|
|
94
|
-
</Text>
|
|
95
|
-
<IconButton
|
|
96
|
-
glyph="✕"
|
|
97
|
-
accessibilityLabel="Back to list"
|
|
98
|
-
onPress={onClose}
|
|
99
|
-
/>
|
|
100
104
|
</View>
|
|
101
105
|
<Pressable
|
|
102
106
|
onPress={() => setUrlExpanded((v) => !v)}
|
|
@@ -292,21 +296,29 @@ const styles = StyleSheet.create({
|
|
|
292
296
|
paddingVertical: 10,
|
|
293
297
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
294
298
|
},
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
navRow: {
|
|
300
|
+
flexDirection: 'row',
|
|
301
|
+
alignItems: 'center',
|
|
302
|
+
justifyContent: 'space-between',
|
|
303
|
+
marginLeft: -8,
|
|
304
|
+
},
|
|
305
|
+
back: { paddingHorizontal: 8, paddingVertical: 4 },
|
|
306
|
+
backLabel: { fontSize: 16 },
|
|
307
|
+
pillRow: { flexDirection: 'row', alignItems: 'center', marginTop: 6 },
|
|
308
|
+
headerPill: { marginRight: 8 },
|
|
309
|
+
summary: { flexShrink: 1, fontSize: 12, fontVariant: ['tabular-nums'] },
|
|
297
310
|
url: { fontSize: 14, marginTop: 8, lineHeight: 19 },
|
|
298
311
|
gql: { fontSize: 12, marginTop: 4, fontWeight: '600' },
|
|
299
312
|
tabs: { marginTop: 10 },
|
|
300
313
|
overview: { paddingBottom: 24 },
|
|
301
|
-
sectionActions: { flexDirection: 'row'
|
|
302
|
-
smallButton: { paddingVertical: 3, paddingHorizontal: 8 },
|
|
314
|
+
sectionActions: { flexDirection: 'row' },
|
|
315
|
+
smallButton: { paddingVertical: 3, paddingHorizontal: 8, marginLeft: 6 },
|
|
303
316
|
exportRow: {
|
|
304
317
|
flexDirection: 'row',
|
|
305
318
|
flexWrap: 'wrap',
|
|
306
|
-
gap: 8,
|
|
307
319
|
paddingVertical: 8,
|
|
308
320
|
},
|
|
309
|
-
exportButton: { paddingVertical: 8 },
|
|
321
|
+
exportButton: { paddingVertical: 8, marginRight: 8, marginBottom: 8 },
|
|
310
322
|
footer: { textAlign: 'center', fontSize: 11, marginTop: 20 },
|
|
311
323
|
});
|
|
312
324
|
|
|
@@ -155,8 +155,8 @@ const styles = StyleSheet.create({
|
|
|
155
155
|
paddingLeft: 12,
|
|
156
156
|
letterSpacing: 0.5,
|
|
157
157
|
},
|
|
158
|
-
chips: { paddingRight: 12
|
|
159
|
-
chip: { paddingVertical: 4, paddingHorizontal: 10 },
|
|
158
|
+
chips: { paddingRight: 12 },
|
|
159
|
+
chip: { paddingVertical: 4, paddingHorizontal: 10, marginRight: 6 },
|
|
160
160
|
clear: {
|
|
161
161
|
alignSelf: 'flex-end',
|
|
162
162
|
marginRight: 12,
|