@buoy-gg/network 7.0.10 → 7.0.12
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 +1 -0
- package/lib/commonjs/network/components/NetworkDetailStepper.js +94 -0
- package/lib/commonjs/network/components/NetworkModal.js +66 -11
- package/lib/commonjs/network/components/NetworkSavedScreen.js +12 -15
- package/lib/commonjs/network/utils/selectSavedEvents.js +34 -0
- package/lib/module/network/components/NetworkDetailStepper.js +90 -0
- package/lib/module/network/components/NetworkModal.js +66 -11
- package/lib/module/network/components/NetworkSavedScreen.js +13 -16
- package/lib/module/network/utils/selectSavedEvents.js +30 -0
- package/lib/typescript/network/components/NetworkDetailStepper.d.ts +39 -0
- package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkSavedScreen.d.ts +12 -1
- package/lib/typescript/network/components/NetworkSavedScreen.d.ts.map +1 -1
- package/lib/typescript/network/utils/selectSavedEvents.d.ts +19 -0
- package/lib/typescript/network/utils/selectSavedEvents.d.ts.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Want a different label or color? Build the tool with `createNetworkTool({ name,
|
|
|
40
40
|
- **Full request detail** — request/response headers, payloads, HTTP status, and duration for every call, with status-colored rows.
|
|
41
41
|
- **Filter and search** — narrow by status code, HTTP method, or URL pattern; search captured events; pause/resume capture; clear history.
|
|
42
42
|
- **Pin the one that failed** — pin a request to hold it at the top of the list (ignoring your filters), or save it to a separate favorites list. Both keep a full snapshot, so it survives Clear, the 500-request cap, and an app restart. Long-press a row, or use the buttons in the request detail header.
|
|
43
|
+
- **Step between requests** — the detail view has a Previous/Next footer that walks exactly what the list was showing (same pins, filters, and search — or your Saved list), so comparing two calls doesn't mean going back and finding your place.
|
|
43
44
|
- **Ignore the noise** — a shared ignored-domains/URL filter (`useIgnoredPatterns`) hides endpoints you never want to see, shared with the Events tool.
|
|
44
45
|
- **Hooks for custom UIs** — `useNetworkEvents`, `useNetworkEventList`, `useNetworkBadgeCounts`, and `useNetworkSaved` expose the capture stream with narrow per-consumer subscriptions.
|
|
45
46
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NetworkDetailStepper = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
|
+
var _useNetworkEventList = require("../hooks/useNetworkEventList");
|
|
10
|
+
var _useNetworkSaved = require("../hooks/useNetworkSaved");
|
|
11
|
+
var _withPinnedFirst = require("../utils/withPinnedFirst");
|
|
12
|
+
var _selectSavedEvents = require("../utils/selectSavedEvents");
|
|
13
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
|
+
/**
|
|
15
|
+
* Previous/Next footer for the request DETAIL screen — step through requests
|
|
16
|
+
* without bouncing back to the list.
|
|
17
|
+
*
|
|
18
|
+
* It is its OWN leaf (rule3) because it is the only part of the detail screen
|
|
19
|
+
* that needs the event LIST. Subscribing here keeps the request stream out of
|
|
20
|
+
* the detail body's render path, which is the expensive subtree (headers,
|
|
21
|
+
* bodies, JSON viewers, diff views): a request arriving re-renders this
|
|
22
|
+
* ~30-node footer and nothing else.
|
|
23
|
+
*
|
|
24
|
+
* The order it steps through is EXACTLY what the list screen renders —
|
|
25
|
+
* `[...pinned, ...rest]` off the same `useNetworkEventList` + `splitPinned`
|
|
26
|
+
* derivation — so "Next" always lands on the row below the one the user
|
|
27
|
+
* tapped. Because the live filter is an input, typing in the search box or
|
|
28
|
+
* flipping a status chip re-scopes the stepper in place; the counter is
|
|
29
|
+
* "request N of <what the list is showing>", never "N of everything ever
|
|
30
|
+
* captured". Detail opened from the Saved screen steps through the SAVED
|
|
31
|
+
* list instead (with its own search applied), for the same reason.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const NetworkDetailStepper = exports.NetworkDetailStepper = /*#__PURE__*/(0, _react.memo)(function NetworkDetailStepper({
|
|
35
|
+
eventId,
|
|
36
|
+
paused,
|
|
37
|
+
filter,
|
|
38
|
+
ignoredPatterns,
|
|
39
|
+
isPro,
|
|
40
|
+
fromSaved,
|
|
41
|
+
savedSearch,
|
|
42
|
+
onSelect,
|
|
43
|
+
applySafeAreaInset
|
|
44
|
+
}) {
|
|
45
|
+
const {
|
|
46
|
+
visibleEvents
|
|
47
|
+
} = (0, _useNetworkEventList.useNetworkEventList)({
|
|
48
|
+
filter,
|
|
49
|
+
ignoredPatterns,
|
|
50
|
+
paused,
|
|
51
|
+
isPro
|
|
52
|
+
});
|
|
53
|
+
const {
|
|
54
|
+
pinnedEvents,
|
|
55
|
+
pinnedLiveIds,
|
|
56
|
+
savedRecords
|
|
57
|
+
} = (0, _useNetworkSaved.useNetworkSaved)();
|
|
58
|
+
const ordered = (0, _react.useMemo)(() => {
|
|
59
|
+
if (fromSaved) {
|
|
60
|
+
return (0, _selectSavedEvents.selectSavedEvents)(savedRecords, isPro, savedSearch).events;
|
|
61
|
+
}
|
|
62
|
+
// Same split the list screen does — pins render above the FlatList, so
|
|
63
|
+
// they come first here too.
|
|
64
|
+
const {
|
|
65
|
+
pinned,
|
|
66
|
+
rest
|
|
67
|
+
} = (0, _withPinnedFirst.splitPinned)(visibleEvents, pinnedEvents, pinnedLiveIds);
|
|
68
|
+
return pinned.length === 0 ? rest : [...pinned, ...rest];
|
|
69
|
+
}, [fromSaved, savedRecords, savedSearch, isPro, visibleEvents, pinnedEvents, pinnedLiveIds]);
|
|
70
|
+
const index = ordered.findIndex(event => event.id === eventId);
|
|
71
|
+
|
|
72
|
+
// Not in the list at all: the filter changed under it while it was open, it
|
|
73
|
+
// aged past the 500-request cap, or it is a saved snapshot with no live twin.
|
|
74
|
+
// There is no coherent "next" in that state, so show no footer rather than a
|
|
75
|
+
// counter that lies.
|
|
76
|
+
if (index < 0) return null;
|
|
77
|
+
|
|
78
|
+
// Both lists are newest-first, so "Previous" walks toward the NEWER request —
|
|
79
|
+
// the same direction as scrolling up, which is what the arrow means here.
|
|
80
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EventStepperFooter, {
|
|
81
|
+
currentIndex: index,
|
|
82
|
+
totalItems: ordered.length,
|
|
83
|
+
itemLabel: "Request",
|
|
84
|
+
applySafeAreaInset: applySafeAreaInset,
|
|
85
|
+
onPrevious: () => {
|
|
86
|
+
const target = ordered[index - 1];
|
|
87
|
+
if (target) onSelect(target.id);
|
|
88
|
+
},
|
|
89
|
+
onNext: () => {
|
|
90
|
+
const target = ordered[index + 1];
|
|
91
|
+
if (target) onSelect(target.id);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -15,6 +15,7 @@ var _NetworkCopySettingsView = require("./NetworkCopySettingsView");
|
|
|
15
15
|
var _NetworkHeaderLive = require("./NetworkHeaderLive");
|
|
16
16
|
var _NetworkListScreen = require("./NetworkListScreen");
|
|
17
17
|
var _NetworkSavedScreen = require("./NetworkSavedScreen");
|
|
18
|
+
var _NetworkDetailStepper = require("./NetworkDetailStepper");
|
|
18
19
|
var _useNetworkEventList = require("../hooks/useNetworkEventList");
|
|
19
20
|
var _useLiveNetworkEvent = require("../hooks/useLiveNetworkEvent");
|
|
20
21
|
var _networkSavedStore = require("../utils/networkSavedStore");
|
|
@@ -31,7 +32,14 @@ function NetworkDetailScreen({
|
|
|
31
32
|
eventId,
|
|
32
33
|
paused,
|
|
33
34
|
ignoredPatternValues,
|
|
34
|
-
onTogglePattern
|
|
35
|
+
onTogglePattern,
|
|
36
|
+
isPro,
|
|
37
|
+
filter,
|
|
38
|
+
ignoredPatterns,
|
|
39
|
+
fromSaved,
|
|
40
|
+
savedSearch,
|
|
41
|
+
onSelectEvent,
|
|
42
|
+
applySafeAreaInset
|
|
35
43
|
}) {
|
|
36
44
|
// Resolve the ID to the live event so a request opened while pending picks
|
|
37
45
|
// up its response/error in place instead of freezing on the snapshot taken
|
|
@@ -40,10 +48,26 @@ function NetworkDetailScreen({
|
|
|
40
48
|
paused: paused || !visible
|
|
41
49
|
});
|
|
42
50
|
if (!visible || !event) return null;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
// Column: the detail body scrolls, the stepper stays pinned under it. The
|
|
52
|
+
// body owns its scroll here (see `bodyOwnsScroll`) — inside JsModal's
|
|
53
|
+
// wrapper the footer would scroll away with the content.
|
|
54
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
55
|
+
style: styles.detailRoot,
|
|
56
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkEventDetailView.NetworkEventDetailView, {
|
|
57
|
+
event: event,
|
|
58
|
+
ignoredPatterns: ignoredPatternValues,
|
|
59
|
+
onTogglePattern: onTogglePattern
|
|
60
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkDetailStepper.NetworkDetailStepper, {
|
|
61
|
+
eventId: event.id,
|
|
62
|
+
paused: paused,
|
|
63
|
+
filter: filter,
|
|
64
|
+
ignoredPatterns: ignoredPatterns,
|
|
65
|
+
isPro: isPro,
|
|
66
|
+
fromSaved: fromSaved,
|
|
67
|
+
savedSearch: savedSearch,
|
|
68
|
+
onSelect: onSelectEvent,
|
|
69
|
+
applySafeAreaInset: applySafeAreaInset
|
|
70
|
+
})]
|
|
47
71
|
});
|
|
48
72
|
}
|
|
49
73
|
function NetworkFilterScreen({
|
|
@@ -144,6 +168,10 @@ function NetworkModalInner({
|
|
|
144
168
|
const [activeTab, setActiveTab] = (0, _react.useState)("filters");
|
|
145
169
|
const [searchText, setSearchText] = (0, _react.useState)("");
|
|
146
170
|
const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
|
|
171
|
+
// The Saved screen's search box lives HERE, not in that screen, because the
|
|
172
|
+
// detail footer steps through the same list the user was just looking at —
|
|
173
|
+
// and the Saved screen is unmounted while a detail is open.
|
|
174
|
+
const [savedSearch, setSavedSearch] = (0, _react.useState)("");
|
|
147
175
|
const searchInputRef = (0, _react.useRef)(null);
|
|
148
176
|
// Ignored domains/URL patterns live in a shared, persisted singleton store
|
|
149
177
|
// (@buoy-gg/shared-ui) so the Network tool and the Events tool stay in sync.
|
|
@@ -228,8 +256,13 @@ function NetworkModalInner({
|
|
|
228
256
|
status: prev.status === status ? undefined : status
|
|
229
257
|
}));
|
|
230
258
|
};
|
|
231
|
-
|
|
232
|
-
|
|
259
|
+
|
|
260
|
+
// JsModal owns the mode; the only thing this modal needs it for is the
|
|
261
|
+
// detail footer's bottom inset — a bottom sheet sits on the home indicator,
|
|
262
|
+
// a floating window does not (same call StorageModalWithTabs makes).
|
|
263
|
+
const [modalMode, setModalMode] = (0, _react.useState)("bottomSheet");
|
|
264
|
+
const handleModeChange = mode => {
|
|
265
|
+
setModalMode(mode);
|
|
233
266
|
};
|
|
234
267
|
(0, _react.useEffect)(() => {
|
|
235
268
|
if (isSearchActive) {
|
|
@@ -386,8 +419,10 @@ function NetworkModalInner({
|
|
|
386
419
|
// it — inside JsModal's ScrollView wrapper every row mounts, which at
|
|
387
420
|
// the 500-event cap measured 17 JS FPS / 133% CPU / 1.4GB; see
|
|
388
421
|
// NetworkListScreen). The Saved screen is a FlatList too, so it takes the
|
|
389
|
-
// same deal.
|
|
390
|
-
|
|
422
|
+
// same deal. The DETAIL screen owns it as well, so its prev/next footer
|
|
423
|
+
// stays pinned instead of scrolling away with the body. Filter/copy still
|
|
424
|
+
// use the wrapper.
|
|
425
|
+
const bodyOwnsScroll = selectedEventId != null || !showFilterView;
|
|
391
426
|
const body = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
392
427
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
393
428
|
style: styles.container,
|
|
@@ -396,7 +431,19 @@ function NetworkModalInner({
|
|
|
396
431
|
eventId: selectedEventId,
|
|
397
432
|
paused: paused,
|
|
398
433
|
ignoredPatternValues: ignoredPatternValues,
|
|
399
|
-
onTogglePattern: toggleIgnoredPattern
|
|
434
|
+
onTogglePattern: toggleIgnoredPattern,
|
|
435
|
+
isPro: isPro,
|
|
436
|
+
filter: filter,
|
|
437
|
+
ignoredPatterns: ignoredPatterns
|
|
438
|
+
// `showSavedView` is what Back returns to, so it is also which list
|
|
439
|
+
// the user came from — and therefore which one to step through.
|
|
440
|
+
,
|
|
441
|
+
fromSaved: showSavedView,
|
|
442
|
+
savedSearch: savedSearch,
|
|
443
|
+
onSelectEvent: setSelectedEventId
|
|
444
|
+
// Inline (desktop panel) has no home indicator to clear.
|
|
445
|
+
,
|
|
446
|
+
applySafeAreaInset: !inline && modalMode === "bottomSheet"
|
|
400
447
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkFilterScreen, {
|
|
401
448
|
visible: showFilterView && activeTab === "filters" && !selectedEventId,
|
|
402
449
|
paused: paused,
|
|
@@ -419,6 +466,8 @@ function NetworkModalInner({
|
|
|
419
466
|
visible: showSavedView && selectedEventId == null && !showFilterView,
|
|
420
467
|
isPro: isPro,
|
|
421
468
|
copySettings: copySettings,
|
|
469
|
+
search: savedSearch,
|
|
470
|
+
onSearchChange: setSavedSearch,
|
|
422
471
|
onEventPress: handleEventPress,
|
|
423
472
|
onUpgradePress: () => showUpgrade("saved")
|
|
424
473
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkListScreen.NetworkListScreen, {
|
|
@@ -464,7 +513,7 @@ function NetworkModalInner({
|
|
|
464
513
|
enablePersistence: true,
|
|
465
514
|
initialMode: "bottomSheet",
|
|
466
515
|
enableGlitchEffects: true,
|
|
467
|
-
disableScrollWrapper:
|
|
516
|
+
disableScrollWrapper: bodyOwnsScroll,
|
|
468
517
|
styles: {},
|
|
469
518
|
children: body
|
|
470
519
|
});
|
|
@@ -474,6 +523,12 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
474
523
|
flex: 1,
|
|
475
524
|
backgroundColor: _sharedUi.macOSColors.background.base
|
|
476
525
|
},
|
|
526
|
+
/** Detail = scrolling body + pinned stepper footer. `minHeight: 0` so the
|
|
527
|
+
* body's ScrollView scrolls instead of stretching the column. */
|
|
528
|
+
detailRoot: {
|
|
529
|
+
flex: 1,
|
|
530
|
+
minHeight: 0
|
|
531
|
+
},
|
|
477
532
|
/** Inline shell — fills the host panel; `minHeight: 0` so the list's own
|
|
478
533
|
* FlatList can scroll instead of stretching the column. */
|
|
479
534
|
inlineRoot: {
|
|
@@ -10,7 +10,7 @@ var _sharedUi = require("@buoy-gg/shared-ui");
|
|
|
10
10
|
var _NetworkEventItemCompact = require("./NetworkEventItemCompact");
|
|
11
11
|
var _NetworkUpgradeBanner = require("./NetworkUpgradeBanner");
|
|
12
12
|
var _generateNetworkCopyText = require("../utils/generateNetworkCopyText");
|
|
13
|
-
var
|
|
13
|
+
var _selectSavedEvents = require("../utils/selectSavedEvents");
|
|
14
14
|
var _useNetworkSaved = require("../hooks/useNetworkSaved");
|
|
15
15
|
var _networkSavedStore = require("../utils/networkSavedStore");
|
|
16
16
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
@@ -51,6 +51,8 @@ function NetworkSavedScreen({
|
|
|
51
51
|
visible,
|
|
52
52
|
isPro,
|
|
53
53
|
copySettings,
|
|
54
|
+
search: searchProp,
|
|
55
|
+
onSearchChange,
|
|
54
56
|
onEventPress,
|
|
55
57
|
onUpgradePress
|
|
56
58
|
}) {
|
|
@@ -60,22 +62,17 @@ function NetworkSavedScreen({
|
|
|
60
62
|
toggleSave,
|
|
61
63
|
clearSaved
|
|
62
64
|
} = (0, _useNetworkSaved.useNetworkSaved)();
|
|
63
|
-
const [
|
|
65
|
+
const [searchState, setSearchState] = (0, _react.useState)("");
|
|
66
|
+
const controlled = searchProp != null && onSearchChange != null;
|
|
67
|
+
const search = controlled ? searchProp : searchState;
|
|
68
|
+
const setSearch = controlled ? onSearchChange : setSearchState;
|
|
64
69
|
if (!visible) return null;
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
*/
|
|
72
|
-
const limit = isPro ? _networkSavedStore.MAX_SAVED : _networkSavedStore.FREE_TIER_SAVED_LIMIT;
|
|
73
|
-
const unlocked = savedRecords.slice(0, limit);
|
|
74
|
-
const lockedCount = savedRecords.length - unlocked.length;
|
|
75
|
-
const events = unlocked.map(record => record.event);
|
|
76
|
-
const filtered = search ? (0, _filterNetworkEvents.filterNetworkEvents)(events, {
|
|
77
|
-
searchText: search
|
|
78
|
-
}) : events;
|
|
71
|
+
// Shared with the detail footer, so "Next" can't disagree with the row below.
|
|
72
|
+
const {
|
|
73
|
+
events: filtered,
|
|
74
|
+
lockedCount
|
|
75
|
+
} = (0, _selectSavedEvents.selectSavedEvents)(savedRecords, isPro, search);
|
|
79
76
|
const handleLongPress = event => {
|
|
80
77
|
toggleSave(event);
|
|
81
78
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.selectSavedEvents = selectSavedEvents;
|
|
7
|
+
var _filterNetworkEvents = require("./filterNetworkEvents");
|
|
8
|
+
var _networkSavedStore = require("./networkSavedStore");
|
|
9
|
+
/**
|
|
10
|
+
* The SAVED screen's visible list, as a pure function.
|
|
11
|
+
*
|
|
12
|
+
* Two places need the exact same list: the screen that renders it
|
|
13
|
+
* (`NetworkSavedScreen`) and the detail footer that steps through it
|
|
14
|
+
* (`NetworkDetailStepper`). If they derived it independently, "Next" would
|
|
15
|
+
* eventually disagree with the row below — the same drift `withPinnedFirst`
|
|
16
|
+
* exists to prevent for the live list.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function selectSavedEvents(savedRecords, isPro, search) {
|
|
20
|
+
/**
|
|
21
|
+
* A downgrade (or a limit change) can leave a free user holding more saves
|
|
22
|
+
* than the free tier allows. Rather than deleting their data, the overflow
|
|
23
|
+
* is LOCKED — oldest first, so the newest saves stay reachable.
|
|
24
|
+
*/
|
|
25
|
+
const limit = isPro ? _networkSavedStore.MAX_SAVED : _networkSavedStore.FREE_TIER_SAVED_LIMIT;
|
|
26
|
+
const unlocked = savedRecords.slice(0, limit);
|
|
27
|
+
const events = unlocked.map(record => record.event);
|
|
28
|
+
return {
|
|
29
|
+
events: search ? (0, _filterNetworkEvents.filterNetworkEvents)(events, {
|
|
30
|
+
searchText: search
|
|
31
|
+
}) : events,
|
|
32
|
+
lockedCount: savedRecords.length - unlocked.length
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Previous/Next footer for the request DETAIL screen — step through requests
|
|
5
|
+
* without bouncing back to the list.
|
|
6
|
+
*
|
|
7
|
+
* It is its OWN leaf (rule3) because it is the only part of the detail screen
|
|
8
|
+
* that needs the event LIST. Subscribing here keeps the request stream out of
|
|
9
|
+
* the detail body's render path, which is the expensive subtree (headers,
|
|
10
|
+
* bodies, JSON viewers, diff views): a request arriving re-renders this
|
|
11
|
+
* ~30-node footer and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* The order it steps through is EXACTLY what the list screen renders —
|
|
14
|
+
* `[...pinned, ...rest]` off the same `useNetworkEventList` + `splitPinned`
|
|
15
|
+
* derivation — so "Next" always lands on the row below the one the user
|
|
16
|
+
* tapped. Because the live filter is an input, typing in the search box or
|
|
17
|
+
* flipping a status chip re-scopes the stepper in place; the counter is
|
|
18
|
+
* "request N of <what the list is showing>", never "N of everything ever
|
|
19
|
+
* captured". Detail opened from the Saved screen steps through the SAVED
|
|
20
|
+
* list instead (with its own search applied), for the same reason.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { memo, useMemo } from "react";
|
|
24
|
+
import { EventStepperFooter } from "@buoy-gg/shared-ui";
|
|
25
|
+
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
26
|
+
import { useNetworkSaved } from "../hooks/useNetworkSaved";
|
|
27
|
+
import { splitPinned } from "../utils/withPinnedFirst";
|
|
28
|
+
import { selectSavedEvents } from "../utils/selectSavedEvents";
|
|
29
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
30
|
+
export const NetworkDetailStepper = /*#__PURE__*/memo(function NetworkDetailStepper({
|
|
31
|
+
eventId,
|
|
32
|
+
paused,
|
|
33
|
+
filter,
|
|
34
|
+
ignoredPatterns,
|
|
35
|
+
isPro,
|
|
36
|
+
fromSaved,
|
|
37
|
+
savedSearch,
|
|
38
|
+
onSelect,
|
|
39
|
+
applySafeAreaInset
|
|
40
|
+
}) {
|
|
41
|
+
const {
|
|
42
|
+
visibleEvents
|
|
43
|
+
} = useNetworkEventList({
|
|
44
|
+
filter,
|
|
45
|
+
ignoredPatterns,
|
|
46
|
+
paused,
|
|
47
|
+
isPro
|
|
48
|
+
});
|
|
49
|
+
const {
|
|
50
|
+
pinnedEvents,
|
|
51
|
+
pinnedLiveIds,
|
|
52
|
+
savedRecords
|
|
53
|
+
} = useNetworkSaved();
|
|
54
|
+
const ordered = useMemo(() => {
|
|
55
|
+
if (fromSaved) {
|
|
56
|
+
return selectSavedEvents(savedRecords, isPro, savedSearch).events;
|
|
57
|
+
}
|
|
58
|
+
// Same split the list screen does — pins render above the FlatList, so
|
|
59
|
+
// they come first here too.
|
|
60
|
+
const {
|
|
61
|
+
pinned,
|
|
62
|
+
rest
|
|
63
|
+
} = splitPinned(visibleEvents, pinnedEvents, pinnedLiveIds);
|
|
64
|
+
return pinned.length === 0 ? rest : [...pinned, ...rest];
|
|
65
|
+
}, [fromSaved, savedRecords, savedSearch, isPro, visibleEvents, pinnedEvents, pinnedLiveIds]);
|
|
66
|
+
const index = ordered.findIndex(event => event.id === eventId);
|
|
67
|
+
|
|
68
|
+
// Not in the list at all: the filter changed under it while it was open, it
|
|
69
|
+
// aged past the 500-request cap, or it is a saved snapshot with no live twin.
|
|
70
|
+
// There is no coherent "next" in that state, so show no footer rather than a
|
|
71
|
+
// counter that lies.
|
|
72
|
+
if (index < 0) return null;
|
|
73
|
+
|
|
74
|
+
// Both lists are newest-first, so "Previous" walks toward the NEWER request —
|
|
75
|
+
// the same direction as scrolling up, which is what the arrow means here.
|
|
76
|
+
return /*#__PURE__*/_jsx(EventStepperFooter, {
|
|
77
|
+
currentIndex: index,
|
|
78
|
+
totalItems: ordered.length,
|
|
79
|
+
itemLabel: "Request",
|
|
80
|
+
applySafeAreaInset: applySafeAreaInset,
|
|
81
|
+
onPrevious: () => {
|
|
82
|
+
const target = ordered[index - 1];
|
|
83
|
+
if (target) onSelect(target.id);
|
|
84
|
+
},
|
|
85
|
+
onNext: () => {
|
|
86
|
+
const target = ordered[index + 1];
|
|
87
|
+
if (target) onSelect(target.id);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -10,6 +10,7 @@ import { NetworkCopySettingsView, DEFAULT_COPY_SETTINGS } from "./NetworkCopySet
|
|
|
10
10
|
import { NetworkHeaderBadges, NetworkClearButton, NetworkDetailPinActions, NetworkHeaderCopyButton, NetworkSavedButton } from "./NetworkHeaderLive";
|
|
11
11
|
import { NetworkListScreen } from "./NetworkListScreen";
|
|
12
12
|
import { NetworkSavedScreen } from "./NetworkSavedScreen";
|
|
13
|
+
import { NetworkDetailStepper } from "./NetworkDetailStepper";
|
|
13
14
|
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
14
15
|
import { useLiveNetworkEvent } from "../hooks/useLiveNetworkEvent";
|
|
15
16
|
import { FREE_TIER_SAVED_LIMIT, MAX_SAVED } from "../utils/networkSavedStore";
|
|
@@ -26,7 +27,14 @@ function NetworkDetailScreen({
|
|
|
26
27
|
eventId,
|
|
27
28
|
paused,
|
|
28
29
|
ignoredPatternValues,
|
|
29
|
-
onTogglePattern
|
|
30
|
+
onTogglePattern,
|
|
31
|
+
isPro,
|
|
32
|
+
filter,
|
|
33
|
+
ignoredPatterns,
|
|
34
|
+
fromSaved,
|
|
35
|
+
savedSearch,
|
|
36
|
+
onSelectEvent,
|
|
37
|
+
applySafeAreaInset
|
|
30
38
|
}) {
|
|
31
39
|
// Resolve the ID to the live event so a request opened while pending picks
|
|
32
40
|
// up its response/error in place instead of freezing on the snapshot taken
|
|
@@ -35,10 +43,26 @@ function NetworkDetailScreen({
|
|
|
35
43
|
paused: paused || !visible
|
|
36
44
|
});
|
|
37
45
|
if (!visible || !event) return null;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
// Column: the detail body scrolls, the stepper stays pinned under it. The
|
|
47
|
+
// body owns its scroll here (see `bodyOwnsScroll`) — inside JsModal's
|
|
48
|
+
// wrapper the footer would scroll away with the content.
|
|
49
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
50
|
+
style: styles.detailRoot,
|
|
51
|
+
children: [/*#__PURE__*/_jsx(NetworkEventDetailView, {
|
|
52
|
+
event: event,
|
|
53
|
+
ignoredPatterns: ignoredPatternValues,
|
|
54
|
+
onTogglePattern: onTogglePattern
|
|
55
|
+
}), /*#__PURE__*/_jsx(NetworkDetailStepper, {
|
|
56
|
+
eventId: event.id,
|
|
57
|
+
paused: paused,
|
|
58
|
+
filter: filter,
|
|
59
|
+
ignoredPatterns: ignoredPatterns,
|
|
60
|
+
isPro: isPro,
|
|
61
|
+
fromSaved: fromSaved,
|
|
62
|
+
savedSearch: savedSearch,
|
|
63
|
+
onSelect: onSelectEvent,
|
|
64
|
+
applySafeAreaInset: applySafeAreaInset
|
|
65
|
+
})]
|
|
42
66
|
});
|
|
43
67
|
}
|
|
44
68
|
function NetworkFilterScreen({
|
|
@@ -139,6 +163,10 @@ function NetworkModalInner({
|
|
|
139
163
|
const [activeTab, setActiveTab] = useState("filters");
|
|
140
164
|
const [searchText, setSearchText] = useState("");
|
|
141
165
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
166
|
+
// The Saved screen's search box lives HERE, not in that screen, because the
|
|
167
|
+
// detail footer steps through the same list the user was just looking at —
|
|
168
|
+
// and the Saved screen is unmounted while a detail is open.
|
|
169
|
+
const [savedSearch, setSavedSearch] = useState("");
|
|
142
170
|
const searchInputRef = useRef(null);
|
|
143
171
|
// Ignored domains/URL patterns live in a shared, persisted singleton store
|
|
144
172
|
// (@buoy-gg/shared-ui) so the Network tool and the Events tool stay in sync.
|
|
@@ -223,8 +251,13 @@ function NetworkModalInner({
|
|
|
223
251
|
status: prev.status === status ? undefined : status
|
|
224
252
|
}));
|
|
225
253
|
};
|
|
226
|
-
|
|
227
|
-
|
|
254
|
+
|
|
255
|
+
// JsModal owns the mode; the only thing this modal needs it for is the
|
|
256
|
+
// detail footer's bottom inset — a bottom sheet sits on the home indicator,
|
|
257
|
+
// a floating window does not (same call StorageModalWithTabs makes).
|
|
258
|
+
const [modalMode, setModalMode] = useState("bottomSheet");
|
|
259
|
+
const handleModeChange = mode => {
|
|
260
|
+
setModalMode(mode);
|
|
228
261
|
};
|
|
229
262
|
useEffect(() => {
|
|
230
263
|
if (isSearchActive) {
|
|
@@ -381,8 +414,10 @@ function NetworkModalInner({
|
|
|
381
414
|
// it — inside JsModal's ScrollView wrapper every row mounts, which at
|
|
382
415
|
// the 500-event cap measured 17 JS FPS / 133% CPU / 1.4GB; see
|
|
383
416
|
// NetworkListScreen). The Saved screen is a FlatList too, so it takes the
|
|
384
|
-
// same deal.
|
|
385
|
-
|
|
417
|
+
// same deal. The DETAIL screen owns it as well, so its prev/next footer
|
|
418
|
+
// stays pinned instead of scrolling away with the body. Filter/copy still
|
|
419
|
+
// use the wrapper.
|
|
420
|
+
const bodyOwnsScroll = selectedEventId != null || !showFilterView;
|
|
386
421
|
const body = /*#__PURE__*/_jsxs(_Fragment, {
|
|
387
422
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
388
423
|
style: styles.container,
|
|
@@ -391,7 +426,19 @@ function NetworkModalInner({
|
|
|
391
426
|
eventId: selectedEventId,
|
|
392
427
|
paused: paused,
|
|
393
428
|
ignoredPatternValues: ignoredPatternValues,
|
|
394
|
-
onTogglePattern: toggleIgnoredPattern
|
|
429
|
+
onTogglePattern: toggleIgnoredPattern,
|
|
430
|
+
isPro: isPro,
|
|
431
|
+
filter: filter,
|
|
432
|
+
ignoredPatterns: ignoredPatterns
|
|
433
|
+
// `showSavedView` is what Back returns to, so it is also which list
|
|
434
|
+
// the user came from — and therefore which one to step through.
|
|
435
|
+
,
|
|
436
|
+
fromSaved: showSavedView,
|
|
437
|
+
savedSearch: savedSearch,
|
|
438
|
+
onSelectEvent: setSelectedEventId
|
|
439
|
+
// Inline (desktop panel) has no home indicator to clear.
|
|
440
|
+
,
|
|
441
|
+
applySafeAreaInset: !inline && modalMode === "bottomSheet"
|
|
395
442
|
}), /*#__PURE__*/_jsx(NetworkFilterScreen, {
|
|
396
443
|
visible: showFilterView && activeTab === "filters" && !selectedEventId,
|
|
397
444
|
paused: paused,
|
|
@@ -414,6 +461,8 @@ function NetworkModalInner({
|
|
|
414
461
|
visible: showSavedView && selectedEventId == null && !showFilterView,
|
|
415
462
|
isPro: isPro,
|
|
416
463
|
copySettings: copySettings,
|
|
464
|
+
search: savedSearch,
|
|
465
|
+
onSearchChange: setSavedSearch,
|
|
417
466
|
onEventPress: handleEventPress,
|
|
418
467
|
onUpgradePress: () => showUpgrade("saved")
|
|
419
468
|
}), /*#__PURE__*/_jsx(NetworkListScreen, {
|
|
@@ -459,7 +508,7 @@ function NetworkModalInner({
|
|
|
459
508
|
enablePersistence: true,
|
|
460
509
|
initialMode: "bottomSheet",
|
|
461
510
|
enableGlitchEffects: true,
|
|
462
|
-
disableScrollWrapper:
|
|
511
|
+
disableScrollWrapper: bodyOwnsScroll,
|
|
463
512
|
styles: {},
|
|
464
513
|
children: body
|
|
465
514
|
});
|
|
@@ -469,6 +518,12 @@ const styles = StyleSheet.create({
|
|
|
469
518
|
flex: 1,
|
|
470
519
|
backgroundColor: macOSColors.background.base
|
|
471
520
|
},
|
|
521
|
+
/** Detail = scrolling body + pinned stepper footer. `minHeight: 0` so the
|
|
522
|
+
* body's ScrollView scrolls instead of stretching the column. */
|
|
523
|
+
detailRoot: {
|
|
524
|
+
flex: 1,
|
|
525
|
+
minHeight: 0
|
|
526
|
+
},
|
|
472
527
|
/** Inline shell — fills the host panel; `minHeight: 0` so the list's own
|
|
473
528
|
* FlatList can scroll instead of stretching the column. */
|
|
474
529
|
inlineRoot: {
|
|
@@ -23,9 +23,9 @@ import { Bookmark, CopyButton, Search, macOSColors } from "@buoy-gg/shared-ui";
|
|
|
23
23
|
import { NetworkEventItemCompact } from "./NetworkEventItemCompact";
|
|
24
24
|
import { NetworkUpgradeBanner } from "./NetworkUpgradeBanner";
|
|
25
25
|
import { generateNetworkCopyText } from "../utils/generateNetworkCopyText";
|
|
26
|
-
import {
|
|
26
|
+
import { selectSavedEvents } from "../utils/selectSavedEvents";
|
|
27
27
|
import { useNetworkSaved } from "../hooks/useNetworkSaved";
|
|
28
|
-
import { FREE_TIER_SAVED_LIMIT
|
|
28
|
+
import { FREE_TIER_SAVED_LIMIT } from "../utils/networkSavedStore";
|
|
29
29
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
30
30
|
const keyExtractor = item => item.id;
|
|
31
31
|
function EmptyState() {
|
|
@@ -47,6 +47,8 @@ export function NetworkSavedScreen({
|
|
|
47
47
|
visible,
|
|
48
48
|
isPro,
|
|
49
49
|
copySettings,
|
|
50
|
+
search: searchProp,
|
|
51
|
+
onSearchChange,
|
|
50
52
|
onEventPress,
|
|
51
53
|
onUpgradePress
|
|
52
54
|
}) {
|
|
@@ -56,22 +58,17 @@ export function NetworkSavedScreen({
|
|
|
56
58
|
toggleSave,
|
|
57
59
|
clearSaved
|
|
58
60
|
} = useNetworkSaved();
|
|
59
|
-
const [
|
|
61
|
+
const [searchState, setSearchState] = useState("");
|
|
62
|
+
const controlled = searchProp != null && onSearchChange != null;
|
|
63
|
+
const search = controlled ? searchProp : searchState;
|
|
64
|
+
const setSearch = controlled ? onSearchChange : setSearchState;
|
|
60
65
|
if (!visible) return null;
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*/
|
|
68
|
-
const limit = isPro ? MAX_SAVED : FREE_TIER_SAVED_LIMIT;
|
|
69
|
-
const unlocked = savedRecords.slice(0, limit);
|
|
70
|
-
const lockedCount = savedRecords.length - unlocked.length;
|
|
71
|
-
const events = unlocked.map(record => record.event);
|
|
72
|
-
const filtered = search ? filterNetworkEvents(events, {
|
|
73
|
-
searchText: search
|
|
74
|
-
}) : events;
|
|
67
|
+
// Shared with the detail footer, so "Next" can't disagree with the row below.
|
|
68
|
+
const {
|
|
69
|
+
events: filtered,
|
|
70
|
+
lockedCount
|
|
71
|
+
} = selectSavedEvents(savedRecords, isPro, search);
|
|
75
72
|
const handleLongPress = event => {
|
|
76
73
|
toggleSave(event);
|
|
77
74
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The SAVED screen's visible list, as a pure function.
|
|
5
|
+
*
|
|
6
|
+
* Two places need the exact same list: the screen that renders it
|
|
7
|
+
* (`NetworkSavedScreen`) and the detail footer that steps through it
|
|
8
|
+
* (`NetworkDetailStepper`). If they derived it independently, "Next" would
|
|
9
|
+
* eventually disagree with the row below — the same drift `withPinnedFirst`
|
|
10
|
+
* exists to prevent for the live list.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { filterNetworkEvents } from "./filterNetworkEvents";
|
|
14
|
+
import { FREE_TIER_SAVED_LIMIT, MAX_SAVED } from "./networkSavedStore";
|
|
15
|
+
export function selectSavedEvents(savedRecords, isPro, search) {
|
|
16
|
+
/**
|
|
17
|
+
* A downgrade (or a limit change) can leave a free user holding more saves
|
|
18
|
+
* than the free tier allows. Rather than deleting their data, the overflow
|
|
19
|
+
* is LOCKED — oldest first, so the newest saves stay reachable.
|
|
20
|
+
*/
|
|
21
|
+
const limit = isPro ? MAX_SAVED : FREE_TIER_SAVED_LIMIT;
|
|
22
|
+
const unlocked = savedRecords.slice(0, limit);
|
|
23
|
+
const events = unlocked.map(record => record.event);
|
|
24
|
+
return {
|
|
25
|
+
events: search ? filterNetworkEvents(events, {
|
|
26
|
+
searchText: search
|
|
27
|
+
}) : events,
|
|
28
|
+
lockedCount: savedRecords.length - unlocked.length
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Previous/Next footer for the request DETAIL screen — step through requests
|
|
3
|
+
* without bouncing back to the list.
|
|
4
|
+
*
|
|
5
|
+
* It is its OWN leaf (rule3) because it is the only part of the detail screen
|
|
6
|
+
* that needs the event LIST. Subscribing here keeps the request stream out of
|
|
7
|
+
* the detail body's render path, which is the expensive subtree (headers,
|
|
8
|
+
* bodies, JSON viewers, diff views): a request arriving re-renders this
|
|
9
|
+
* ~30-node footer and nothing else.
|
|
10
|
+
*
|
|
11
|
+
* The order it steps through is EXACTLY what the list screen renders —
|
|
12
|
+
* `[...pinned, ...rest]` off the same `useNetworkEventList` + `splitPinned`
|
|
13
|
+
* derivation — so "Next" always lands on the row below the one the user
|
|
14
|
+
* tapped. Because the live filter is an input, typing in the search box or
|
|
15
|
+
* flipping a status chip re-scopes the stepper in place; the counter is
|
|
16
|
+
* "request N of <what the list is showing>", never "N of everything ever
|
|
17
|
+
* captured". Detail opened from the Saved screen steps through the SAVED
|
|
18
|
+
* list instead (with its own search applied), for the same reason.
|
|
19
|
+
*/
|
|
20
|
+
import { type IgnoredPattern } from "@buoy-gg/shared-ui";
|
|
21
|
+
import type { NetworkFilter } from "../types";
|
|
22
|
+
interface NetworkDetailStepperProps {
|
|
23
|
+
/** The request currently on screen. */
|
|
24
|
+
eventId: string;
|
|
25
|
+
paused: boolean;
|
|
26
|
+
filter: NetworkFilter;
|
|
27
|
+
ignoredPatterns: readonly IgnoredPattern[];
|
|
28
|
+
isPro: boolean;
|
|
29
|
+
/** Detail was opened from the Saved screen — step through THAT list. */
|
|
30
|
+
fromSaved: boolean;
|
|
31
|
+
/** The Saved screen's search box (lifted to the modal root so both agree). */
|
|
32
|
+
savedSearch: string;
|
|
33
|
+
onSelect: (eventId: string) => void;
|
|
34
|
+
/** Only a bottom sheet sits on the home indicator — see NetworkModal. */
|
|
35
|
+
applySafeAreaInset: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare const NetworkDetailStepper: import("react").NamedExoticComponent<NetworkDetailStepperProps>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=NetworkDetailStepper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NetworkDetailStepper.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkDetailStepper.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,UAAU,CAAC;AAM5D,UAAU,yBAAyB;IACjC,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,yEAAyE;IACzE,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,oBAAoB,iEAmE/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkModal.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkModal.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NetworkModal.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkModal.tsx"],"names":[],"mappings":"AAkDA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5C;AAopBD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAMpD;AAED,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,oBAAyB,+BAa/D"}
|
|
@@ -20,10 +20,21 @@ interface NetworkSavedScreenProps {
|
|
|
20
20
|
visible: boolean;
|
|
21
21
|
isPro: boolean;
|
|
22
22
|
copySettings: CopySettings;
|
|
23
|
+
/**
|
|
24
|
+
* Controlled search box — pass both halves or neither, same deal as
|
|
25
|
+
* NetworkModal's `paused`.
|
|
26
|
+
*
|
|
27
|
+
* NetworkModal controls it because the detail screen's prev/next footer
|
|
28
|
+
* steps through THIS list, and this screen is unmounted while a detail is
|
|
29
|
+
* open (see NetworkDetailStepper). A host rendering the screen on its own
|
|
30
|
+
* can omit both and keep the local state.
|
|
31
|
+
*/
|
|
32
|
+
search?: string;
|
|
33
|
+
onSearchChange?: (search: string) => void;
|
|
23
34
|
/** Row tap — the root's setSelectedEvent (inherently stable setter). */
|
|
24
35
|
onEventPress: (event: NetworkEvent) => void;
|
|
25
36
|
onUpgradePress: () => void;
|
|
26
37
|
}
|
|
27
|
-
export declare function NetworkSavedScreen({ visible, isPro, copySettings, onEventPress, onUpgradePress, }: NetworkSavedScreenProps): import("react").JSX.Element | null;
|
|
38
|
+
export declare function NetworkSavedScreen({ visible, isPro, copySettings, search: searchProp, onSearchChange, onEventPress, onUpgradePress, }: NetworkSavedScreenProps): import("react").JSX.Element | null;
|
|
28
39
|
export {};
|
|
29
40
|
//# sourceMappingURL=NetworkSavedScreen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkSavedScreen.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkSavedScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"NetworkSavedScreen.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkSavedScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAqB9D,UAAU,uBAAuB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,YAAY,CAAC;IAC3B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,wEAAwE;IACxE,YAAY,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,KAAK,EACL,YAAY,EACZ,MAAM,EAAE,UAAU,EAClB,cAAc,EACd,YAAY,EACZ,cAAc,GACf,EAAE,uBAAuB,sCA2HzB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SAVED screen's visible list, as a pure function.
|
|
3
|
+
*
|
|
4
|
+
* Two places need the exact same list: the screen that renders it
|
|
5
|
+
* (`NetworkSavedScreen`) and the detail footer that steps through it
|
|
6
|
+
* (`NetworkDetailStepper`). If they derived it independently, "Next" would
|
|
7
|
+
* eventually disagree with the row below — the same drift `withPinnedFirst`
|
|
8
|
+
* exists to prevent for the live list.
|
|
9
|
+
*/
|
|
10
|
+
import type { NetworkEvent } from "../types";
|
|
11
|
+
import { type SavedNetworkRecord } from "./networkSavedStore";
|
|
12
|
+
export interface SavedEventSelection {
|
|
13
|
+
/** Unlocked saves, minus anything the search box excludes. */
|
|
14
|
+
events: NetworkEvent[];
|
|
15
|
+
/** Saves past the tier ceiling — rendered as the upgrade banner, not rows. */
|
|
16
|
+
lockedCount: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function selectSavedEvents(savedRecords: readonly SavedNetworkRecord[], isPro: boolean, search: string): SavedEventSelection;
|
|
19
|
+
//# sourceMappingURL=selectSavedEvents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectSavedEvents.d.ts","sourceRoot":"","sources":["../../../../src/network/utils/selectSavedEvents.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,SAAS,kBAAkB,EAAE,EAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,GACb,mBAAmB,CAarB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/network",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.12",
|
|
4
4
|
"description": "In-app network inspector for React Native — requests, responses, timing & errors live, with GraphQL operation parsing. Part of Buoy devtools.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
29
|
+
"@buoy-gg/shared-ui": "^7.0.12"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": "*",
|