@buoy-gg/network 7.0.3 → 7.0.5
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 +3 -2
- package/lib/commonjs/index.js +85 -0
- package/lib/commonjs/network/components/NetworkEventDetailView.js +201 -40
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +52 -5
- package/lib/commonjs/network/components/NetworkHeaderLive.js +132 -0
- package/lib/commonjs/network/components/NetworkListScreen.js +134 -56
- package/lib/commonjs/network/components/NetworkModal.js +154 -36
- package/lib/commonjs/network/components/NetworkSavedScreen.js +245 -0
- package/lib/commonjs/network/components/NetworkUpgradeBanner.js +76 -0
- package/lib/commonjs/network/hooks/useLiveNetworkEvent.js +62 -0
- package/lib/commonjs/network/hooks/useNetworkSaved.js +38 -0
- package/lib/commonjs/network/index.js +84 -0
- package/lib/commonjs/network/sync/networkSyncAdapter.js +85 -7
- package/lib/commonjs/network/utils/generateCurl.js +71 -0
- package/lib/commonjs/network/utils/networkEventStore.js +10 -1
- package/lib/commonjs/network/utils/networkListener.js +54 -1
- package/lib/commonjs/network/utils/networkSavedStore.js +575 -0
- package/lib/commonjs/network/utils/withPinnedFirst.js +32 -0
- package/lib/module/index.js +17 -1
- package/lib/module/network/components/NetworkEventDetailView.js +204 -41
- package/lib/module/network/components/NetworkEventItemCompact.js +53 -6
- package/lib/module/network/components/NetworkHeaderLive.js +132 -2
- package/lib/module/network/components/NetworkListScreen.js +136 -58
- package/lib/module/network/components/NetworkModal.js +156 -39
- package/lib/module/network/components/NetworkSavedScreen.js +241 -0
- package/lib/module/network/components/NetworkUpgradeBanner.js +72 -0
- package/lib/module/network/hooks/useLiveNetworkEvent.js +58 -0
- package/lib/module/network/hooks/useNetworkSaved.js +34 -0
- package/lib/module/network/index.js +7 -1
- package/lib/module/network/sync/networkSyncAdapter.js +85 -7
- package/lib/module/network/utils/generateCurl.js +67 -0
- package/lib/module/network/utils/networkEventStore.js +10 -1
- package/lib/module/network/utils/networkListener.js +54 -1
- package/lib/module/network/utils/networkSavedStore.js +569 -0
- package/lib/module/network/utils/withPinnedFirst.js +28 -0
- package/lib/typescript/index.d.ts +13 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +12 -0
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts +28 -0
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkModal.d.ts +34 -0
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkSavedScreen.d.ts +29 -0
- package/lib/typescript/network/components/NetworkSavedScreen.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkUpgradeBanner.d.ts +17 -0
- package/lib/typescript/network/components/NetworkUpgradeBanner.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useLiveNetworkEvent.d.ts +19 -0
- package/lib/typescript/network/hooks/useLiveNetworkEvent.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useNetworkSaved.d.ts +21 -0
- package/lib/typescript/network/hooks/useNetworkSaved.d.ts.map +1 -0
- package/lib/typescript/network/index.d.ts +8 -1
- package/lib/typescript/network/index.d.ts.map +1 -1
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts +52 -3
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/network/types/index.d.ts +43 -0
- package/lib/typescript/network/types/index.d.ts.map +1 -1
- package/lib/typescript/network/utils/generateCurl.d.ts +3 -0
- package/lib/typescript/network/utils/generateCurl.d.ts.map +1 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkListener.d.ts +4 -0
- package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkSavedStore.d.ts +186 -0
- package/lib/typescript/network/utils/networkSavedStore.d.ts.map +1 -0
- package/lib/typescript/network/utils/withPinnedFirst.d.ts +24 -0
- package/lib/typescript/network/utils/withPinnedFirst.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.NetworkModal = NetworkModal;
|
|
7
|
+
exports.NetworkToolView = NetworkToolView;
|
|
7
8
|
var _react = require("react");
|
|
8
9
|
var _reactNative = require("react-native");
|
|
9
10
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
@@ -13,7 +14,10 @@ var _NetworkEventDetailView = require("./NetworkEventDetailView");
|
|
|
13
14
|
var _NetworkCopySettingsView = require("./NetworkCopySettingsView");
|
|
14
15
|
var _NetworkHeaderLive = require("./NetworkHeaderLive");
|
|
15
16
|
var _NetworkListScreen = require("./NetworkListScreen");
|
|
17
|
+
var _NetworkSavedScreen = require("./NetworkSavedScreen");
|
|
16
18
|
var _useNetworkEventList = require("../hooks/useNetworkEventList");
|
|
19
|
+
var _useLiveNetworkEvent = require("../hooks/useLiveNetworkEvent");
|
|
20
|
+
var _networkSavedStore = require("../utils/networkSavedStore");
|
|
17
21
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
18
22
|
// ============================================================================
|
|
19
23
|
// Screens (rule4 stable router: each gates itself with `return null`; the
|
|
@@ -24,10 +28,17 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
24
28
|
|
|
25
29
|
function NetworkDetailScreen({
|
|
26
30
|
visible,
|
|
27
|
-
|
|
31
|
+
eventId,
|
|
32
|
+
paused,
|
|
28
33
|
ignoredPatternValues,
|
|
29
34
|
onTogglePattern
|
|
30
35
|
}) {
|
|
36
|
+
// Resolve the ID to the live event so a request opened while pending picks
|
|
37
|
+
// up its response/error in place instead of freezing on the snapshot taken
|
|
38
|
+
// when the row was tapped.
|
|
39
|
+
const event = (0, _useLiveNetworkEvent.useLiveNetworkEvent)(eventId, {
|
|
40
|
+
paused: paused || !visible
|
|
41
|
+
});
|
|
31
42
|
if (!visible || !event) return null;
|
|
32
43
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkEventDetailView.NetworkEventDetailView, {
|
|
33
44
|
event: event,
|
|
@@ -105,18 +116,31 @@ function NetworkModalInner({
|
|
|
105
116
|
onClose,
|
|
106
117
|
onBack,
|
|
107
118
|
onMinimize,
|
|
108
|
-
enableSharedModalDimensions = false
|
|
119
|
+
enableSharedModalDimensions = false,
|
|
120
|
+
inline = false,
|
|
121
|
+
paused: pausedProp,
|
|
122
|
+
onPausedChange
|
|
109
123
|
}) {
|
|
110
124
|
const {
|
|
111
125
|
isPro
|
|
112
126
|
} = (0, _sharedUi.useFeatureGate)();
|
|
113
127
|
|
|
114
128
|
// Capture pause (the power toggle's freeze-stream semantic) — gates
|
|
115
|
-
// every subscriber in this modal's tree via props.
|
|
116
|
-
|
|
129
|
+
// every subscriber in this modal's tree via props. Controlled when the host
|
|
130
|
+
// supplies both halves; otherwise this local state is the source of truth.
|
|
131
|
+
const [pausedState, setPausedState] = (0, _react.useState)(false);
|
|
132
|
+
const controlledPause = pausedProp != null && onPausedChange != null;
|
|
133
|
+
const paused = controlledPause ? pausedProp : pausedState;
|
|
134
|
+
const setPaused = next => {
|
|
135
|
+
const value = typeof next === "function" ? next(paused) : next;
|
|
136
|
+
if (controlledPause) onPausedChange(value);else setPausedState(value);
|
|
137
|
+
};
|
|
117
138
|
const [filter, setFilter] = (0, _react.useState)({});
|
|
118
|
-
|
|
139
|
+
// The SELECTION is UI state (an ID); the event object it points at is read
|
|
140
|
+
// live from the store by the detail screen — see useLiveNetworkEvent.
|
|
141
|
+
const [selectedEventId, setSelectedEventId] = (0, _react.useState)(null);
|
|
119
142
|
const [showFilterView, setShowFilterView] = (0, _react.useState)(false);
|
|
143
|
+
const [showSavedView, setShowSavedView] = (0, _react.useState)(false);
|
|
120
144
|
const [activeTab, setActiveTab] = (0, _react.useState)("filters");
|
|
121
145
|
const [searchText, setSearchText] = (0, _react.useState)("");
|
|
122
146
|
const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
|
|
@@ -134,6 +158,13 @@ function NetworkModalInner({
|
|
|
134
158
|
const [copySettings, setCopySettings] = (0, _react.useState)(_NetworkCopySettingsView.DEFAULT_COPY_SETTINGS);
|
|
135
159
|
const hasLoadedCopySettings = (0, _react.useRef)(false);
|
|
136
160
|
const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
|
|
161
|
+
// Which ceiling the user hit — the upgrade sheet has to argue for the thing
|
|
162
|
+
// they were actually trying to do, not for exporting.
|
|
163
|
+
const [upgradeReason, setUpgradeReason] = (0, _react.useState)("export");
|
|
164
|
+
const showUpgrade = reason => {
|
|
165
|
+
setUpgradeReason(reason);
|
|
166
|
+
setShowUpgradeModal(true);
|
|
167
|
+
};
|
|
137
168
|
|
|
138
169
|
// Auto-close upgrade modal when user becomes Pro
|
|
139
170
|
(0, _react.useEffect)(() => {
|
|
@@ -177,8 +208,13 @@ function NetworkModalInner({
|
|
|
177
208
|
|
|
178
209
|
// Simple handlers - no useCallback needed per rule2
|
|
179
210
|
const handleBack = () => {
|
|
180
|
-
|
|
211
|
+
setSelectedEventId(null);
|
|
181
212
|
};
|
|
213
|
+
|
|
214
|
+
// Stable identity: this is passed down to the memoized list rows.
|
|
215
|
+
const handleEventPress = (0, _react.useCallback)(event => {
|
|
216
|
+
setSelectedEventId(event.id);
|
|
217
|
+
}, []);
|
|
182
218
|
const handleSearch = text => {
|
|
183
219
|
setSearchText(text);
|
|
184
220
|
setFilter(prev => ({
|
|
@@ -230,13 +266,32 @@ function NetworkModalInner({
|
|
|
230
266
|
}
|
|
231
267
|
|
|
232
268
|
// Event detail view header
|
|
233
|
-
if (
|
|
269
|
+
if (selectedEventId) {
|
|
234
270
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
235
271
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
236
272
|
onBack: handleBack
|
|
237
273
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
238
274
|
title: "Request Details",
|
|
239
275
|
centered: true
|
|
276
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
|
|
277
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderLive.NetworkDetailPinActions, {
|
|
278
|
+
eventId: selectedEventId,
|
|
279
|
+
isPro: isPro,
|
|
280
|
+
onUpgradePress: () => showUpgrade("saved")
|
|
281
|
+
})
|
|
282
|
+
})]
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Saved (favorites) header. Checked AFTER the detail branch so opening a
|
|
287
|
+
// saved request still gets the detail header — and its pin/save toggles.
|
|
288
|
+
if (showSavedView) {
|
|
289
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
290
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
291
|
+
onBack: () => setShowSavedView(false)
|
|
292
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
293
|
+
title: "Saved Requests",
|
|
294
|
+
centered: true
|
|
240
295
|
})]
|
|
241
296
|
});
|
|
242
297
|
}
|
|
@@ -305,13 +360,15 @@ function NetworkModalInner({
|
|
|
305
360
|
size: 14,
|
|
306
361
|
color: filter.status || filter.method || filter.contentType ? _sharedUi.macOSColors.semantic.info : _sharedUi.macOSColors.text.muted
|
|
307
362
|
})
|
|
363
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderLive.NetworkSavedButton, {
|
|
364
|
+
onPress: () => setShowSavedView(true)
|
|
308
365
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderLive.NetworkHeaderCopyButton, {
|
|
309
366
|
isPro: isPro,
|
|
310
367
|
filter: filter,
|
|
311
368
|
ignoredPatterns: ignoredPatterns,
|
|
312
369
|
copySettings: copySettings,
|
|
313
370
|
paused: paused,
|
|
314
|
-
onUpgradePress: () =>
|
|
371
|
+
onUpgradePress: () => showUpgrade("export")
|
|
315
372
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
|
|
316
373
|
isEnabled: !paused,
|
|
317
374
|
onToggle: () => setPaused(prev => !prev),
|
|
@@ -328,32 +385,20 @@ function NetworkModalInner({
|
|
|
328
385
|
// The LIST screen owns its scrolling (FlatList virtualization requires
|
|
329
386
|
// it — inside JsModal's ScrollView wrapper every row mounts, which at
|
|
330
387
|
// the 500-event cap measured 17 JS FPS / 133% CPU / 1.4GB; see
|
|
331
|
-
// NetworkListScreen).
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
onClose: onClose,
|
|
336
|
-
onMinimize: onMinimize,
|
|
337
|
-
persistenceKey: persistenceKey,
|
|
338
|
-
header: {
|
|
339
|
-
showToggleButton: true,
|
|
340
|
-
customContent: renderHeaderContent()
|
|
341
|
-
},
|
|
342
|
-
onModeChange: handleModeChange,
|
|
343
|
-
enablePersistence: true,
|
|
344
|
-
initialMode: "bottomSheet",
|
|
345
|
-
enableGlitchEffects: true,
|
|
346
|
-
disableScrollWrapper: listOwnsScroll,
|
|
347
|
-
styles: {},
|
|
388
|
+
// NetworkListScreen). The Saved screen is a FlatList too, so it takes the
|
|
389
|
+
// same deal. Detail/filter/copy still use the wrapper.
|
|
390
|
+
const listOwnsScroll = selectedEventId == null && !showFilterView;
|
|
391
|
+
const body = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
348
392
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
349
393
|
style: styles.container,
|
|
350
394
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkDetailScreen, {
|
|
351
|
-
visible:
|
|
352
|
-
|
|
395
|
+
visible: selectedEventId != null,
|
|
396
|
+
eventId: selectedEventId,
|
|
397
|
+
paused: paused,
|
|
353
398
|
ignoredPatternValues: ignoredPatternValues,
|
|
354
399
|
onTogglePattern: toggleIgnoredPattern
|
|
355
400
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkFilterScreen, {
|
|
356
|
-
visible: showFilterView && activeTab === "filters" && !
|
|
401
|
+
visible: showFilterView && activeTab === "filters" && !selectedEventId,
|
|
357
402
|
paused: paused,
|
|
358
403
|
isPro: isPro,
|
|
359
404
|
filter: filter,
|
|
@@ -363,37 +408,86 @@ function NetworkModalInner({
|
|
|
363
408
|
onAddPattern: addIgnoredPattern,
|
|
364
409
|
onTogglePatternMode: toggleIgnoredPatternMode
|
|
365
410
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkCopyScreen, {
|
|
366
|
-
visible: showFilterView && activeTab === "copy" && !
|
|
411
|
+
visible: showFilterView && activeTab === "copy" && !selectedEventId,
|
|
367
412
|
paused: paused,
|
|
368
413
|
isPro: isPro,
|
|
369
414
|
filter: filter,
|
|
370
415
|
ignoredPatterns: ignoredPatterns,
|
|
371
416
|
settings: copySettings,
|
|
372
417
|
onSettingsChange: setCopySettings
|
|
418
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkSavedScreen.NetworkSavedScreen, {
|
|
419
|
+
visible: showSavedView && selectedEventId == null && !showFilterView,
|
|
420
|
+
isPro: isPro,
|
|
421
|
+
copySettings: copySettings,
|
|
422
|
+
onEventPress: handleEventPress,
|
|
423
|
+
onUpgradePress: () => showUpgrade("saved")
|
|
373
424
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkListScreen.NetworkListScreen, {
|
|
374
|
-
visible:
|
|
425
|
+
visible: selectedEventId == null && !showFilterView && !showSavedView,
|
|
375
426
|
paused: paused,
|
|
376
427
|
filter: filter,
|
|
377
428
|
ignoredPatterns: ignoredPatterns,
|
|
378
429
|
isPro: isPro,
|
|
379
430
|
searchText: searchText,
|
|
380
|
-
onEventPress:
|
|
381
|
-
onUpgradePress: () =>
|
|
431
|
+
onEventPress: handleEventPress,
|
|
432
|
+
onUpgradePress: () => showUpgrade("export")
|
|
382
433
|
})]
|
|
383
434
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.UpgradeModal, {
|
|
384
435
|
visible: showUpgradeModal,
|
|
385
436
|
onClose: () => setShowUpgradeModal(false),
|
|
386
|
-
featureName: "Network Export",
|
|
387
|
-
description: "Export network requests to share with your team, debug APIs, or feed into AI tools.",
|
|
388
|
-
benefits: ["Export to JSON, Markdown, or plain text", "Customizable copy presets (URLs only, LLM-ready, full data)", "Share request details with teammates", "Feed network data directly to AI assistants"]
|
|
437
|
+
featureName: upgradeReason === "saved" ? "Saved Requests" : "Network Export",
|
|
438
|
+
description: upgradeReason === "saved" ? "Keep the requests that matter. Saved requests survive Clear, app restarts, and the 500-request history cap." : "Export network requests to share with your team, debug APIs, or feed into AI tools.",
|
|
439
|
+
benefits: upgradeReason === "saved" ? [`Save up to ${_networkSavedStore.MAX_SAVED} requests (free keeps ${_networkSavedStore.FREE_TIER_SAVED_LIMIT})`, "Failed requests stay put through a Clear", "Snapshots survive an app restart", "Export saved requests to teammates or AI assistants"] : ["Export to JSON, Markdown, or plain text", "Customizable copy presets (URLs only, LLM-ready, full data)", "Share request details with teammates", "Feed network data directly to AI assistants"]
|
|
389
440
|
})]
|
|
390
441
|
});
|
|
442
|
+
|
|
443
|
+
// The ONLY thing that differs between the phone and the desktop dashboard is
|
|
444
|
+
// which shell the header and body go in. Everything above this line is shared.
|
|
445
|
+
if (inline) {
|
|
446
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
447
|
+
style: styles.inlineRoot,
|
|
448
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
449
|
+
style: styles.inlineHeader,
|
|
450
|
+
children: renderHeaderContent()
|
|
451
|
+
}), body]
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
|
|
455
|
+
visible: visible,
|
|
456
|
+
onClose: onClose,
|
|
457
|
+
onMinimize: onMinimize,
|
|
458
|
+
persistenceKey: persistenceKey,
|
|
459
|
+
header: {
|
|
460
|
+
showToggleButton: true,
|
|
461
|
+
customContent: renderHeaderContent()
|
|
462
|
+
},
|
|
463
|
+
onModeChange: handleModeChange,
|
|
464
|
+
enablePersistence: true,
|
|
465
|
+
initialMode: "bottomSheet",
|
|
466
|
+
enableGlitchEffects: true,
|
|
467
|
+
disableScrollWrapper: listOwnsScroll,
|
|
468
|
+
styles: {},
|
|
469
|
+
children: body
|
|
470
|
+
});
|
|
391
471
|
}
|
|
392
472
|
const styles = _reactNative.StyleSheet.create({
|
|
393
473
|
container: {
|
|
394
474
|
flex: 1,
|
|
395
475
|
backgroundColor: _sharedUi.macOSColors.background.base
|
|
396
476
|
},
|
|
477
|
+
/** Inline shell — fills the host panel; `minHeight: 0` so the list's own
|
|
478
|
+
* FlatList can scroll instead of stretching the column. */
|
|
479
|
+
inlineRoot: {
|
|
480
|
+
flex: 1,
|
|
481
|
+
minHeight: 0,
|
|
482
|
+
backgroundColor: _sharedUi.macOSColors.background.base
|
|
483
|
+
},
|
|
484
|
+
inlineHeader: {
|
|
485
|
+
paddingHorizontal: 12,
|
|
486
|
+
paddingVertical: 8,
|
|
487
|
+
borderBottomWidth: 1,
|
|
488
|
+
borderBottomColor: _sharedUi.macOSColors.border.default,
|
|
489
|
+
backgroundColor: _sharedUi.macOSColors.background.card
|
|
490
|
+
},
|
|
397
491
|
headerSearchContainer: {
|
|
398
492
|
flexDirection: "row",
|
|
399
493
|
alignItems: "center",
|
|
@@ -441,4 +535,28 @@ function NetworkModal(props) {
|
|
|
441
535
|
...props
|
|
442
536
|
})
|
|
443
537
|
});
|
|
444
|
-
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* The same network tool as `NetworkModal`, rendered INLINE — header bar, then
|
|
541
|
+
* body — for hosts that place tools in a panel rather than a modal. Buoy Desktop
|
|
542
|
+
* is the only one today.
|
|
543
|
+
*
|
|
544
|
+
* This exists so there is exactly ONE network tool. The desktop dashboard used
|
|
545
|
+
* to ship its own 1,258-line copy of the modal's internals, which is how the two
|
|
546
|
+
* surfaces drifted apart in the first place; anything added here now shows up on
|
|
547
|
+
* both without a second edit.
|
|
548
|
+
*/
|
|
549
|
+
function NetworkToolView(props = {}) {
|
|
550
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_useTickEveryMinute.TickProvider, {
|
|
551
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkModalInner, {
|
|
552
|
+
...props,
|
|
553
|
+
inline: true,
|
|
554
|
+
visible: true
|
|
555
|
+
// Inline has no chrome to drive: there is no close button, no minimize
|
|
556
|
+
// and no outer back — the host panel owns all of that.
|
|
557
|
+
,
|
|
558
|
+
onClose: noop
|
|
559
|
+
})
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
const noop = () => {};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NetworkSavedScreen = NetworkSavedScreen;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
+
var _NetworkEventItemCompact = require("./NetworkEventItemCompact");
|
|
11
|
+
var _NetworkUpgradeBanner = require("./NetworkUpgradeBanner");
|
|
12
|
+
var _generateNetworkCopyText = require("../utils/generateNetworkCopyText");
|
|
13
|
+
var _filterNetworkEvents = require("../utils/filterNetworkEvents");
|
|
14
|
+
var _useNetworkSaved = require("../hooks/useNetworkSaved");
|
|
15
|
+
var _networkSavedStore = require("../utils/networkSavedStore");
|
|
16
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
|
+
/**
|
|
18
|
+
* The SAVED screen — the network tool's favorites list.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is read from the persisted saved store, never from the live
|
|
21
|
+
* event store. That is the whole point of the screen: these requests survive a
|
|
22
|
+
* Clear, the 500-event cap, and an app restart, so a failure you kept is still
|
|
23
|
+
* here tomorrow.
|
|
24
|
+
*
|
|
25
|
+
* Rows are the same two row components the live list uses, so a saved request
|
|
26
|
+
* looks exactly like the request it was. Long-press here UNSAVES rather than
|
|
27
|
+
* pins — in this list "get rid of it" is the action the row affords, and
|
|
28
|
+
* pinning is still available from the detail header.
|
|
29
|
+
*
|
|
30
|
+
* Stable-tree contract (rule4): hooks run unconditionally, then the component
|
|
31
|
+
* returns null when not visible.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const keyExtractor = item => item.id;
|
|
35
|
+
function EmptyState() {
|
|
36
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
37
|
+
style: styles.emptyState,
|
|
38
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Bookmark, {
|
|
39
|
+
size: 32,
|
|
40
|
+
color: _sharedUi.macOSColors.text.muted
|
|
41
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
42
|
+
style: styles.emptyTitle,
|
|
43
|
+
children: "No saved requests"
|
|
44
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
45
|
+
style: styles.emptyText,
|
|
46
|
+
children: "Open a request and tap the bookmark to keep it here \u2014 saved requests survive Clear and app restarts."
|
|
47
|
+
})]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function NetworkSavedScreen({
|
|
51
|
+
visible,
|
|
52
|
+
isPro,
|
|
53
|
+
copySettings,
|
|
54
|
+
onEventPress,
|
|
55
|
+
onUpgradePress
|
|
56
|
+
}) {
|
|
57
|
+
const {
|
|
58
|
+
savedRecords,
|
|
59
|
+
pinnedLiveIds,
|
|
60
|
+
toggleSave,
|
|
61
|
+
clearSaved
|
|
62
|
+
} = (0, _useNetworkSaved.useNetworkSaved)();
|
|
63
|
+
const [search, setSearch] = (0, _react.useState)("");
|
|
64
|
+
if (!visible) return null;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A downgrade (or a limit change) can leave a free user holding more saves
|
|
68
|
+
* than the free tier allows. Rather than deleting their data, the overflow
|
|
69
|
+
* is LOCKED — oldest first, so the newest saves stay reachable — and the
|
|
70
|
+
* banner offers the upgrade that unlocks it.
|
|
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;
|
|
79
|
+
const handleLongPress = event => {
|
|
80
|
+
toggleSave(event);
|
|
81
|
+
};
|
|
82
|
+
const renderItem = ({
|
|
83
|
+
item
|
|
84
|
+
}) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkEventItemCompact.NetworkEventItemCompact, {
|
|
85
|
+
event: item,
|
|
86
|
+
onPress: onEventPress,
|
|
87
|
+
onLongPress: handleLongPress,
|
|
88
|
+
saved: true,
|
|
89
|
+
pinned: pinnedLiveIds.has(item.id)
|
|
90
|
+
});
|
|
91
|
+
const renderListFooter = () => {
|
|
92
|
+
if (lockedCount === 0) return null;
|
|
93
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkUpgradeBanner.NetworkUpgradeBanner, {
|
|
94
|
+
title: `${lockedCount} saved ${lockedCount === 1 ? "request" : "requests"} locked`,
|
|
95
|
+
subtitle: `Free keeps ${_networkSavedStore.FREE_TIER_SAVED_LIMIT}. Upgrade to Pro to unlock the rest.`,
|
|
96
|
+
onPress: onUpgradePress
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
100
|
+
style: styles.root,
|
|
101
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
102
|
+
style: styles.toolbar,
|
|
103
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Bookmark, {
|
|
104
|
+
size: 11,
|
|
105
|
+
color: _sharedUi.macOSColors.semantic.debug
|
|
106
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
107
|
+
style: styles.toolbarTitle,
|
|
108
|
+
children: ["SAVED \xB7 ", savedRecords.length, isPro ? "" : `/${_networkSavedStore.FREE_TIER_SAVED_LIMIT}`]
|
|
109
|
+
}), isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton
|
|
110
|
+
// Lazy value, resolved at press time — no string is built per
|
|
111
|
+
// render, and it always reflects the current list.
|
|
112
|
+
, {
|
|
113
|
+
value: () => (0, _generateNetworkCopyText.generateNetworkCopyText)(filtered, copySettings),
|
|
114
|
+
size: 13,
|
|
115
|
+
buttonStyle: styles.toolbarButton,
|
|
116
|
+
colors: {
|
|
117
|
+
idle: _sharedUi.macOSColors.text.secondary,
|
|
118
|
+
success: _sharedUi.macOSColors.semantic.success,
|
|
119
|
+
error: _sharedUi.macOSColors.semantic.error
|
|
120
|
+
},
|
|
121
|
+
disabled: filtered.length === 0
|
|
122
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
123
|
+
onPress: clearSaved,
|
|
124
|
+
"sentry-label": "ignore clear saved",
|
|
125
|
+
style: styles.toolbarClear,
|
|
126
|
+
disabled: savedRecords.length === 0,
|
|
127
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
128
|
+
style: [styles.toolbarClearText, savedRecords.length === 0 && styles.toolbarClearTextDisabled],
|
|
129
|
+
children: "Clear all"
|
|
130
|
+
})
|
|
131
|
+
})]
|
|
132
|
+
}), savedRecords.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
133
|
+
style: styles.searchRow,
|
|
134
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
|
|
135
|
+
size: 13,
|
|
136
|
+
color: _sharedUi.macOSColors.text.secondary
|
|
137
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
|
|
138
|
+
style: styles.searchInput,
|
|
139
|
+
placeholder: "Search saved requests\u2026",
|
|
140
|
+
placeholderTextColor: _sharedUi.macOSColors.text.muted,
|
|
141
|
+
value: search,
|
|
142
|
+
onChangeText: setSearch,
|
|
143
|
+
"sentry-label": "ignore saved search",
|
|
144
|
+
accessibilityLabel: "Search saved requests",
|
|
145
|
+
autoCapitalize: "none",
|
|
146
|
+
autoCorrect: false,
|
|
147
|
+
returnKeyType: "search"
|
|
148
|
+
})]
|
|
149
|
+
}) : null, savedRecords.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
|
|
150
|
+
data: filtered,
|
|
151
|
+
renderItem: renderItem,
|
|
152
|
+
keyExtractor: keyExtractor,
|
|
153
|
+
contentContainerStyle: styles.listContent,
|
|
154
|
+
showsVerticalScrollIndicator: true,
|
|
155
|
+
removeClippedSubviews: true,
|
|
156
|
+
scrollEnabled: true,
|
|
157
|
+
initialNumToRender: 12,
|
|
158
|
+
maxToRenderPerBatch: 8,
|
|
159
|
+
windowSize: 5,
|
|
160
|
+
extraData: pinnedLiveIds,
|
|
161
|
+
"sentry-label": "ignore saved requests list",
|
|
162
|
+
ListFooterComponent: renderListFooter
|
|
163
|
+
})]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const styles = _reactNative.StyleSheet.create({
|
|
167
|
+
root: {
|
|
168
|
+
flex: 1,
|
|
169
|
+
minHeight: 0
|
|
170
|
+
},
|
|
171
|
+
toolbar: {
|
|
172
|
+
flexDirection: "row",
|
|
173
|
+
alignItems: "center",
|
|
174
|
+
gap: 6,
|
|
175
|
+
paddingHorizontal: 20,
|
|
176
|
+
paddingTop: 10,
|
|
177
|
+
paddingBottom: 6
|
|
178
|
+
},
|
|
179
|
+
toolbarTitle: {
|
|
180
|
+
flex: 1,
|
|
181
|
+
color: _sharedUi.macOSColors.semantic.debug,
|
|
182
|
+
fontSize: 10,
|
|
183
|
+
fontWeight: "700",
|
|
184
|
+
letterSpacing: 0.6,
|
|
185
|
+
fontFamily: "monospace"
|
|
186
|
+
},
|
|
187
|
+
toolbarButton: {
|
|
188
|
+
width: 26,
|
|
189
|
+
height: 26,
|
|
190
|
+
borderRadius: 6,
|
|
191
|
+
alignItems: "center",
|
|
192
|
+
justifyContent: "center"
|
|
193
|
+
},
|
|
194
|
+
toolbarClear: {
|
|
195
|
+
paddingHorizontal: 6,
|
|
196
|
+
paddingVertical: 2
|
|
197
|
+
},
|
|
198
|
+
toolbarClearText: {
|
|
199
|
+
color: _sharedUi.macOSColors.text.muted,
|
|
200
|
+
fontSize: 10
|
|
201
|
+
},
|
|
202
|
+
toolbarClearTextDisabled: {
|
|
203
|
+
color: _sharedUi.macOSColors.text.disabled
|
|
204
|
+
},
|
|
205
|
+
searchRow: {
|
|
206
|
+
flexDirection: "row",
|
|
207
|
+
alignItems: "center",
|
|
208
|
+
gap: 6,
|
|
209
|
+
marginHorizontal: 12,
|
|
210
|
+
marginBottom: 8,
|
|
211
|
+
paddingHorizontal: 10,
|
|
212
|
+
paddingVertical: 5,
|
|
213
|
+
backgroundColor: _sharedUi.macOSColors.background.input,
|
|
214
|
+
borderRadius: 10,
|
|
215
|
+
borderWidth: 1,
|
|
216
|
+
borderColor: _sharedUi.macOSColors.border.default
|
|
217
|
+
},
|
|
218
|
+
searchInput: {
|
|
219
|
+
flex: 1,
|
|
220
|
+
color: _sharedUi.macOSColors.text.primary,
|
|
221
|
+
fontSize: 13,
|
|
222
|
+
paddingVertical: 2
|
|
223
|
+
},
|
|
224
|
+
listContent: {
|
|
225
|
+
paddingTop: 4
|
|
226
|
+
},
|
|
227
|
+
emptyState: {
|
|
228
|
+
alignItems: "center",
|
|
229
|
+
paddingVertical: 40,
|
|
230
|
+
paddingHorizontal: 24
|
|
231
|
+
},
|
|
232
|
+
emptyTitle: {
|
|
233
|
+
color: _sharedUi.macOSColors.text.primary,
|
|
234
|
+
fontSize: 14,
|
|
235
|
+
fontWeight: "600",
|
|
236
|
+
marginTop: 12,
|
|
237
|
+
marginBottom: 6
|
|
238
|
+
},
|
|
239
|
+
emptyText: {
|
|
240
|
+
color: _sharedUi.macOSColors.text.muted,
|
|
241
|
+
fontSize: 12,
|
|
242
|
+
textAlign: "center",
|
|
243
|
+
lineHeight: 17
|
|
244
|
+
}
|
|
245
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NetworkUpgradeBanner = NetworkUpgradeBanner;
|
|
7
|
+
var _reactNative = require("react-native");
|
|
8
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
+
/**
|
|
11
|
+
* The "N things are locked, upgrade to see them" banner.
|
|
12
|
+
*
|
|
13
|
+
* Extracted because there are now two of them — locked request history on the
|
|
14
|
+
* live list, and locked saves on the Saved screen — and they are the same
|
|
15
|
+
* offer. Two copies would drift the moment one of them gets a copy tweak.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function NetworkUpgradeBanner({
|
|
19
|
+
title,
|
|
20
|
+
subtitle,
|
|
21
|
+
onPress
|
|
22
|
+
}) {
|
|
23
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
24
|
+
style: styles.banner,
|
|
25
|
+
onPress: onPress,
|
|
26
|
+
activeOpacity: 0.8,
|
|
27
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
28
|
+
style: styles.content,
|
|
29
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
30
|
+
style: styles.left,
|
|
31
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
|
|
32
|
+
size: 16,
|
|
33
|
+
color: _sharedUi.buoyColors.primary
|
|
34
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
35
|
+
style: styles.title,
|
|
36
|
+
children: title
|
|
37
|
+
})]
|
|
38
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {})]
|
|
39
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
40
|
+
style: styles.subtitle,
|
|
41
|
+
children: subtitle
|
|
42
|
+
})]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const styles = _reactNative.StyleSheet.create({
|
|
46
|
+
banner: {
|
|
47
|
+
backgroundColor: _sharedUi.buoyColors.card,
|
|
48
|
+
borderRadius: 10,
|
|
49
|
+
borderWidth: 1,
|
|
50
|
+
borderColor: _sharedUi.buoyColors.primary + "40",
|
|
51
|
+
padding: 12,
|
|
52
|
+
marginHorizontal: 12,
|
|
53
|
+
marginTop: 12,
|
|
54
|
+
marginBottom: 34
|
|
55
|
+
},
|
|
56
|
+
content: {
|
|
57
|
+
flexDirection: "row",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
justifyContent: "space-between",
|
|
60
|
+
marginBottom: 4
|
|
61
|
+
},
|
|
62
|
+
left: {
|
|
63
|
+
flexDirection: "row",
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
gap: 8
|
|
66
|
+
},
|
|
67
|
+
title: {
|
|
68
|
+
color: _sharedUi.buoyColors.text,
|
|
69
|
+
fontSize: 13,
|
|
70
|
+
fontWeight: "600"
|
|
71
|
+
},
|
|
72
|
+
subtitle: {
|
|
73
|
+
color: _sharedUi.buoyColors.textMuted,
|
|
74
|
+
fontSize: 11
|
|
75
|
+
}
|
|
76
|
+
});
|