@buoy-gg/network 5.0.0 → 6.0.1
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 +29 -353
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +32 -18
- package/lib/commonjs/network/components/NetworkHeaderLive.js +199 -0
- package/lib/commonjs/network/components/NetworkListScreen.js +203 -0
- package/lib/commonjs/network/components/NetworkModal.js +153 -630
- package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +83 -0
- package/lib/commonjs/network/hooks/useNetworkEventList.js +93 -0
- package/lib/commonjs/network/hooks/useNetworkHasMatches.js +56 -0
- package/lib/commonjs/network/utils/filterNetworkEvents.js +96 -0
- package/lib/commonjs/network/utils/generateNetworkCopyText.js +139 -0
- package/lib/module/index.js +5 -0
- package/lib/module/network/components/NetworkEventItemCompact.js +32 -18
- package/lib/module/network/components/NetworkHeaderLive.js +193 -0
- package/lib/module/network/components/NetworkListScreen.js +199 -0
- package/lib/module/network/components/NetworkModal.js +157 -634
- package/lib/module/network/hooks/useNetworkBadgeCounts.js +79 -0
- package/lib/module/network/hooks/useNetworkEventList.js +89 -0
- package/lib/module/network/hooks/useNetworkHasMatches.js +52 -0
- package/lib/module/network/utils/filterNetworkEvents.js +92 -0
- package/lib/module/network/utils/generateNetworkCopyText.js +135 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +0 -4
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts +52 -0
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkListScreen.d.ts +27 -0
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts +24 -0
- package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useNetworkEventList.d.ts +34 -0
- package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts +23 -0
- package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +1 -0
- package/lib/typescript/network/utils/filterNetworkEvents.d.ts +13 -0
- package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +1 -0
- package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +11 -0
- package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -1,34 +1,101 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useState, useRef,
|
|
4
|
-
import { View,
|
|
5
|
-
import {
|
|
6
|
-
import { NetworkEventItemCompact } from "./NetworkEventItemCompact";
|
|
3
|
+
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { View, StyleSheet, TouchableOpacity, TextInput } from "react-native";
|
|
5
|
+
import { Search, Filter, X, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, persistentStorage, TabSelector, useFeatureGate, UpgradeModal, PowerToggleButton, useIgnoredPatterns } from "@buoy-gg/shared-ui";
|
|
7
6
|
import { NetworkFilterViewV3 } from "./NetworkFilterViewV3";
|
|
8
7
|
import { TickProvider } from "../hooks/useTickEveryMinute";
|
|
9
8
|
import { NetworkEventDetailView } from "./NetworkEventDetailView";
|
|
10
9
|
import { NetworkCopySettingsView, DEFAULT_COPY_SETTINGS } from "./NetworkCopySettingsView";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
import { NetworkHeaderBadges, NetworkClearButton, NetworkHeaderCopyButton } from "./NetworkHeaderLive";
|
|
11
|
+
import { NetworkListScreen } from "./NetworkListScreen";
|
|
12
|
+
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
13
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Screens (rule4 stable router: each gates itself with `return null`; the
|
|
16
|
+
// root renders all four as stable siblings instead of a nested ternary).
|
|
17
|
+
// Subscribing screens run their hooks unconditionally and pass
|
|
18
|
+
// `paused || !visible` so hidden screens don't subscribe.
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
function NetworkDetailScreen({
|
|
22
|
+
visible,
|
|
23
|
+
event,
|
|
24
|
+
ignoredPatternValues,
|
|
25
|
+
onTogglePattern
|
|
17
26
|
}) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (!visible || !event) return null;
|
|
28
|
+
return /*#__PURE__*/_jsx(NetworkEventDetailView, {
|
|
29
|
+
event: event,
|
|
30
|
+
ignoredPatterns: ignoredPatternValues,
|
|
31
|
+
onTogglePattern: onTogglePattern
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function NetworkFilterScreen({
|
|
35
|
+
visible,
|
|
36
|
+
paused,
|
|
37
|
+
isPro,
|
|
38
|
+
filter,
|
|
39
|
+
onFilterChange,
|
|
40
|
+
ignoredPatterns,
|
|
41
|
+
onRemovePattern,
|
|
42
|
+
onAddPattern,
|
|
43
|
+
onTogglePatternMode
|
|
44
|
+
}) {
|
|
45
|
+
// Facet counts read the same filtered list the user is looking at.
|
|
46
|
+
const {
|
|
47
|
+
events
|
|
48
|
+
} = useNetworkEventList({
|
|
49
|
+
filter,
|
|
50
|
+
ignoredPatterns,
|
|
51
|
+
paused: paused || !visible,
|
|
52
|
+
isPro
|
|
53
|
+
});
|
|
54
|
+
if (!visible) return null;
|
|
55
|
+
return /*#__PURE__*/_jsx(NetworkFilterViewV3, {
|
|
56
|
+
events: events,
|
|
57
|
+
filter: filter,
|
|
58
|
+
onFilterChange: onFilterChange,
|
|
59
|
+
ignoredPatterns: ignoredPatterns,
|
|
60
|
+
onRemovePattern: onRemovePattern,
|
|
61
|
+
onAddPattern: onAddPattern,
|
|
62
|
+
onTogglePatternMode: onTogglePatternMode
|
|
30
63
|
});
|
|
31
64
|
}
|
|
65
|
+
function NetworkCopyScreen({
|
|
66
|
+
visible,
|
|
67
|
+
paused,
|
|
68
|
+
isPro,
|
|
69
|
+
filter,
|
|
70
|
+
ignoredPatterns,
|
|
71
|
+
settings,
|
|
72
|
+
onSettingsChange
|
|
73
|
+
}) {
|
|
74
|
+
const {
|
|
75
|
+
events
|
|
76
|
+
} = useNetworkEventList({
|
|
77
|
+
filter,
|
|
78
|
+
ignoredPatterns,
|
|
79
|
+
paused: paused || !visible,
|
|
80
|
+
isPro
|
|
81
|
+
});
|
|
82
|
+
if (!visible) return null;
|
|
83
|
+
return /*#__PURE__*/_jsx(NetworkCopySettingsView, {
|
|
84
|
+
settings: settings,
|
|
85
|
+
onSettingsChange: onSettingsChange,
|
|
86
|
+
events: events
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Root — UI-state orchestration ONLY (rule3). No event-store subscription
|
|
92
|
+
// lives here: live data is consumed by the header leaves
|
|
93
|
+
// (NetworkHeaderLive) and the screens, so network events never re-render
|
|
94
|
+
// the modal shell/header. Profiling evidence: tool-bench 2026-07-06 —
|
|
95
|
+
// with the subscription here, 15 events re-rendered JsModal + every
|
|
96
|
+
// header button 15× and all visible rows 105×.
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
32
99
|
function NetworkModalInner({
|
|
33
100
|
visible,
|
|
34
101
|
onClose,
|
|
@@ -39,44 +106,19 @@ function NetworkModalInner({
|
|
|
39
106
|
const {
|
|
40
107
|
isPro
|
|
41
108
|
} = useFeatureGate();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
setFilter,
|
|
48
|
-
clearEvents,
|
|
49
|
-
isCapturing,
|
|
50
|
-
pauseCapturing,
|
|
51
|
-
resumeCapturing,
|
|
52
|
-
hasLockedEvents,
|
|
53
|
-
lockedEventCount,
|
|
54
|
-
isEventLocked,
|
|
55
|
-
requestLimit
|
|
56
|
-
} = useNetworkEvents({
|
|
57
|
-
isPro
|
|
58
|
-
});
|
|
59
|
-
const isEnabled = isCapturing;
|
|
60
|
-
const toggleInterception = useCallback(() => {
|
|
61
|
-
if (isCapturing) {
|
|
62
|
-
pauseCapturing();
|
|
63
|
-
} else {
|
|
64
|
-
resumeCapturing();
|
|
65
|
-
}
|
|
66
|
-
}, [isCapturing, pauseCapturing, resumeCapturing]);
|
|
67
|
-
const handleModeChange = useCallback(_mode => {
|
|
68
|
-
// Mode changes handled by JsModal
|
|
69
|
-
}, []);
|
|
109
|
+
|
|
110
|
+
// Capture pause (the power toggle's freeze-stream semantic) — gates
|
|
111
|
+
// every subscriber in this modal's tree via props.
|
|
112
|
+
const [paused, setPaused] = useState(false);
|
|
113
|
+
const [filter, setFilter] = useState({});
|
|
70
114
|
const [selectedEvent, setSelectedEvent] = useState(null);
|
|
71
115
|
const [showFilterView, setShowFilterView] = useState(false);
|
|
72
116
|
const [activeTab, setActiveTab] = useState("filters");
|
|
73
117
|
const [searchText, setSearchText] = useState("");
|
|
74
118
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
75
119
|
const searchInputRef = useRef(null);
|
|
76
|
-
// Ignored domains/URL patterns
|
|
77
|
-
// (@buoy-gg/shared-ui) so the Network tool and the Events tool stay in sync
|
|
78
|
-
// share the exact same filter logic. The four handlers below are thin wrappers
|
|
79
|
-
// exposed by the hook; `values` is the derived Set used for presence checks.
|
|
120
|
+
// Ignored domains/URL patterns live in a shared, persisted singleton store
|
|
121
|
+
// (@buoy-gg/shared-ui) so the Network tool and the Events tool stay in sync.
|
|
80
122
|
const {
|
|
81
123
|
patterns: ignoredPatterns,
|
|
82
124
|
values: ignoredPatternValues,
|
|
@@ -86,7 +128,6 @@ function NetworkModalInner({
|
|
|
86
128
|
toggleMode: toggleIgnoredPatternMode
|
|
87
129
|
} = useIgnoredPatterns();
|
|
88
130
|
const [copySettings, setCopySettings] = useState(DEFAULT_COPY_SETTINGS);
|
|
89
|
-
const flatListRef = useRef(null);
|
|
90
131
|
const hasLoadedCopySettings = useRef(false);
|
|
91
132
|
const [showUpgradeModal, setShowUpgradeModal] = useState(false);
|
|
92
133
|
|
|
@@ -131,9 +172,6 @@ function NetworkModalInner({
|
|
|
131
172
|
}, [copySettings]);
|
|
132
173
|
|
|
133
174
|
// Simple handlers - no useCallback needed per rule2
|
|
134
|
-
const handleEventPress = event => {
|
|
135
|
-
setSelectedEvent(event);
|
|
136
|
-
};
|
|
137
175
|
const handleBack = () => {
|
|
138
176
|
setSelectedEvent(null);
|
|
139
177
|
};
|
|
@@ -144,6 +182,15 @@ function NetworkModalInner({
|
|
|
144
182
|
searchText: text
|
|
145
183
|
}));
|
|
146
184
|
};
|
|
185
|
+
const toggleStatusFilter = status => {
|
|
186
|
+
setFilter(prev => ({
|
|
187
|
+
...prev,
|
|
188
|
+
status: prev.status === status ? undefined : status
|
|
189
|
+
}));
|
|
190
|
+
};
|
|
191
|
+
const handleModeChange = _mode => {
|
|
192
|
+
// Mode changes handled by JsModal
|
|
193
|
+
};
|
|
147
194
|
useEffect(() => {
|
|
148
195
|
if (isSearchActive) {
|
|
149
196
|
requestAnimationFrame(() => {
|
|
@@ -152,226 +199,10 @@ function NetworkModalInner({
|
|
|
152
199
|
}
|
|
153
200
|
}, [isSearchActive]);
|
|
154
201
|
|
|
155
|
-
// Filter events based on ignored patterns (mode-aware)
|
|
156
|
-
const filteredEvents = useMemo(() => {
|
|
157
|
-
if (ignoredPatterns.length === 0) return events;
|
|
158
|
-
return events.filter(event => {
|
|
159
|
-
const isFiltered = ignoredPatterns.some(pattern => urlMatchesIgnoredPattern(event.url, pattern));
|
|
160
|
-
return !isFiltered;
|
|
161
|
-
});
|
|
162
|
-
}, [events, ignoredPatterns]);
|
|
163
|
-
|
|
164
|
-
// Helper to check if payload should be included based on size
|
|
165
|
-
const shouldIncludePayload = useCallback(data => {
|
|
166
|
-
if (copySettings.bodySizeThreshold === -1) return true;
|
|
167
|
-
if (!data) return true;
|
|
168
|
-
try {
|
|
169
|
-
const size = JSON.stringify(data).length;
|
|
170
|
-
return size < copySettings.bodySizeThreshold * 1024;
|
|
171
|
-
} catch {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}, [copySettings.bodySizeThreshold]);
|
|
175
|
-
|
|
176
|
-
// Generate copy text based on settings
|
|
177
|
-
const generateCopyText = useCallback(() => {
|
|
178
|
-
let eventsToUse = filteredEvents;
|
|
179
|
-
|
|
180
|
-
// Apply filter mode
|
|
181
|
-
if (copySettings.filterMode === "failed") {
|
|
182
|
-
eventsToUse = eventsToUse.filter(e => e.error || e.status && e.status >= 400);
|
|
183
|
-
} else if (copySettings.filterMode === "success") {
|
|
184
|
-
eventsToUse = eventsToUse.filter(e => e.status && e.status >= 200 && e.status < 300);
|
|
185
|
-
}
|
|
186
|
-
if (eventsToUse.length === 0) {
|
|
187
|
-
return "No requests to copy";
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Plain text format (URLs only)
|
|
191
|
-
if (copySettings.format === "plaintext") {
|
|
192
|
-
return eventsToUse.map(event => `${event.method} ${event.url}`).join("\n");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// JSON format
|
|
196
|
-
if (copySettings.format === "json") {
|
|
197
|
-
const requests = eventsToUse.map(event => {
|
|
198
|
-
const obj = {};
|
|
199
|
-
if (copySettings.includeMethod) {
|
|
200
|
-
obj.method = event.method;
|
|
201
|
-
obj.url = event.url;
|
|
202
|
-
}
|
|
203
|
-
if (copySettings.includeStatus) {
|
|
204
|
-
obj.status = event.status;
|
|
205
|
-
obj.statusText = event.statusText;
|
|
206
|
-
}
|
|
207
|
-
if (copySettings.includeDuration) obj.duration = event.duration;
|
|
208
|
-
if (copySettings.includeTimestamp) obj.timestamp = event.timestamp;
|
|
209
|
-
if (copySettings.includeClient) obj.requestClient = event.requestClient;
|
|
210
|
-
if (copySettings.includeSizes) {
|
|
211
|
-
obj.requestSize = event.requestSize;
|
|
212
|
-
obj.responseSize = event.responseSize;
|
|
213
|
-
}
|
|
214
|
-
if (copySettings.includeErrors && event.error) obj.error = event.error;
|
|
215
|
-
if (copySettings.includeRequestHeaders) {
|
|
216
|
-
obj.requestHeaders = event.requestHeaders;
|
|
217
|
-
}
|
|
218
|
-
if (copySettings.includeResponseHeaders) {
|
|
219
|
-
obj.responseHeaders = event.responseHeaders;
|
|
220
|
-
}
|
|
221
|
-
if (copySettings.includeRequestBody && shouldIncludePayload(event.requestData)) {
|
|
222
|
-
obj.requestData = event.requestData;
|
|
223
|
-
}
|
|
224
|
-
if (copySettings.includeResponseBody && shouldIncludePayload(event.responseData)) {
|
|
225
|
-
obj.responseData = event.responseData;
|
|
226
|
-
}
|
|
227
|
-
return obj;
|
|
228
|
-
});
|
|
229
|
-
return JSON.stringify(requests, null, 2);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Markdown format
|
|
233
|
-
const requests = eventsToUse.map((event, index) => {
|
|
234
|
-
let md = `# Request ${index + 1}\n\n`;
|
|
235
|
-
if (copySettings.includeMethod) {
|
|
236
|
-
md += `**Method:** ${event.method}\n`;
|
|
237
|
-
md += `**URL:** ${event.url}\n`;
|
|
238
|
-
}
|
|
239
|
-
if (copySettings.includeStatus) {
|
|
240
|
-
const status = event.status || "Pending";
|
|
241
|
-
md += `**Status:** ${status}${event.statusText ? ` (${event.statusText})` : ""}\n`;
|
|
242
|
-
}
|
|
243
|
-
if (copySettings.includeClient && event.requestClient) {
|
|
244
|
-
md += `**Client:** ${event.requestClient}\n`;
|
|
245
|
-
}
|
|
246
|
-
if (copySettings.includeDuration && event.duration) {
|
|
247
|
-
md += `**Duration:** ${event.duration}ms\n`;
|
|
248
|
-
}
|
|
249
|
-
if (copySettings.includeTimestamp) {
|
|
250
|
-
md += `**Timestamp:** ${new Date(event.timestamp).toISOString()}\n`;
|
|
251
|
-
}
|
|
252
|
-
if (copySettings.includeSizes) {
|
|
253
|
-
const reqSize = event.requestSize ? formatBytes(event.requestSize) : "N/A";
|
|
254
|
-
const resSize = event.responseSize ? formatBytes(event.responseSize) : "N/A";
|
|
255
|
-
md += `**Request Size:** ${reqSize}\n`;
|
|
256
|
-
md += `**Response Size:** ${resSize}\n`;
|
|
257
|
-
}
|
|
258
|
-
if (copySettings.includeErrors && event.error) {
|
|
259
|
-
md += `**Error:** ${event.error}\n`;
|
|
260
|
-
}
|
|
261
|
-
if (copySettings.includeRequestHeaders && Object.keys(event.requestHeaders).length > 0) {
|
|
262
|
-
md += `\n## Request Headers\n\`\`\`json\n${JSON.stringify(event.requestHeaders, null, 2)}\n\`\`\`\n`;
|
|
263
|
-
}
|
|
264
|
-
if (copySettings.includeRequestBody) {
|
|
265
|
-
md += `\n## Request Data\n`;
|
|
266
|
-
if (shouldIncludePayload(event.requestData)) {
|
|
267
|
-
md += `\`\`\`json\n${JSON.stringify(event.requestData, null, 2)}\n\`\`\`\n`;
|
|
268
|
-
} else {
|
|
269
|
-
const size = event.requestSize ? formatBytes(event.requestSize) : "Unknown size";
|
|
270
|
-
md += `_Payload omitted (${size}). Exceeds ${copySettings.bodySizeThreshold}KB threshold._\n`;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (copySettings.includeResponseHeaders && Object.keys(event.responseHeaders).length > 0) {
|
|
274
|
-
md += `\n## Response Headers\n\`\`\`json\n${JSON.stringify(event.responseHeaders, null, 2)}\n\`\`\`\n`;
|
|
275
|
-
}
|
|
276
|
-
if (copySettings.includeResponseBody) {
|
|
277
|
-
md += `\n## Response Data\n`;
|
|
278
|
-
if (shouldIncludePayload(event.responseData)) {
|
|
279
|
-
md += `\`\`\`json\n${JSON.stringify(event.responseData, null, 2)}\n\`\`\`\n`;
|
|
280
|
-
} else {
|
|
281
|
-
const size = event.responseSize ? formatBytes(event.responseSize) : "Unknown size";
|
|
282
|
-
md += `_Payload omitted (${size}). Exceeds ${copySettings.bodySizeThreshold}KB threshold._\n`;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
md += `\n---\n`;
|
|
286
|
-
return md;
|
|
287
|
-
}).join("\n");
|
|
288
|
-
const header = `# Network Requests (${eventsToUse.length} total)\n\n`;
|
|
289
|
-
return header + requests;
|
|
290
|
-
}, [filteredEvents, copySettings, shouldIncludePayload]);
|
|
291
|
-
|
|
292
|
-
// Memoized copy text value for CopyButton
|
|
293
|
-
const copyTextValue = useMemo(() => {
|
|
294
|
-
if (filteredEvents.length === 0) return "";
|
|
295
|
-
return generateCopyText();
|
|
296
|
-
}, [filteredEvents.length, generateCopyText]);
|
|
297
|
-
|
|
298
|
-
// Compute badge stats from all events (before status filtering) with ignored patterns applied.
|
|
299
|
-
// This ensures counts stay stable when toggling between status filters.
|
|
300
|
-
const badgeStats = useMemo(() => {
|
|
301
|
-
let successful = 0;
|
|
302
|
-
let failed = 0;
|
|
303
|
-
let pending = 0;
|
|
304
|
-
const eventsForStats = ignoredPatterns.length === 0 ? allEvents : allEvents.filter(event => !ignoredPatterns.some(pattern => urlMatchesIgnoredPattern(event.url, pattern)));
|
|
305
|
-
for (const event of eventsForStats) {
|
|
306
|
-
if (event.status && event.status >= 200 && event.status < 300) {
|
|
307
|
-
successful++;
|
|
308
|
-
} else if (event.error || event.status && event.status >= 400) {
|
|
309
|
-
failed++;
|
|
310
|
-
} else if (!event.status && !event.error) {
|
|
311
|
-
pending++;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return {
|
|
315
|
-
successful,
|
|
316
|
-
failed,
|
|
317
|
-
pending
|
|
318
|
-
};
|
|
319
|
-
}, [allEvents, ignoredPatterns]);
|
|
320
|
-
|
|
321
|
-
// FlatList optimization - only keep what's needed for FlatList performance
|
|
322
|
-
const keyExtractor = item => item.id;
|
|
323
|
-
|
|
324
|
-
// Filter to only show unlocked events in the list
|
|
325
|
-
const unlockedEvents = useMemo(() => {
|
|
326
|
-
return filteredEvents.filter(event => !isEventLocked(event.id));
|
|
327
|
-
}, [filteredEvents, isEventLocked]);
|
|
328
|
-
|
|
329
|
-
// Keep renderItem memoized for FlatList performance (justified by FlatList docs)
|
|
330
|
-
const renderItem = useMemo(() => {
|
|
331
|
-
return ({
|
|
332
|
-
item
|
|
333
|
-
}) => {
|
|
334
|
-
return /*#__PURE__*/_jsx(NetworkEventItemCompact, {
|
|
335
|
-
event: item,
|
|
336
|
-
onPress: handleEventPress
|
|
337
|
-
});
|
|
338
|
-
};
|
|
339
|
-
}, []); // handleEventPress defined inline
|
|
340
|
-
|
|
341
|
-
// Footer component showing upgrade banner when events are locked
|
|
342
|
-
const renderListFooter = useCallback(() => {
|
|
343
|
-
if (!hasLockedEvents) return null;
|
|
344
|
-
const isSearching = searchText.length > 0;
|
|
345
|
-
const bannerText = isSearching ? `${lockedEventCount} matching ${lockedEventCount === 1 ? 'request' : 'requests'} in locked history` : `${lockedEventCount} requests locked`;
|
|
346
|
-
const subtextMessage = isSearching ? "Upgrade to Pro to search your full request history" : "Upgrade to Pro to unlock full request history";
|
|
347
|
-
return /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
348
|
-
style: styles.limitBanner,
|
|
349
|
-
onPress: () => setShowUpgradeModal(true),
|
|
350
|
-
activeOpacity: 0.8,
|
|
351
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
352
|
-
style: styles.limitBannerContent,
|
|
353
|
-
children: [/*#__PURE__*/_jsxs(View, {
|
|
354
|
-
style: styles.limitBannerLeft,
|
|
355
|
-
children: [/*#__PURE__*/_jsx(Zap, {
|
|
356
|
-
size: 16,
|
|
357
|
-
color: "#20C997"
|
|
358
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
359
|
-
style: styles.limitBannerText,
|
|
360
|
-
children: bannerText
|
|
361
|
-
})]
|
|
362
|
-
}), /*#__PURE__*/_jsx(ProBadge, {})]
|
|
363
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
364
|
-
style: styles.limitBannerSubtext,
|
|
365
|
-
children: subtextMessage
|
|
366
|
-
})]
|
|
367
|
-
});
|
|
368
|
-
}, [hasLockedEvents, lockedEventCount, searchText]);
|
|
369
|
-
|
|
370
202
|
// Compact header with actions (like Sentry/Storage modals)
|
|
371
203
|
const renderHeaderContent = () => {
|
|
372
204
|
// Filter view header with tabs
|
|
373
205
|
if (showFilterView) {
|
|
374
|
-
// Copy tab is always visible and clickable - gating handled inside the view
|
|
375
206
|
const tabs = [{
|
|
376
207
|
key: "filters",
|
|
377
208
|
label: "Filters"
|
|
@@ -406,7 +237,9 @@ function NetworkModalInner({
|
|
|
406
237
|
});
|
|
407
238
|
}
|
|
408
239
|
|
|
409
|
-
// Main list view header with search and filters
|
|
240
|
+
// Main list view header with search and filters. The live pieces
|
|
241
|
+
// (count chips, copy, trash) are self-subscribing leaves — this
|
|
242
|
+
// header function itself only re-runs on user interaction.
|
|
410
243
|
return /*#__PURE__*/_jsxs(ModalHeader, {
|
|
411
244
|
children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
412
245
|
onBack: onBack
|
|
@@ -443,54 +276,11 @@ function NetworkModalInner({
|
|
|
443
276
|
color: macOSColors.text.secondary
|
|
444
277
|
})
|
|
445
278
|
}) : null]
|
|
446
|
-
}) : /*#__PURE__*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
...filter,
|
|
452
|
-
status: filter.status === "success" ? undefined : "success"
|
|
453
|
-
}),
|
|
454
|
-
children: [/*#__PURE__*/_jsx(CheckCircle, {
|
|
455
|
-
size: 12,
|
|
456
|
-
color: macOSColors.semantic.success
|
|
457
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
458
|
-
style: [styles.headerChipValue, {
|
|
459
|
-
color: macOSColors.semantic.success
|
|
460
|
-
}],
|
|
461
|
-
children: badgeStats.successful
|
|
462
|
-
})]
|
|
463
|
-
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
464
|
-
style: [styles.headerChip, filter.status === "error" && styles.headerChipActive],
|
|
465
|
-
onPress: () => setFilter({
|
|
466
|
-
...filter,
|
|
467
|
-
status: filter.status === "error" ? undefined : "error"
|
|
468
|
-
}),
|
|
469
|
-
children: [/*#__PURE__*/_jsx(XCircle, {
|
|
470
|
-
size: 12,
|
|
471
|
-
color: macOSColors.semantic.error
|
|
472
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
473
|
-
style: [styles.headerChipValue, {
|
|
474
|
-
color: macOSColors.semantic.error
|
|
475
|
-
}],
|
|
476
|
-
children: badgeStats.failed
|
|
477
|
-
})]
|
|
478
|
-
}), /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
479
|
-
style: [styles.headerChip, filter.status === "pending" && styles.headerChipActive],
|
|
480
|
-
onPress: () => setFilter({
|
|
481
|
-
...filter,
|
|
482
|
-
status: filter.status === "pending" ? undefined : "pending"
|
|
483
|
-
}),
|
|
484
|
-
children: [/*#__PURE__*/_jsx(Clock, {
|
|
485
|
-
size: 12,
|
|
486
|
-
color: macOSColors.semantic.warning
|
|
487
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
488
|
-
style: [styles.headerChipValue, {
|
|
489
|
-
color: macOSColors.semantic.warning
|
|
490
|
-
}],
|
|
491
|
-
children: badgeStats.pending
|
|
492
|
-
})]
|
|
493
|
-
})]
|
|
279
|
+
}) : /*#__PURE__*/_jsx(NetworkHeaderBadges, {
|
|
280
|
+
activeStatus: filter.status,
|
|
281
|
+
onToggleStatus: toggleStatusFilter,
|
|
282
|
+
ignoredPatterns: ignoredPatterns,
|
|
283
|
+
paused: paused
|
|
494
284
|
})
|
|
495
285
|
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
496
286
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
@@ -511,44 +301,31 @@ function NetworkModalInner({
|
|
|
511
301
|
size: 14,
|
|
512
302
|
color: filter.status || filter.method || filter.contentType ? macOSColors.semantic.info : macOSColors.text.muted
|
|
513
303
|
})
|
|
514
|
-
}),
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
error: macOSColors.semantic.error
|
|
522
|
-
},
|
|
523
|
-
disabled: filteredEvents.length === 0
|
|
524
|
-
}) : /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
525
|
-
"sentry-label": "ignore copy pro",
|
|
526
|
-
onPress: () => setShowUpgradeModal(true),
|
|
527
|
-
style: [styles.headerActionButton, filteredEvents.length === 0 && styles.headerActionButtonDisabled],
|
|
528
|
-
disabled: filteredEvents.length === 0,
|
|
529
|
-
children: /*#__PURE__*/_jsx(Copy, {
|
|
530
|
-
size: 14,
|
|
531
|
-
color: filteredEvents.length > 0 ? macOSColors.text.secondary : macOSColors.text.disabled
|
|
532
|
-
})
|
|
304
|
+
}), /*#__PURE__*/_jsx(NetworkHeaderCopyButton, {
|
|
305
|
+
isPro: isPro,
|
|
306
|
+
filter: filter,
|
|
307
|
+
ignoredPatterns: ignoredPatterns,
|
|
308
|
+
copySettings: copySettings,
|
|
309
|
+
paused: paused,
|
|
310
|
+
onUpgradePress: () => setShowUpgradeModal(true)
|
|
533
311
|
}), /*#__PURE__*/_jsx(PowerToggleButton, {
|
|
534
|
-
isEnabled:
|
|
535
|
-
onToggle:
|
|
312
|
+
isEnabled: !paused,
|
|
313
|
+
onToggle: () => setPaused(prev => !prev),
|
|
536
314
|
accessibilityLabel: "Toggle network interception"
|
|
537
|
-
}), /*#__PURE__*/_jsx(
|
|
538
|
-
|
|
539
|
-
onPress: clearEvents,
|
|
540
|
-
style: [styles.headerActionButton, events.length === 0 && styles.headerActionButtonDisabled],
|
|
541
|
-
disabled: events.length === 0,
|
|
542
|
-
children: /*#__PURE__*/_jsx(Trash2, {
|
|
543
|
-
size: 14,
|
|
544
|
-
color: events.length > 0 ? macOSColors.text.muted : macOSColors.text.disabled
|
|
545
|
-
})
|
|
315
|
+
}), /*#__PURE__*/_jsx(NetworkClearButton, {
|
|
316
|
+
paused: paused
|
|
546
317
|
})]
|
|
547
318
|
})]
|
|
548
319
|
});
|
|
549
320
|
};
|
|
550
321
|
const persistenceKey = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : devToolsStorageKeys.network.modal();
|
|
551
322
|
if (!visible) return null;
|
|
323
|
+
|
|
324
|
+
// The LIST screen owns its scrolling (FlatList virtualization requires
|
|
325
|
+
// it — inside JsModal's ScrollView wrapper every row mounts, which at
|
|
326
|
+
// the 500-event cap measured 17 JS FPS / 133% CPU / 1.4GB; see
|
|
327
|
+
// NetworkListScreen). Detail/filter/copy still use the wrapper.
|
|
328
|
+
const listOwnsScroll = selectedEvent == null && !showFilterView;
|
|
552
329
|
return /*#__PURE__*/_jsxs(JsModal, {
|
|
553
330
|
visible: visible,
|
|
554
331
|
onClose: onClose,
|
|
@@ -562,58 +339,43 @@ function NetworkModalInner({
|
|
|
562
339
|
enablePersistence: true,
|
|
563
340
|
initialMode: "bottomSheet",
|
|
564
341
|
enableGlitchEffects: true,
|
|
342
|
+
disableScrollWrapper: listOwnsScroll,
|
|
565
343
|
styles: {},
|
|
566
|
-
children: [/*#__PURE__*/
|
|
344
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
567
345
|
style: styles.container,
|
|
568
|
-
children:
|
|
569
|
-
|
|
570
|
-
/* Show detail view if event is selected */
|
|
571
|
-
_jsx(NetworkEventDetailView, {
|
|
346
|
+
children: [/*#__PURE__*/_jsx(NetworkDetailScreen, {
|
|
347
|
+
visible: selectedEvent != null,
|
|
572
348
|
event: selectedEvent,
|
|
573
|
-
|
|
349
|
+
ignoredPatternValues: ignoredPatternValues,
|
|
574
350
|
onTogglePattern: toggleIgnoredPattern
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
events: filteredEvents
|
|
580
|
-
}) : /*#__PURE__*/_jsx(NetworkFilterViewV3, {
|
|
581
|
-
events: filteredEvents,
|
|
351
|
+
}), /*#__PURE__*/_jsx(NetworkFilterScreen, {
|
|
352
|
+
visible: showFilterView && activeTab === "filters" && !selectedEvent,
|
|
353
|
+
paused: paused,
|
|
354
|
+
isPro: isPro,
|
|
582
355
|
filter: filter,
|
|
583
356
|
onFilterChange: setFilter,
|
|
584
357
|
ignoredPatterns: ignoredPatterns,
|
|
585
358
|
onRemovePattern: removeIgnoredPattern,
|
|
586
359
|
onAddPattern: addIgnoredPattern,
|
|
587
360
|
onTogglePatternMode: toggleIgnoredPatternMode
|
|
588
|
-
})
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
onEndReachedThreshold: 0.8,
|
|
607
|
-
initialNumToRender: 10,
|
|
608
|
-
maxToRenderPerBatch: 10,
|
|
609
|
-
windowSize: 10,
|
|
610
|
-
scrollEnabled: false,
|
|
611
|
-
"sentry-label": "ignore network events list",
|
|
612
|
-
ListFooterComponent: renderListFooter
|
|
613
|
-
}) : /*#__PURE__*/_jsx(EmptyState, {
|
|
614
|
-
isEnabled: isEnabled
|
|
615
|
-
})]
|
|
616
|
-
})
|
|
361
|
+
}), /*#__PURE__*/_jsx(NetworkCopyScreen, {
|
|
362
|
+
visible: showFilterView && activeTab === "copy" && !selectedEvent,
|
|
363
|
+
paused: paused,
|
|
364
|
+
isPro: isPro,
|
|
365
|
+
filter: filter,
|
|
366
|
+
ignoredPatterns: ignoredPatterns,
|
|
367
|
+
settings: copySettings,
|
|
368
|
+
onSettingsChange: setCopySettings
|
|
369
|
+
}), /*#__PURE__*/_jsx(NetworkListScreen, {
|
|
370
|
+
visible: selectedEvent == null && !showFilterView,
|
|
371
|
+
paused: paused,
|
|
372
|
+
filter: filter,
|
|
373
|
+
ignoredPatterns: ignoredPatterns,
|
|
374
|
+
isPro: isPro,
|
|
375
|
+
searchText: searchText,
|
|
376
|
+
onEventPress: setSelectedEvent,
|
|
377
|
+
onUpgradePress: () => setShowUpgradeModal(true)
|
|
378
|
+
})]
|
|
617
379
|
}), /*#__PURE__*/_jsx(UpgradeModal, {
|
|
618
380
|
visible: showUpgradeModal,
|
|
619
381
|
onClose: () => setShowUpgradeModal(false),
|
|
@@ -628,46 +390,6 @@ const styles = StyleSheet.create({
|
|
|
628
390
|
flex: 1,
|
|
629
391
|
backgroundColor: macOSColors.background.base
|
|
630
392
|
},
|
|
631
|
-
// Compact header styles matching Sentry/Storage modals
|
|
632
|
-
headerContainer: {
|
|
633
|
-
flexDirection: "row",
|
|
634
|
-
alignItems: "center",
|
|
635
|
-
flex: 1,
|
|
636
|
-
gap: 8,
|
|
637
|
-
minHeight: 32,
|
|
638
|
-
paddingLeft: 4
|
|
639
|
-
},
|
|
640
|
-
headerTitle: {
|
|
641
|
-
color: macOSColors.text.primary,
|
|
642
|
-
fontSize: 14,
|
|
643
|
-
fontWeight: "500",
|
|
644
|
-
flex: 1,
|
|
645
|
-
marginLeft: 8
|
|
646
|
-
},
|
|
647
|
-
headerStats: {
|
|
648
|
-
display: "none"
|
|
649
|
-
},
|
|
650
|
-
headerStatsText: {
|
|
651
|
-
fontSize: 12,
|
|
652
|
-
color: macOSColors.text.muted,
|
|
653
|
-
fontWeight: "500"
|
|
654
|
-
},
|
|
655
|
-
headerFilteredText: {
|
|
656
|
-
fontSize: 11,
|
|
657
|
-
color: macOSColors.semantic.warning,
|
|
658
|
-
fontWeight: "500",
|
|
659
|
-
marginLeft: 4
|
|
660
|
-
},
|
|
661
|
-
headerActions: {
|
|
662
|
-
flexDirection: "row",
|
|
663
|
-
gap: 6,
|
|
664
|
-
marginLeft: "auto",
|
|
665
|
-
marginRight: 4
|
|
666
|
-
},
|
|
667
|
-
headerCenterArea: {
|
|
668
|
-
flex: 1,
|
|
669
|
-
marginHorizontal: 8
|
|
670
|
-
},
|
|
671
393
|
headerSearchContainer: {
|
|
672
394
|
flexDirection: "row",
|
|
673
395
|
alignItems: "center",
|
|
@@ -689,39 +411,6 @@ const styles = StyleSheet.create({
|
|
|
689
411
|
marginLeft: 6,
|
|
690
412
|
padding: 4
|
|
691
413
|
},
|
|
692
|
-
headerChipRow: {
|
|
693
|
-
flexDirection: "row",
|
|
694
|
-
alignItems: "center",
|
|
695
|
-
gap: 8
|
|
696
|
-
},
|
|
697
|
-
headerChip: {
|
|
698
|
-
flexDirection: "row",
|
|
699
|
-
alignItems: "center",
|
|
700
|
-
gap: 4,
|
|
701
|
-
backgroundColor: macOSColors.background.hover,
|
|
702
|
-
paddingHorizontal: 10,
|
|
703
|
-
paddingVertical: 5,
|
|
704
|
-
borderRadius: 12,
|
|
705
|
-
borderWidth: 1,
|
|
706
|
-
borderColor: macOSColors.border.default
|
|
707
|
-
},
|
|
708
|
-
headerChipActive: {
|
|
709
|
-
backgroundColor: macOSColors.semantic.infoBackground,
|
|
710
|
-
borderColor: macOSColors.semantic.info + "50",
|
|
711
|
-
shadowColor: macOSColors.semantic.info,
|
|
712
|
-
shadowOffset: {
|
|
713
|
-
width: 0,
|
|
714
|
-
height: 1
|
|
715
|
-
},
|
|
716
|
-
shadowOpacity: 0.08,
|
|
717
|
-
shadowRadius: 2,
|
|
718
|
-
elevation: 1
|
|
719
|
-
},
|
|
720
|
-
headerChipValue: {
|
|
721
|
-
fontSize: 12,
|
|
722
|
-
fontWeight: "600",
|
|
723
|
-
fontFamily: "monospace"
|
|
724
|
-
},
|
|
725
414
|
headerActionButton: {
|
|
726
415
|
width: 32,
|
|
727
416
|
height: 32,
|
|
@@ -732,175 +421,9 @@ const styles = StyleSheet.create({
|
|
|
732
421
|
alignItems: "center",
|
|
733
422
|
justifyContent: "center"
|
|
734
423
|
},
|
|
735
|
-
headerActionButtonDisabled: {
|
|
736
|
-
opacity: 0.55
|
|
737
|
-
},
|
|
738
|
-
// Shared navbar styles (matching React Query modal)
|
|
739
|
-
tabNavigationContainer: {
|
|
740
|
-
flexDirection: "row",
|
|
741
|
-
backgroundColor: macOSColors.background.card,
|
|
742
|
-
borderRadius: 6,
|
|
743
|
-
padding: 2,
|
|
744
|
-
borderWidth: 1,
|
|
745
|
-
borderColor: macOSColors.border.default,
|
|
746
|
-
justifyContent: "space-evenly",
|
|
747
|
-
flex: 1,
|
|
748
|
-
marginLeft: 8,
|
|
749
|
-
marginRight: 8
|
|
750
|
-
},
|
|
751
|
-
tabButton: {
|
|
752
|
-
paddingHorizontal: 8,
|
|
753
|
-
paddingVertical: 5,
|
|
754
|
-
borderRadius: 4,
|
|
755
|
-
alignItems: "center",
|
|
756
|
-
justifyContent: "center",
|
|
757
|
-
flex: 1,
|
|
758
|
-
marginHorizontal: 1
|
|
759
|
-
},
|
|
760
|
-
tabButtonActive: {
|
|
761
|
-
backgroundColor: macOSColors.semantic.infoBackground,
|
|
762
|
-
borderWidth: 1,
|
|
763
|
-
borderColor: macOSColors.semantic.info + "40"
|
|
764
|
-
},
|
|
765
|
-
tabButtonInactive: {
|
|
766
|
-
backgroundColor: "transparent"
|
|
767
|
-
},
|
|
768
|
-
tabButtonText: {
|
|
769
|
-
fontSize: 12,
|
|
770
|
-
fontWeight: "600",
|
|
771
|
-
letterSpacing: 0.5,
|
|
772
|
-
fontFamily: "monospace",
|
|
773
|
-
textTransform: "uppercase"
|
|
774
|
-
},
|
|
775
|
-
tabButtonTextActive: {
|
|
776
|
-
color: macOSColors.semantic.info
|
|
777
|
-
},
|
|
778
|
-
tabButtonTextInactive: {
|
|
779
|
-
color: macOSColors.text.muted
|
|
780
|
-
},
|
|
781
|
-
startButton: {
|
|
782
|
-
backgroundColor: macOSColors.semantic.successBackground,
|
|
783
|
-
borderColor: macOSColors.semantic.success + "40"
|
|
784
|
-
},
|
|
785
|
-
stopButton: {
|
|
786
|
-
backgroundColor: macOSColors.semantic.errorBackground,
|
|
787
|
-
borderColor: macOSColors.semantic.error + "40"
|
|
788
|
-
},
|
|
789
424
|
activeFilterButton: {
|
|
790
425
|
backgroundColor: macOSColors.semantic.infoBackground,
|
|
791
426
|
borderColor: macOSColors.semantic.info + "40"
|
|
792
|
-
},
|
|
793
|
-
activeIgnoreButton: {
|
|
794
|
-
backgroundColor: macOSColors.semantic.warningBackground,
|
|
795
|
-
borderColor: macOSColors.semantic.warning + "33"
|
|
796
|
-
},
|
|
797
|
-
detailHeaderActions: {
|
|
798
|
-
flexDirection: "row",
|
|
799
|
-
gap: 6,
|
|
800
|
-
marginLeft: "auto",
|
|
801
|
-
marginRight: 4
|
|
802
|
-
},
|
|
803
|
-
// Search bar - minimal design with theme colors
|
|
804
|
-
searchContainer: {
|
|
805
|
-
display: "none"
|
|
806
|
-
},
|
|
807
|
-
searchInput: {},
|
|
808
|
-
// Stats bar - minimal design
|
|
809
|
-
statsBar: {
|
|
810
|
-
display: "none"
|
|
811
|
-
},
|
|
812
|
-
statChip: {
|
|
813
|
-
flexDirection: "row",
|
|
814
|
-
alignItems: "center",
|
|
815
|
-
gap: 4,
|
|
816
|
-
backgroundColor: macOSColors.background.hover,
|
|
817
|
-
paddingHorizontal: 8,
|
|
818
|
-
paddingVertical: 4,
|
|
819
|
-
borderRadius: 12,
|
|
820
|
-
borderWidth: 1,
|
|
821
|
-
borderColor: "transparent"
|
|
822
|
-
},
|
|
823
|
-
statChipActive: {
|
|
824
|
-
backgroundColor: macOSColors.semantic.info + "26",
|
|
825
|
-
borderColor: macOSColors.semantic.info + "66"
|
|
826
|
-
},
|
|
827
|
-
statValue: {
|
|
828
|
-
fontSize: 14,
|
|
829
|
-
fontWeight: "600",
|
|
830
|
-
fontFamily: "monospace"
|
|
831
|
-
},
|
|
832
|
-
statLabel: {
|
|
833
|
-
fontSize: 10,
|
|
834
|
-
color: macOSColors.text.muted,
|
|
835
|
-
fontWeight: "500",
|
|
836
|
-
textTransform: "uppercase"
|
|
837
|
-
},
|
|
838
|
-
disabledBanner: {
|
|
839
|
-
flexDirection: "row",
|
|
840
|
-
alignItems: "center",
|
|
841
|
-
gap: 8,
|
|
842
|
-
padding: 10,
|
|
843
|
-
marginHorizontal: 12,
|
|
844
|
-
marginTop: 8,
|
|
845
|
-
backgroundColor: macOSColors.semantic.warningBackground,
|
|
846
|
-
borderRadius: 8,
|
|
847
|
-
borderWidth: 1,
|
|
848
|
-
borderColor: macOSColors.semantic.warning + "20"
|
|
849
|
-
},
|
|
850
|
-
disabledText: {
|
|
851
|
-
color: macOSColors.semantic.warning,
|
|
852
|
-
fontSize: 11,
|
|
853
|
-
flex: 1
|
|
854
|
-
},
|
|
855
|
-
listContent: {
|
|
856
|
-
paddingTop: 8
|
|
857
|
-
},
|
|
858
|
-
emptyState: {
|
|
859
|
-
alignItems: "center",
|
|
860
|
-
paddingVertical: 40
|
|
861
|
-
},
|
|
862
|
-
emptyTitle: {
|
|
863
|
-
color: macOSColors.text.primary,
|
|
864
|
-
fontSize: 14,
|
|
865
|
-
fontWeight: "600",
|
|
866
|
-
marginTop: 12,
|
|
867
|
-
marginBottom: 6
|
|
868
|
-
},
|
|
869
|
-
emptyText: {
|
|
870
|
-
color: macOSColors.text.muted,
|
|
871
|
-
fontSize: 12,
|
|
872
|
-
textAlign: "center"
|
|
873
|
-
},
|
|
874
|
-
// Free tier limit banner
|
|
875
|
-
limitBanner: {
|
|
876
|
-
backgroundColor: "#1A1A1A",
|
|
877
|
-
borderRadius: 10,
|
|
878
|
-
borderWidth: 1,
|
|
879
|
-
borderColor: "#20C997" + "40",
|
|
880
|
-
padding: 12,
|
|
881
|
-
marginHorizontal: 12,
|
|
882
|
-
marginTop: 12,
|
|
883
|
-
marginBottom: 34
|
|
884
|
-
},
|
|
885
|
-
limitBannerContent: {
|
|
886
|
-
flexDirection: "row",
|
|
887
|
-
alignItems: "center",
|
|
888
|
-
justifyContent: "space-between",
|
|
889
|
-
marginBottom: 4
|
|
890
|
-
},
|
|
891
|
-
limitBannerLeft: {
|
|
892
|
-
flexDirection: "row",
|
|
893
|
-
alignItems: "center",
|
|
894
|
-
gap: 8
|
|
895
|
-
},
|
|
896
|
-
limitBannerText: {
|
|
897
|
-
color: "#E0E0E0",
|
|
898
|
-
fontSize: 13,
|
|
899
|
-
fontWeight: "600"
|
|
900
|
-
},
|
|
901
|
-
limitBannerSubtext: {
|
|
902
|
-
color: "#888888",
|
|
903
|
-
fontSize: 11
|
|
904
427
|
}
|
|
905
428
|
});
|
|
906
429
|
|