@buoy-gg/network 7.0.13 → 7.0.15
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/lib/commonjs/index.js +39 -0
- package/lib/commonjs/network/components/NetworkEventDetailView.js +30 -3
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +4 -0
- package/lib/commonjs/network/components/NetworkHeaderLive.js +26 -6
- package/lib/commonjs/network/components/NetworkHeaderMenu.js +183 -0
- package/lib/commonjs/network/components/NetworkListScreen.js +126 -5
- package/lib/commonjs/network/components/NetworkModal.js +136 -29
- package/lib/commonjs/network/components/NetworkNumberPrompt.js +178 -0
- package/lib/commonjs/network/components/NetworkOverrideEditor.js +797 -0
- package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +102 -0
- package/lib/commonjs/network/components/NetworkOverridesButton.js +88 -0
- package/lib/commonjs/network/components/NetworkOverridesScreen.js +489 -0
- package/lib/commonjs/network/components/response-editor/Explorer.js +973 -0
- package/lib/commonjs/network/components/response-editor/deleteNestedDataByPath.js +56 -0
- package/lib/commonjs/network/components/response-editor/editorContext.js +33 -0
- package/lib/commonjs/network/components/response-editor/updateNestedDataByPath.js +61 -0
- package/lib/commonjs/network/hooks/useOverrideRules.js +36 -0
- package/lib/commonjs/network/overrides/applyToFetch.js +80 -0
- package/lib/commonjs/network/overrides/applyToXhr.js +190 -0
- package/lib/commonjs/network/overrides/engine.js +70 -0
- package/lib/commonjs/network/overrides/matchRule.js +81 -0
- package/lib/commonjs/network/overrides/overrideRulesStore.js +550 -0
- package/lib/commonjs/network/overrides/presets.js +70 -0
- package/lib/commonjs/network/overrides/resolveOverride.js +158 -0
- package/lib/commonjs/network/overrides/types.js +85 -0
- package/lib/commonjs/network/sync/networkSyncAdapter.js +230 -1
- package/lib/commonjs/network/types/index.js +5 -1
- package/lib/commonjs/network/utils/networkEventStore.js +52 -6
- package/lib/commonjs/network/utils/networkListener.js +90 -10
- package/lib/commonjs/preset.js +11 -0
- package/lib/module/index.js +15 -0
- package/lib/module/network/components/NetworkEventDetailView.js +31 -4
- package/lib/module/network/components/NetworkEventItemCompact.js +5 -1
- package/lib/module/network/components/NetworkHeaderLive.js +26 -6
- package/lib/module/network/components/NetworkHeaderMenu.js +177 -0
- package/lib/module/network/components/NetworkListScreen.js +127 -6
- package/lib/module/network/components/NetworkModal.js +138 -31
- package/lib/module/network/components/NetworkNumberPrompt.js +174 -0
- package/lib/module/network/components/NetworkOverrideEditor.js +794 -0
- package/lib/module/network/components/NetworkOverrideHeaderButton.js +99 -0
- package/lib/module/network/components/NetworkOverridesButton.js +85 -0
- package/lib/module/network/components/NetworkOverridesScreen.js +485 -0
- package/lib/module/network/components/response-editor/Explorer.js +972 -0
- package/lib/module/network/components/response-editor/deleteNestedDataByPath.js +51 -0
- package/lib/module/network/components/response-editor/editorContext.js +29 -0
- package/lib/module/network/components/response-editor/updateNestedDataByPath.js +56 -0
- package/lib/module/network/hooks/useOverrideRules.js +32 -0
- package/lib/module/network/overrides/applyToFetch.js +72 -0
- package/lib/module/network/overrides/applyToXhr.js +185 -0
- package/lib/module/network/overrides/engine.js +64 -0
- package/lib/module/network/overrides/matchRule.js +75 -0
- package/lib/module/network/overrides/overrideRulesStore.js +547 -0
- package/lib/module/network/overrides/presets.js +65 -0
- package/lib/module/network/overrides/resolveOverride.js +152 -0
- package/lib/module/network/overrides/types.js +80 -0
- package/lib/module/network/sync/networkSyncAdapter.js +230 -1
- package/lib/module/network/types/index.js +3 -1
- package/lib/module/network/utils/networkEventStore.js +52 -6
- package/lib/module/network/utils/networkListener.js +91 -10
- package/lib/module/preset.js +13 -1
- package/lib/typescript/index.d.ts +8 -0
- 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 +6 -0
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts +8 -1
- package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkHeaderMenu.d.ts +46 -0
- package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
- package/lib/typescript/network/components/NetworkNumberPrompt.d.ts +33 -0
- package/lib/typescript/network/components/NetworkNumberPrompt.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverrideEditor.d.ts +17 -0
- package/lib/typescript/network/components/NetworkOverrideEditor.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts +27 -0
- package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverridesButton.d.ts +17 -0
- package/lib/typescript/network/components/NetworkOverridesButton.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +29 -0
- package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/Explorer.d.ts +17 -0
- package/lib/typescript/network/components/response-editor/Explorer.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts +9 -0
- package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/editorContext.d.ts +29 -0
- package/lib/typescript/network/components/response-editor/editorContext.d.ts.map +1 -0
- package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts +10 -0
- package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useOverrideRules.d.ts +29 -0
- package/lib/typescript/network/hooks/useOverrideRules.d.ts.map +1 -0
- package/lib/typescript/network/overrides/__tests__/applyToFetch.test.d.ts +16 -0
- package/lib/typescript/network/overrides/__tests__/applyToFetch.test.d.ts.map +1 -0
- package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts +19 -0
- package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -0
- package/lib/typescript/network/overrides/__tests__/resolveOverride.test.d.ts +22 -0
- package/lib/typescript/network/overrides/__tests__/resolveOverride.test.d.ts.map +1 -0
- package/lib/typescript/network/overrides/applyToFetch.d.ts +50 -0
- package/lib/typescript/network/overrides/applyToFetch.d.ts.map +1 -0
- package/lib/typescript/network/overrides/applyToXhr.d.ts +43 -0
- package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -0
- package/lib/typescript/network/overrides/engine.d.ts +47 -0
- package/lib/typescript/network/overrides/engine.d.ts.map +1 -0
- package/lib/typescript/network/overrides/matchRule.d.ts +22 -0
- package/lib/typescript/network/overrides/matchRule.d.ts.map +1 -0
- package/lib/typescript/network/overrides/overrideRulesStore.d.ts +205 -0
- package/lib/typescript/network/overrides/overrideRulesStore.d.ts.map +1 -0
- package/lib/typescript/network/overrides/presets.d.ts +38 -0
- package/lib/typescript/network/overrides/presets.d.ts.map +1 -0
- package/lib/typescript/network/overrides/resolveOverride.d.ts +46 -0
- package/lib/typescript/network/overrides/resolveOverride.d.ts.map +1 -0
- package/lib/typescript/network/overrides/types.d.ts +169 -0
- package/lib/typescript/network/overrides/types.d.ts.map +1 -0
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts +73 -0
- package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/network/types/index.d.ts +10 -0
- package/lib/typescript/network/types/index.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkEventStore.d.ts +14 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
- package/lib/typescript/network/utils/networkListener.d.ts +3 -0
- package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
- package/lib/typescript/preset.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
import { useCallback } from "react";
|
|
16
16
|
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
17
|
-
import { Globe, Pin, Power, macOSColors } from "@buoy-gg/shared-ui";
|
|
17
|
+
import { FlaskConical, Globe, Pin, Power, macOSColors } from "@buoy-gg/shared-ui";
|
|
18
18
|
import { NetworkEventItemCompact } from "./NetworkEventItemCompact";
|
|
19
19
|
import { NetworkUpgradeBanner } from "./NetworkUpgradeBanner";
|
|
20
20
|
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
21
21
|
import { useNetworkSaved } from "../hooks/useNetworkSaved";
|
|
22
|
+
import { useOverrideRules } from "../hooks/useOverrideRules";
|
|
23
|
+
import { ruleMatches } from "../overrides/matchRule";
|
|
22
24
|
import { splitPinned } from "../utils/withPinnedFirst";
|
|
23
25
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
24
26
|
function EmptyState({
|
|
@@ -87,6 +89,62 @@ function PinnedSection({
|
|
|
87
89
|
}, event.id))]
|
|
88
90
|
});
|
|
89
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The OVERRIDDEN strip, above the pins.
|
|
95
|
+
*
|
|
96
|
+
* An overridden request is the one thing in this list that isn't true — the app
|
|
97
|
+
* got something the server never sent. That has to be impossible to miss, and a
|
|
98
|
+
* glyph on a row twenty scroll-positions down isn't. It sits ABOVE pins because
|
|
99
|
+
* a stale override explains a bug, whereas a pin only marks one.
|
|
100
|
+
*
|
|
101
|
+
* Rows are matched against the RULES rather than filtered on the `override`
|
|
102
|
+
* mark, so the request you built a rule from hoists immediately instead of
|
|
103
|
+
* waiting for the endpoint to be called again.
|
|
104
|
+
*
|
|
105
|
+
* The header button is PAUSE, not "Manage". Flipping overrides off and on is by
|
|
106
|
+
* far the most repeated action in a testing session — force the failure, check
|
|
107
|
+
* the UI, turn it off, check the happy path, turn it back on — and it used to
|
|
108
|
+
* cost a trip to another screen each way. The manager is still one tap away on
|
|
109
|
+
* the toolbar; it just isn't the thing you reach for twenty times an hour.
|
|
110
|
+
*/
|
|
111
|
+
function OverriddenSection({
|
|
112
|
+
events,
|
|
113
|
+
onEventPress,
|
|
114
|
+
onLongPress,
|
|
115
|
+
savedLiveIds,
|
|
116
|
+
paused,
|
|
117
|
+
onTogglePaused
|
|
118
|
+
}) {
|
|
119
|
+
if (events.length === 0) return null;
|
|
120
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
121
|
+
style: [styles.overriddenSection, paused && styles.overriddenSectionOff],
|
|
122
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
123
|
+
style: styles.overriddenHeader,
|
|
124
|
+
children: [/*#__PURE__*/_jsx(FlaskConical, {
|
|
125
|
+
size: 11,
|
|
126
|
+
color: paused ? macOSColors.text.muted : macOSColors.semantic.warning
|
|
127
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
128
|
+
style: [styles.overriddenTitle, paused && styles.overriddenTitleOff],
|
|
129
|
+
children: paused ? `PAUSED · ${events.length}` : `OVERRIDDEN · ${events.length}`
|
|
130
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
131
|
+
onPress: onTogglePaused,
|
|
132
|
+
"sentry-label": "ignore pause overrides",
|
|
133
|
+
style: styles.overriddenManage,
|
|
134
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
135
|
+
style: [styles.overriddenManageText, paused && styles.overriddenTitleOff],
|
|
136
|
+
children: paused ? "Resume" : "Pause"
|
|
137
|
+
})
|
|
138
|
+
})]
|
|
139
|
+
}), events.map(event => /*#__PURE__*/_jsx(NetworkEventItemCompact, {
|
|
140
|
+
event: event,
|
|
141
|
+
onPress: onEventPress,
|
|
142
|
+
onLongPress: onLongPress,
|
|
143
|
+
overridden: true,
|
|
144
|
+
saved: savedLiveIds.has(event.id)
|
|
145
|
+
}, event.id))]
|
|
146
|
+
});
|
|
147
|
+
}
|
|
90
148
|
export function NetworkListScreen({
|
|
91
149
|
visible,
|
|
92
150
|
paused,
|
|
@@ -118,6 +176,15 @@ export function NetworkListScreen({
|
|
|
118
176
|
clearPinned
|
|
119
177
|
} = useNetworkSaved();
|
|
120
178
|
|
|
179
|
+
// Which rules are live right now — the strip below reflects the rules, not
|
|
180
|
+
// the history, so it's correct the moment a rule is made or removed.
|
|
181
|
+
const {
|
|
182
|
+
rules,
|
|
183
|
+
enabled: overridesEnabled,
|
|
184
|
+
setEnabled,
|
|
185
|
+
isRuleSpent
|
|
186
|
+
} = useOverrideRules();
|
|
187
|
+
|
|
121
188
|
// Stable identity: handed to memoized rows.
|
|
122
189
|
const handleLongPress = useCallback(event => {
|
|
123
190
|
togglePin(event);
|
|
@@ -132,6 +199,17 @@ export function NetworkListScreen({
|
|
|
132
199
|
pinned,
|
|
133
200
|
rest
|
|
134
201
|
} = splitPinned(visibleEvents, pinnedEvents, pinnedLiveIds);
|
|
202
|
+
|
|
203
|
+
// Hoist anything an active rule covers. Done after `splitPinned` so a request
|
|
204
|
+
// that is both pinned and overridden appears once, in the louder section.
|
|
205
|
+
// Paused rules still populate the strip — that's how you find the thing you
|
|
206
|
+
// paused in order to resume it. The header says PAUSED instead.
|
|
207
|
+
const activeRules = rules.filter(rule => rule.enabled && !isRuleSpent(rule));
|
|
208
|
+
const isOverridden = event => activeRules.some(rule => ruleMatches(rule, event.url, String(event.method || "GET")));
|
|
209
|
+
const overridden = activeRules.length ? [...pinned, ...rest].filter(isOverridden) : [];
|
|
210
|
+
const overriddenIds = new Set(overridden.map(event => event.id));
|
|
211
|
+
const pinnedRows = pinned.filter(event => !overriddenIds.has(event.id));
|
|
212
|
+
const restRows = overriddenIds.size ? rest.filter(event => !overriddenIds.has(event.id)) : rest;
|
|
135
213
|
const isEnabled = !paused;
|
|
136
214
|
const renderItem = ({
|
|
137
215
|
item
|
|
@@ -163,14 +241,21 @@ export function NetworkListScreen({
|
|
|
163
241
|
style: styles.disabledText,
|
|
164
242
|
children: "Network interception is disabled"
|
|
165
243
|
})]
|
|
166
|
-
}) : null, /*#__PURE__*/_jsx(
|
|
167
|
-
events:
|
|
244
|
+
}) : null, /*#__PURE__*/_jsx(OverriddenSection, {
|
|
245
|
+
events: overridden,
|
|
246
|
+
onEventPress: onEventPress,
|
|
247
|
+
onLongPress: handleLongPress,
|
|
248
|
+
savedLiveIds: savedLiveIds,
|
|
249
|
+
paused: !overridesEnabled,
|
|
250
|
+
onTogglePaused: () => setEnabled(!overridesEnabled)
|
|
251
|
+
}), /*#__PURE__*/_jsx(PinnedSection, {
|
|
252
|
+
events: pinnedRows,
|
|
168
253
|
onEventPress: onEventPress,
|
|
169
254
|
onLongPress: handleLongPress,
|
|
170
255
|
savedLiveIds: savedLiveIds,
|
|
171
256
|
onClearPinned: clearPinned
|
|
172
|
-
}),
|
|
173
|
-
data:
|
|
257
|
+
}), restRows.length > 0 || hasLockedEvents ? /*#__PURE__*/_jsx(FlatList, {
|
|
258
|
+
data: restRows,
|
|
174
259
|
renderItem: renderItem,
|
|
175
260
|
keyExtractor: keyExtractor,
|
|
176
261
|
contentContainerStyle: styles.listContent,
|
|
@@ -199,7 +284,7 @@ export function NetworkListScreen({
|
|
|
199
284
|
extraData: savedLiveIds,
|
|
200
285
|
"sentry-label": "ignore network events list",
|
|
201
286
|
ListFooterComponent: renderListFooter
|
|
202
|
-
}) :
|
|
287
|
+
}) : pinnedRows.length > 0 || overridden.length > 0 ? null : /*#__PURE__*/_jsx(EmptyState, {
|
|
203
288
|
isEnabled: isEnabled
|
|
204
289
|
})]
|
|
205
290
|
});
|
|
@@ -214,6 +299,42 @@ const styles = StyleSheet.create({
|
|
|
214
299
|
* these rows do NOT obey the filters, and the boundary is what tells the
|
|
215
300
|
* user why a request they just filtered out is still on screen.
|
|
216
301
|
*/
|
|
302
|
+
overriddenSection: {
|
|
303
|
+
borderBottomWidth: 1,
|
|
304
|
+
borderBottomColor: macOSColors.semantic.warning + "44",
|
|
305
|
+
backgroundColor: macOSColors.semantic.warningBackground
|
|
306
|
+
},
|
|
307
|
+
overriddenSectionOff: {
|
|
308
|
+
borderBottomColor: macOSColors.border.default,
|
|
309
|
+
backgroundColor: macOSColors.background.card
|
|
310
|
+
},
|
|
311
|
+
overriddenTitleOff: {
|
|
312
|
+
color: macOSColors.text.muted
|
|
313
|
+
},
|
|
314
|
+
overriddenHeader: {
|
|
315
|
+
flexDirection: "row",
|
|
316
|
+
alignItems: "center",
|
|
317
|
+
gap: 6,
|
|
318
|
+
paddingHorizontal: 12,
|
|
319
|
+
paddingTop: 8,
|
|
320
|
+
paddingBottom: 4
|
|
321
|
+
},
|
|
322
|
+
overriddenTitle: {
|
|
323
|
+
flex: 1,
|
|
324
|
+
fontSize: 10,
|
|
325
|
+
fontWeight: "700",
|
|
326
|
+
letterSpacing: 0.8,
|
|
327
|
+
color: macOSColors.semantic.warning
|
|
328
|
+
},
|
|
329
|
+
overriddenManage: {
|
|
330
|
+
paddingHorizontal: 6,
|
|
331
|
+
paddingVertical: 2
|
|
332
|
+
},
|
|
333
|
+
overriddenManageText: {
|
|
334
|
+
fontSize: 10,
|
|
335
|
+
fontWeight: "600",
|
|
336
|
+
color: macOSColors.semantic.warning
|
|
337
|
+
},
|
|
217
338
|
pinnedSection: {
|
|
218
339
|
borderBottomWidth: 1,
|
|
219
340
|
borderBottomColor: macOSColors.border.default,
|
|
@@ -2,20 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
4
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";
|
|
5
|
+
import { Search, Filter, Bookmark, X, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, persistentStorage, TabSelector, useFeatureGate, UpgradeModal, PowerToggleButton, useIgnoredPatterns } from "@buoy-gg/shared-ui";
|
|
6
6
|
import { NetworkFilterViewV3 } from "./NetworkFilterViewV3";
|
|
7
7
|
import { TickProvider } from "../hooks/useTickEveryMinute";
|
|
8
8
|
import { NetworkEventDetailView } from "./NetworkEventDetailView";
|
|
9
9
|
import { NetworkCopySettingsView, DEFAULT_COPY_SETTINGS } from "./NetworkCopySettingsView";
|
|
10
|
-
import { NetworkHeaderBadges, NetworkClearButton, NetworkDetailPinActions, NetworkHeaderCopyButton
|
|
10
|
+
import { NetworkHeaderBadges, NetworkClearButton, NetworkDetailPinActions, NetworkHeaderCopyButton } from "./NetworkHeaderLive";
|
|
11
11
|
import { NetworkListScreen } from "./NetworkListScreen";
|
|
12
12
|
import { NetworkSavedScreen } from "./NetworkSavedScreen";
|
|
13
|
+
import { NetworkOverridesScreen } from "./NetworkOverridesScreen";
|
|
14
|
+
import { NetworkOverridesButton } from "./NetworkOverridesButton";
|
|
15
|
+
import { NetworkOverrideHeaderButton } from "./NetworkOverrideHeaderButton";
|
|
16
|
+
import { NetworkHeaderMenu, NetworkHeaderMenuButton, NetworkHeaderMenuItem } from "./NetworkHeaderMenu";
|
|
13
17
|
import { NetworkDetailStepper } from "./NetworkDetailStepper";
|
|
14
18
|
import { useNetworkEventList } from "../hooks/useNetworkEventList";
|
|
15
19
|
import { useLiveNetworkEvent } from "../hooks/useLiveNetworkEvent";
|
|
16
20
|
import { useHasNetworkSelection, useNetworkBodyEventId, useSelectedNetworkEventId } from "../hooks/useNetworkSelection";
|
|
17
21
|
import { networkSelectionStore } from "../utils/networkSelectionStore";
|
|
18
22
|
import { FREE_TIER_SAVED_LIMIT, MAX_SAVED } from "../utils/networkSavedStore";
|
|
23
|
+
import { FREE_TIER_RULE_LIMIT as FREE_TIER_OVERRIDE_LIMIT, MAX_RULES as MAX_OVERRIDE_RULES } from "../overrides/types";
|
|
19
24
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
20
25
|
// ============================================================================
|
|
21
26
|
// Screens (rule4 stable router: each gates itself with `return null`; the
|
|
@@ -199,6 +204,27 @@ function NetworkModalInner({
|
|
|
199
204
|
const hasSelection = useHasNetworkSelection();
|
|
200
205
|
const [showFilterView, setShowFilterView] = useState(false);
|
|
201
206
|
const [showSavedView, setShowSavedView] = useState(false);
|
|
207
|
+
const [showOverridesView, setShowOverridesView] = useState(false);
|
|
208
|
+
const [showHeaderMenu, setShowHeaderMenu] = useState(false);
|
|
209
|
+
/**
|
|
210
|
+
* Overrides are a DEVICE capability — a rule runs inside the app's own
|
|
211
|
+
* `fetch`/`XMLHttpRequest`, which the dashboard doesn't have.
|
|
212
|
+
*
|
|
213
|
+
* That doesn't mean the dashboard can't AUTHOR them: in `inline` mode the
|
|
214
|
+
* rule store runs as a mirror of the connected device (see
|
|
215
|
+
* `overrideRulesStore.enableRemoteMode`), so the screens below are the same
|
|
216
|
+
* ones the phone renders and every edit is forwarded. The one thing the
|
|
217
|
+
* dashboard never does is apply a rule to its own traffic —
|
|
218
|
+
* `disableEngine()` guarantees that.
|
|
219
|
+
*/
|
|
220
|
+
const overridesAvailable = true;
|
|
221
|
+
/**
|
|
222
|
+
* A rule prefilled from a request the user was looking at, handed to the
|
|
223
|
+
* Overrides screen when it mounts. Lives here rather than in that screen
|
|
224
|
+
* because the request that seeds it is on a DIFFERENT screen — this is the
|
|
225
|
+
* one place both are in scope.
|
|
226
|
+
*/
|
|
227
|
+
const [pendingOverrideDraft, setPendingOverrideDraft] = useState(null);
|
|
202
228
|
const [activeTab, setActiveTab] = useState("filters");
|
|
203
229
|
const [searchText, setSearchText] = useState("");
|
|
204
230
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
@@ -277,6 +303,25 @@ function NetworkModalInner({
|
|
|
277
303
|
const handleEventPress = useCallback(event => {
|
|
278
304
|
networkSelectionStore.select(event.id);
|
|
279
305
|
}, []);
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* "Custom…" in a request's Override section. Stable identity because the
|
|
309
|
+
* detail view is memoized on its props and an unstable callback would defeat
|
|
310
|
+
* the bail-out that keeps prev/next stepping cheap.
|
|
311
|
+
*/
|
|
312
|
+
const handleOverridePress = useCallback((draft, ruleId) => {
|
|
313
|
+
setPendingOverrideDraft(ruleId ? {
|
|
314
|
+
...draft,
|
|
315
|
+
id: ruleId
|
|
316
|
+
} : draft);
|
|
317
|
+
setShowOverridesView(true);
|
|
318
|
+
// Leave the detail view, or its header would sit on top of the editor.
|
|
319
|
+
networkSelectionStore.clear();
|
|
320
|
+
}, []);
|
|
321
|
+
|
|
322
|
+
// Stable identity: the Overrides screen adopts the draft in an effect keyed
|
|
323
|
+
// on both, so an unstable callback would re-run it every render.
|
|
324
|
+
const handlePendingOverrideConsumed = useCallback(() => setPendingOverrideDraft(null), []);
|
|
280
325
|
const handleSearch = text => {
|
|
281
326
|
setSearchText(text);
|
|
282
327
|
setFilter(prev => ({
|
|
@@ -340,11 +385,28 @@ function NetworkModalInner({
|
|
|
340
385
|
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
341
386
|
title: "Request Details",
|
|
342
387
|
centered: true
|
|
343
|
-
}), /*#__PURE__*/
|
|
344
|
-
children: /*#__PURE__*/_jsx(
|
|
388
|
+
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
389
|
+
children: [overridesAvailable ? /*#__PURE__*/_jsx(NetworkOverrideHeaderButton, {
|
|
390
|
+
onOpen: handleOverridePress
|
|
391
|
+
}) : null, /*#__PURE__*/_jsx(NetworkDetailPinActions, {
|
|
345
392
|
isPro: isPro,
|
|
346
393
|
onUpgradePress: () => showUpgrade("saved")
|
|
347
|
-
})
|
|
394
|
+
})]
|
|
395
|
+
})]
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Overrides manager header.
|
|
400
|
+
if (showOverridesView && overridesAvailable) {
|
|
401
|
+
return /*#__PURE__*/_jsxs(ModalHeader, {
|
|
402
|
+
children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
403
|
+
onBack: () => {
|
|
404
|
+
setShowOverridesView(false);
|
|
405
|
+
setPendingOverrideDraft(null);
|
|
406
|
+
}
|
|
407
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
408
|
+
title: "Response Overrides",
|
|
409
|
+
centered: true
|
|
348
410
|
})]
|
|
349
411
|
});
|
|
350
412
|
}
|
|
@@ -416,35 +478,26 @@ function NetworkModalInner({
|
|
|
416
478
|
size: 14,
|
|
417
479
|
color: macOSColors.text.secondary
|
|
418
480
|
})
|
|
419
|
-
}), /*#__PURE__*/_jsx(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
setShowFilterView(true);
|
|
423
|
-
},
|
|
424
|
-
style: [styles.headerActionButton, (filter.status || filter.method || filter.contentType) && styles.activeFilterButton],
|
|
425
|
-
children: /*#__PURE__*/_jsx(Filter, {
|
|
426
|
-
size: 14,
|
|
427
|
-
color: filter.status || filter.method || filter.contentType ? macOSColors.semantic.info : macOSColors.text.muted
|
|
428
|
-
})
|
|
429
|
-
}), /*#__PURE__*/_jsx(NetworkSavedButton, {
|
|
430
|
-
onPress: () => setShowSavedView(true)
|
|
431
|
-
}), /*#__PURE__*/_jsx(NetworkHeaderCopyButton, {
|
|
432
|
-
isPro: isPro,
|
|
433
|
-
filter: filter,
|
|
434
|
-
ignoredPatterns: ignoredPatterns,
|
|
435
|
-
copySettings: copySettings,
|
|
436
|
-
paused: paused,
|
|
437
|
-
onUpgradePress: () => showUpgrade("export")
|
|
438
|
-
}), /*#__PURE__*/_jsx(PowerToggleButton, {
|
|
481
|
+
}), overridesAvailable ? /*#__PURE__*/_jsx(NetworkOverridesButton, {
|
|
482
|
+
onPress: () => setShowOverridesView(true)
|
|
483
|
+
}) : null, /*#__PURE__*/_jsx(PowerToggleButton, {
|
|
439
484
|
isEnabled: !paused,
|
|
440
485
|
onToggle: () => setPaused(prev => !prev),
|
|
441
486
|
accessibilityLabel: "Toggle network interception"
|
|
442
487
|
}), /*#__PURE__*/_jsx(NetworkClearButton, {
|
|
443
488
|
paused: paused
|
|
489
|
+
}), /*#__PURE__*/_jsx(NetworkHeaderMenuButton, {
|
|
490
|
+
onPress: () => setShowHeaderMenu(prev => !prev),
|
|
491
|
+
isOpen: showHeaderMenu,
|
|
492
|
+
hasIndicator: hasActiveFilter
|
|
444
493
|
})]
|
|
445
494
|
})]
|
|
446
495
|
});
|
|
447
496
|
};
|
|
497
|
+
|
|
498
|
+
// Surfaced on the ⋯ so moving Filters into the menu doesn't hide the one
|
|
499
|
+
// thing that button was communicating.
|
|
500
|
+
const hasActiveFilter = !!(filter.status || filter.method || filter.contentType);
|
|
448
501
|
const persistenceKey = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : devToolsStorageKeys.network.modal();
|
|
449
502
|
if (!visible) return null;
|
|
450
503
|
|
|
@@ -493,8 +546,20 @@ function NetworkModalInner({
|
|
|
493
546
|
ignoredPatterns: ignoredPatterns,
|
|
494
547
|
settings: copySettings,
|
|
495
548
|
onSettingsChange: setCopySettings
|
|
549
|
+
}), /*#__PURE__*/_jsx(NetworkOverridesScreen, {
|
|
550
|
+
visible: overridesAvailable && showOverridesView && !hasSelection && !showFilterView,
|
|
551
|
+
isPro: isPro,
|
|
552
|
+
onUpgradePress: () => showUpgrade("overrides"),
|
|
553
|
+
pendingDraft: pendingOverrideDraft,
|
|
554
|
+
onPendingConsumed: handlePendingOverrideConsumed
|
|
555
|
+
// Inline (desktop panel) has no home indicator to clear.
|
|
556
|
+
,
|
|
557
|
+
applySafeAreaInset: !inline && modalMode === "bottomSheet"
|
|
496
558
|
}), /*#__PURE__*/_jsx(NetworkSavedScreen, {
|
|
497
|
-
visible: showSavedView && !hasSelection && !showFilterView
|
|
559
|
+
visible: showSavedView && !hasSelection && !showFilterView &&
|
|
560
|
+
// Overrides wins in the header branch too — the two must agree, or
|
|
561
|
+
// one screen renders under the other's header.
|
|
562
|
+
!(showOverridesView && overridesAvailable),
|
|
498
563
|
isPro: isPro,
|
|
499
564
|
copySettings: copySettings,
|
|
500
565
|
search: savedSearch,
|
|
@@ -502,7 +567,7 @@ function NetworkModalInner({
|
|
|
502
567
|
onEventPress: handleEventPress,
|
|
503
568
|
onUpgradePress: () => showUpgrade("saved")
|
|
504
569
|
}), /*#__PURE__*/_jsx(NetworkListScreen, {
|
|
505
|
-
visible: !hasSelection && !showFilterView && !showSavedView,
|
|
570
|
+
visible: !hasSelection && !showFilterView && !showSavedView && !(showOverridesView && overridesAvailable),
|
|
506
571
|
paused: paused,
|
|
507
572
|
filter: filter,
|
|
508
573
|
ignoredPatterns: ignoredPatterns,
|
|
@@ -511,12 +576,51 @@ function NetworkModalInner({
|
|
|
511
576
|
onEventPress: handleEventPress,
|
|
512
577
|
onUpgradePress: () => showUpgrade("export")
|
|
513
578
|
})]
|
|
514
|
-
}), /*#__PURE__*/
|
|
579
|
+
}), showHeaderMenu ? /*#__PURE__*/_jsxs(NetworkHeaderMenu, {
|
|
580
|
+
onClose: () => setShowHeaderMenu(false),
|
|
581
|
+
children: [/*#__PURE__*/_jsx(NetworkHeaderMenuItem, {
|
|
582
|
+
icon: /*#__PURE__*/_jsx(Filter, {
|
|
583
|
+
size: 14,
|
|
584
|
+
color: hasActiveFilter ? macOSColors.semantic.info : macOSColors.text.secondary
|
|
585
|
+
}),
|
|
586
|
+
label: "Filters",
|
|
587
|
+
detail: hasActiveFilter ? "Active" : undefined,
|
|
588
|
+
onPress: () => {
|
|
589
|
+
setShowHeaderMenu(false);
|
|
590
|
+
setActiveTab("filters");
|
|
591
|
+
setShowFilterView(true);
|
|
592
|
+
}
|
|
593
|
+
}), /*#__PURE__*/_jsx(NetworkHeaderMenuItem, {
|
|
594
|
+
icon: /*#__PURE__*/_jsx(Bookmark, {
|
|
595
|
+
size: 14,
|
|
596
|
+
color: macOSColors.text.secondary
|
|
597
|
+
}),
|
|
598
|
+
label: "Saved requests",
|
|
599
|
+
onPress: () => {
|
|
600
|
+
setShowHeaderMenu(false);
|
|
601
|
+
setShowSavedView(true);
|
|
602
|
+
}
|
|
603
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
604
|
+
style: styles.menuCopyRow,
|
|
605
|
+
children: /*#__PURE__*/_jsx(NetworkHeaderCopyButton, {
|
|
606
|
+
isPro: isPro,
|
|
607
|
+
filter: filter,
|
|
608
|
+
ignoredPatterns: ignoredPatterns,
|
|
609
|
+
copySettings: copySettings,
|
|
610
|
+
paused: paused,
|
|
611
|
+
onUpgradePress: () => {
|
|
612
|
+
setShowHeaderMenu(false);
|
|
613
|
+
showUpgrade("export");
|
|
614
|
+
},
|
|
615
|
+
label: "Copy requests"
|
|
616
|
+
})
|
|
617
|
+
})]
|
|
618
|
+
}) : null, /*#__PURE__*/_jsx(UpgradeModal, {
|
|
515
619
|
visible: showUpgradeModal,
|
|
516
620
|
onClose: () => setShowUpgradeModal(false),
|
|
517
|
-
featureName: upgradeReason === "saved" ? "Saved Requests" : "Network Export",
|
|
518
|
-
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.",
|
|
519
|
-
benefits: upgradeReason === "saved" ? [`Save up to ${MAX_SAVED} requests (free keeps ${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"]
|
|
621
|
+
featureName: upgradeReason === "saved" ? "Saved Requests" : upgradeReason === "overrides" ? "Response Overrides" : "Network Export",
|
|
622
|
+
description: upgradeReason === "saved" ? "Keep the requests that matter. Saved requests survive Clear, app restarts, and the 500-request history cap." : upgradeReason === "overrides" ? "Force any endpoint to fail, stall or return something else — no backend changes, no code changes. Free keeps one active rule." : "Export network requests to share with your team, debug APIs, or feed into AI tools.",
|
|
623
|
+
benefits: upgradeReason === "overrides" ? [`Run ${MAX_OVERRIDE_RULES} rules at once (free keeps ${FREE_TIER_OVERRIDE_LIMIT})`, "Force a 500, a 401 or an empty list in one tap", "Simulate offline, timeouts and slow connections", "Rules survive an app reload, so you can test the boot path"] : upgradeReason === "saved" ? [`Save up to ${MAX_SAVED} requests (free keeps ${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"]
|
|
520
624
|
})]
|
|
521
625
|
});
|
|
522
626
|
|
|
@@ -599,6 +703,9 @@ const styles = StyleSheet.create({
|
|
|
599
703
|
marginLeft: 6,
|
|
600
704
|
padding: 4
|
|
601
705
|
},
|
|
706
|
+
menuCopyRow: {
|
|
707
|
+
paddingHorizontal: 6
|
|
708
|
+
},
|
|
602
709
|
headerActionButton: {
|
|
603
710
|
width: 32,
|
|
604
711
|
height: 32,
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
|
|
5
|
+
import { absoluteFill, macOSColors } from "@buoy-gg/shared-ui";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Entering one number, without the keyboard eating the screen.
|
|
9
|
+
*
|
|
10
|
+
* The custom status / delay / times fields used to be inline inputs sitting
|
|
11
|
+
* mid-form. Focusing one raises the keyboard, which on a phone covers roughly
|
|
12
|
+
* the bottom half of the tool — including, depending on where you'd scrolled,
|
|
13
|
+
* the field you were typing into and the Save button you needed next.
|
|
14
|
+
*
|
|
15
|
+
* Pinning the panel to the TOP is the fix, and it's the same one
|
|
16
|
+
* `StorageValueEditModal` reached for: a centred panel gets shoved up or
|
|
17
|
+
* covered the moment the field focuses, and a bottom sheet is simply
|
|
18
|
+
* underneath it. At the top it stays put and stays readable.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately not a reuse of that component — this asks for a number and
|
|
21
|
+
* nothing else. It has no type inference, no raw-JSON escape hatch and no
|
|
22
|
+
* shape-preservation rules, so borrowing 400 lines to avoid writing 100 would
|
|
23
|
+
* be the wrong trade.
|
|
24
|
+
*/
|
|
25
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
26
|
+
export function NetworkNumberPrompt({
|
|
27
|
+
title,
|
|
28
|
+
hint,
|
|
29
|
+
value,
|
|
30
|
+
placeholder = "Number",
|
|
31
|
+
min,
|
|
32
|
+
max,
|
|
33
|
+
onSubmit,
|
|
34
|
+
onCancel
|
|
35
|
+
}) {
|
|
36
|
+
const [text, setText] = useState(value != null ? String(value) : "");
|
|
37
|
+
const submit = () => {
|
|
38
|
+
const trimmed = text.trim();
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
onSubmit(null);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const parsed = Number(trimmed);
|
|
44
|
+
if (!Number.isFinite(parsed)) {
|
|
45
|
+
onSubmit(null);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Clamped here rather than accepted-then-silently-changed: a field that
|
|
49
|
+
// takes 999 and stores 599 is a field that lies.
|
|
50
|
+
let next = parsed;
|
|
51
|
+
if (min != null) next = Math.max(min, next);
|
|
52
|
+
if (max != null) next = Math.min(max, next);
|
|
53
|
+
onSubmit(next);
|
|
54
|
+
};
|
|
55
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
56
|
+
style: styles.overlay,
|
|
57
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
58
|
+
style: styles.backdrop,
|
|
59
|
+
activeOpacity: 1,
|
|
60
|
+
onPress: onCancel,
|
|
61
|
+
"sentry-label": "ignore dismiss number prompt"
|
|
62
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
63
|
+
style: styles.panel,
|
|
64
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
65
|
+
style: styles.title,
|
|
66
|
+
children: title
|
|
67
|
+
}), /*#__PURE__*/_jsx(TextInput, {
|
|
68
|
+
style: styles.input,
|
|
69
|
+
value: text,
|
|
70
|
+
onChangeText: setText,
|
|
71
|
+
placeholder: placeholder,
|
|
72
|
+
placeholderTextColor: macOSColors.text.muted,
|
|
73
|
+
keyboardType: "number-pad",
|
|
74
|
+
autoFocus: true,
|
|
75
|
+
selectTextOnFocus: true,
|
|
76
|
+
onSubmitEditing: submit,
|
|
77
|
+
returnKeyType: "done",
|
|
78
|
+
"sentry-label": "ignore number prompt input",
|
|
79
|
+
accessibilityLabel: title
|
|
80
|
+
}), hint ? /*#__PURE__*/_jsx(Text, {
|
|
81
|
+
style: styles.hint,
|
|
82
|
+
children: hint
|
|
83
|
+
}) : null, /*#__PURE__*/_jsxs(View, {
|
|
84
|
+
style: styles.actions,
|
|
85
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
86
|
+
onPress: onCancel,
|
|
87
|
+
"sentry-label": "ignore cancel number prompt",
|
|
88
|
+
style: [styles.button, styles.cancelButton],
|
|
89
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
90
|
+
style: styles.cancelText,
|
|
91
|
+
children: "Cancel"
|
|
92
|
+
})
|
|
93
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
94
|
+
onPress: submit,
|
|
95
|
+
"sentry-label": "ignore save number prompt",
|
|
96
|
+
style: [styles.button, styles.saveButton],
|
|
97
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
98
|
+
style: styles.saveText,
|
|
99
|
+
children: "Set"
|
|
100
|
+
})
|
|
101
|
+
})]
|
|
102
|
+
})]
|
|
103
|
+
})]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const styles = StyleSheet.create({
|
|
107
|
+
overlay: {
|
|
108
|
+
...absoluteFill,
|
|
109
|
+
zIndex: 40
|
|
110
|
+
},
|
|
111
|
+
backdrop: {
|
|
112
|
+
...absoluteFill,
|
|
113
|
+
backgroundColor: "rgba(0,0,0,0.6)"
|
|
114
|
+
},
|
|
115
|
+
panel: {
|
|
116
|
+
marginHorizontal: 12,
|
|
117
|
+
marginTop: 12,
|
|
118
|
+
padding: 14,
|
|
119
|
+
borderRadius: 10,
|
|
120
|
+
borderWidth: 1,
|
|
121
|
+
borderColor: macOSColors.border.hover,
|
|
122
|
+
backgroundColor: macOSColors.background.card,
|
|
123
|
+
gap: 10
|
|
124
|
+
},
|
|
125
|
+
title: {
|
|
126
|
+
fontSize: 13,
|
|
127
|
+
fontWeight: "700",
|
|
128
|
+
color: macOSColors.text.primary
|
|
129
|
+
},
|
|
130
|
+
input: {
|
|
131
|
+
backgroundColor: macOSColors.background.input,
|
|
132
|
+
borderWidth: 1,
|
|
133
|
+
borderColor: macOSColors.border.input,
|
|
134
|
+
borderRadius: 6,
|
|
135
|
+
paddingHorizontal: 12,
|
|
136
|
+
paddingVertical: 10,
|
|
137
|
+
fontSize: 16,
|
|
138
|
+
fontWeight: "700",
|
|
139
|
+
color: macOSColors.text.primary
|
|
140
|
+
},
|
|
141
|
+
hint: {
|
|
142
|
+
fontSize: 10,
|
|
143
|
+
color: macOSColors.text.muted
|
|
144
|
+
},
|
|
145
|
+
actions: {
|
|
146
|
+
flexDirection: "row",
|
|
147
|
+
gap: 8
|
|
148
|
+
},
|
|
149
|
+
button: {
|
|
150
|
+
flex: 1,
|
|
151
|
+
paddingVertical: 10,
|
|
152
|
+
borderRadius: 6,
|
|
153
|
+
alignItems: "center",
|
|
154
|
+
borderWidth: 1
|
|
155
|
+
},
|
|
156
|
+
cancelButton: {
|
|
157
|
+
borderColor: macOSColors.border.default,
|
|
158
|
+
backgroundColor: macOSColors.background.base
|
|
159
|
+
},
|
|
160
|
+
cancelText: {
|
|
161
|
+
fontSize: 12,
|
|
162
|
+
fontWeight: "600",
|
|
163
|
+
color: macOSColors.text.secondary
|
|
164
|
+
},
|
|
165
|
+
saveButton: {
|
|
166
|
+
borderColor: macOSColors.semantic.info,
|
|
167
|
+
backgroundColor: `${macOSColors.semantic.info}22`
|
|
168
|
+
},
|
|
169
|
+
saveText: {
|
|
170
|
+
fontSize: 12,
|
|
171
|
+
fontWeight: "700",
|
|
172
|
+
color: macOSColors.semantic.info
|
|
173
|
+
}
|
|
174
|
+
});
|