@buoy-gg/route-events 1.7.2
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 +654 -0
- package/lib/commonjs/RouteObserver.js +54 -0
- package/lib/commonjs/RouteParser.js +310 -0
- package/lib/commonjs/RouteTracker.js +39 -0
- package/lib/commonjs/components/NavigationStack.js +584 -0
- package/lib/commonjs/components/RouteEventDetailContent.js +492 -0
- package/lib/commonjs/components/RouteEventExpandedContent.js +187 -0
- package/lib/commonjs/components/RouteEventItemCompact.js +175 -0
- package/lib/commonjs/components/RouteEventsModalWithTabs.js +560 -0
- package/lib/commonjs/components/RouteEventsTimeline.js +82 -0
- package/lib/commonjs/components/RouteFilterViewV2.js +42 -0
- package/lib/commonjs/components/RoutesSitemap.js +948 -0
- package/lib/commonjs/expoRouterStore.js +104 -0
- package/lib/commonjs/index.js +99 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +83 -0
- package/lib/commonjs/useNavigationStack.js +241 -0
- package/lib/commonjs/useRouteObserver.js +73 -0
- package/lib/commonjs/useRouteSitemap.js +234 -0
- package/lib/commonjs/utils/safeExpoRouter.js +129 -0
- package/lib/commonjs/utils/safeReactNavigation.js +104 -0
- package/lib/module/RouteObserver.js +49 -0
- package/lib/module/RouteParser.js +305 -0
- package/lib/module/RouteTracker.js +35 -0
- package/lib/module/components/NavigationStack.js +580 -0
- package/lib/module/components/RouteEventDetailContent.js +487 -0
- package/lib/module/components/RouteEventExpandedContent.js +183 -0
- package/lib/module/components/RouteEventItemCompact.js +171 -0
- package/lib/module/components/RouteEventsModalWithTabs.js +557 -0
- package/lib/module/components/RouteEventsTimeline.js +78 -0
- package/lib/module/components/RouteFilterViewV2.js +38 -0
- package/lib/module/components/RoutesSitemap.js +944 -0
- package/lib/module/expoRouterStore.js +98 -0
- package/lib/module/index.js +23 -0
- package/lib/module/preset.js +79 -0
- package/lib/module/useNavigationStack.js +238 -0
- package/lib/module/useRouteObserver.js +70 -0
- package/lib/module/useRouteSitemap.js +229 -0
- package/lib/module/utils/safeExpoRouter.js +120 -0
- package/lib/module/utils/safeReactNavigation.js +98 -0
- package/lib/typescript/RouteObserver.d.ts +37 -0
- package/lib/typescript/RouteObserver.d.ts.map +1 -0
- package/lib/typescript/RouteParser.d.ts +129 -0
- package/lib/typescript/RouteParser.d.ts.map +1 -0
- package/lib/typescript/RouteTracker.d.ts +29 -0
- package/lib/typescript/RouteTracker.d.ts.map +1 -0
- package/lib/typescript/components/NavigationStack.d.ts +11 -0
- package/lib/typescript/components/NavigationStack.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventDetailContent.d.ts +21 -0
- package/lib/typescript/components/RouteEventDetailContent.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventExpandedContent.d.ts +16 -0
- package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventItemCompact.d.ts +15 -0
- package/lib/typescript/components/RouteEventItemCompact.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts +15 -0
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventsTimeline.d.ts +17 -0
- package/lib/typescript/components/RouteEventsTimeline.d.ts.map +1 -0
- package/lib/typescript/components/RouteFilterViewV2.d.ts +9 -0
- package/lib/typescript/components/RouteFilterViewV2.d.ts.map +1 -0
- package/lib/typescript/components/RoutesSitemap.d.ts +15 -0
- package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -0
- package/lib/typescript/expoRouterStore.d.ts +28 -0
- package/lib/typescript/expoRouterStore.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +18 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +76 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/lib/typescript/useNavigationStack.d.ts +48 -0
- package/lib/typescript/useNavigationStack.d.ts.map +1 -0
- package/lib/typescript/useRouteObserver.d.ts +27 -0
- package/lib/typescript/useRouteObserver.d.ts.map +1 -0
- package/lib/typescript/useRouteSitemap.d.ts +102 -0
- package/lib/typescript/useRouteSitemap.d.ts.map +1 -0
- package/lib/typescript/utils/safeExpoRouter.d.ts +13 -0
- package/lib/typescript/utils/safeExpoRouter.d.ts.map +1 -0
- package/lib/typescript/utils/safeReactNavigation.d.ts +10 -0
- package/lib/typescript/utils/safeReactNavigation.d.ts.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.RouteEventsModalWithTabs = RouteEventsModalWithTabs;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _expoRouter = require("expo-router");
|
|
10
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
11
|
+
var _RouteObserver = require("../RouteObserver");
|
|
12
|
+
var _RouteFilterViewV = require("./RouteFilterViewV2");
|
|
13
|
+
var _RoutesSitemap = require("./RoutesSitemap");
|
|
14
|
+
var _NavigationStack = require("./NavigationStack");
|
|
15
|
+
var _RouteEventsTimeline = require("./RouteEventsTimeline");
|
|
16
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
|
+
// Lazy load the license hooks to avoid circular dependencies
|
|
18
|
+
let _useIsPro = null;
|
|
19
|
+
let _licenseLoadAttempted = false;
|
|
20
|
+
function loadLicenseModule() {
|
|
21
|
+
if (_licenseLoadAttempted) return;
|
|
22
|
+
_licenseLoadAttempted = true;
|
|
23
|
+
try {
|
|
24
|
+
const mod = require("@buoy-gg/license");
|
|
25
|
+
if (mod) {
|
|
26
|
+
_useIsPro = mod.useIsPro ?? null;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// License package not available
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getUseIsPro() {
|
|
33
|
+
loadLicenseModule();
|
|
34
|
+
return _useIsPro ?? (() => false);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Free tier limit for route events
|
|
38
|
+
const FREE_TIER_EVENT_LIMIT = 3;
|
|
39
|
+
function RouteEventsModalWithTabs({
|
|
40
|
+
visible,
|
|
41
|
+
onClose,
|
|
42
|
+
onBack,
|
|
43
|
+
onMinimize,
|
|
44
|
+
enableSharedModalDimensions = false,
|
|
45
|
+
routeObserver = _RouteObserver.routeObserver
|
|
46
|
+
}) {
|
|
47
|
+
const router = (0, _expoRouter.useRouter)();
|
|
48
|
+
const [activeTab, setActiveTab] = (0, _react.useState)("events");
|
|
49
|
+
const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
|
|
50
|
+
|
|
51
|
+
// Check Pro status internally
|
|
52
|
+
const useIsPro = getUseIsPro();
|
|
53
|
+
const isPro = useIsPro();
|
|
54
|
+
|
|
55
|
+
// NOTE: Route tracking requires <RouteTracker /> to be placed inside the navigation tree.
|
|
56
|
+
// The useRouteObserver hook uses expo-router hooks that only work inside Stack/Tabs/Slot.
|
|
57
|
+
// See the RouteTracker component export for easy setup.
|
|
58
|
+
|
|
59
|
+
// Event Listener state
|
|
60
|
+
const [events, setEvents] = (0, _react.useState)([]);
|
|
61
|
+
const [isListening, setIsListening] = (0, _react.useState)(false);
|
|
62
|
+
const [showFilters, setShowFilters] = (0, _react.useState)(false);
|
|
63
|
+
const [searchQuery, setSearchQuery] = (0, _react.useState)("");
|
|
64
|
+
const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)(new Set(["/_sitemap", "/api", "/__dev"]));
|
|
65
|
+
const lastEventRef = (0, _react.useRef)(null);
|
|
66
|
+
const hasLoadedFilters = (0, _react.useRef)(false);
|
|
67
|
+
const hasLoadedTabState = (0, _react.useRef)(false);
|
|
68
|
+
const hasLoadedMonitoringState = (0, _react.useRef)(false);
|
|
69
|
+
const handleModeChange = (0, _react.useCallback)(_mode => {
|
|
70
|
+
// Mode changes handled by JsModal
|
|
71
|
+
}, []);
|
|
72
|
+
const handleNavigate = (0, _react.useCallback)(pathname => {
|
|
73
|
+
try {
|
|
74
|
+
router.push(pathname);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
_reactNative.Alert.alert("Navigation Error", String(error));
|
|
77
|
+
}
|
|
78
|
+
}, [router]);
|
|
79
|
+
|
|
80
|
+
// Load persisted tab state on mount
|
|
81
|
+
(0, _react.useEffect)(() => {
|
|
82
|
+
if (!visible || hasLoadedTabState.current) return;
|
|
83
|
+
const loadTabState = async () => {
|
|
84
|
+
try {
|
|
85
|
+
const storedTab = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.routeEvents.activeTab());
|
|
86
|
+
if (storedTab && (storedTab === "routes" || storedTab === "events")) {
|
|
87
|
+
setActiveTab(storedTab);
|
|
88
|
+
}
|
|
89
|
+
hasLoadedTabState.current = true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// Failed to load tab state
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
loadTabState();
|
|
95
|
+
}, [visible]);
|
|
96
|
+
|
|
97
|
+
// Load persisted monitoring state on mount
|
|
98
|
+
(0, _react.useEffect)(() => {
|
|
99
|
+
if (!visible || hasLoadedMonitoringState.current) return;
|
|
100
|
+
const loadMonitoringState = async () => {
|
|
101
|
+
try {
|
|
102
|
+
const storedMonitoring = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.routeEvents.isMonitoring());
|
|
103
|
+
if (storedMonitoring !== null) {
|
|
104
|
+
const shouldMonitor = storedMonitoring === "true";
|
|
105
|
+
setIsListening(shouldMonitor);
|
|
106
|
+
}
|
|
107
|
+
hasLoadedMonitoringState.current = true;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
// Failed to load monitoring state
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
loadMonitoringState();
|
|
113
|
+
}, [visible]);
|
|
114
|
+
|
|
115
|
+
// Save tab state when it changes
|
|
116
|
+
(0, _react.useEffect)(() => {
|
|
117
|
+
if (!hasLoadedTabState.current) return;
|
|
118
|
+
const saveTabState = async () => {
|
|
119
|
+
try {
|
|
120
|
+
await (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.routeEvents.activeTab(), activeTab);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// Failed to save tab state
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
saveTabState();
|
|
126
|
+
}, [activeTab]);
|
|
127
|
+
|
|
128
|
+
// Save monitoring state when it changes
|
|
129
|
+
(0, _react.useEffect)(() => {
|
|
130
|
+
if (!hasLoadedMonitoringState.current) return;
|
|
131
|
+
const saveMonitoringState = async () => {
|
|
132
|
+
try {
|
|
133
|
+
await (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.routeEvents.isMonitoring(), isListening.toString());
|
|
134
|
+
} catch (error) {
|
|
135
|
+
// Failed to save monitoring state
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
saveMonitoringState();
|
|
139
|
+
}, [isListening]);
|
|
140
|
+
|
|
141
|
+
// Load persisted filters on mount
|
|
142
|
+
(0, _react.useEffect)(() => {
|
|
143
|
+
if (!visible || hasLoadedFilters.current) return;
|
|
144
|
+
const loadFilters = async () => {
|
|
145
|
+
try {
|
|
146
|
+
const storedFilters = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.routeEvents.eventFilters());
|
|
147
|
+
if (storedFilters) {
|
|
148
|
+
const filters = JSON.parse(storedFilters);
|
|
149
|
+
setIgnoredPatterns(new Set(filters));
|
|
150
|
+
}
|
|
151
|
+
hasLoadedFilters.current = true;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
// Failed to load filters
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
loadFilters();
|
|
157
|
+
}, [visible]);
|
|
158
|
+
|
|
159
|
+
// Save filters when they change
|
|
160
|
+
(0, _react.useEffect)(() => {
|
|
161
|
+
if (!hasLoadedFilters.current) return;
|
|
162
|
+
const saveFilters = async () => {
|
|
163
|
+
try {
|
|
164
|
+
const filters = Array.from(ignoredPatterns);
|
|
165
|
+
await (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.routeEvents.eventFilters(), JSON.stringify(filters));
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Failed to save filters
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
saveFilters();
|
|
171
|
+
}, [ignoredPatterns]);
|
|
172
|
+
|
|
173
|
+
// Event listener setup - keeps capturing even when minimized
|
|
174
|
+
(0, _react.useEffect)(() => {
|
|
175
|
+
if (!isListening) return;
|
|
176
|
+
|
|
177
|
+
// Set up event listener
|
|
178
|
+
const unsubscribe = routeObserver.addListener(event => {
|
|
179
|
+
lastEventRef.current = event;
|
|
180
|
+
setEvents(prev => {
|
|
181
|
+
const updated = [event, ...prev];
|
|
182
|
+
return updated.slice(0, 500);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
return () => {
|
|
186
|
+
unsubscribe();
|
|
187
|
+
};
|
|
188
|
+
}, [isListening, routeObserver]);
|
|
189
|
+
const handleToggleListening = (0, _react.useCallback)(() => {
|
|
190
|
+
setIsListening(prev => !prev);
|
|
191
|
+
}, []);
|
|
192
|
+
const handleClearEvents = (0, _react.useCallback)(() => {
|
|
193
|
+
if (events.length === 0) return;
|
|
194
|
+
|
|
195
|
+
// Gate clear behind Pro
|
|
196
|
+
if (!isPro) {
|
|
197
|
+
setShowUpgradeModal(true);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
_reactNative.Alert.alert("Clear Events", `Clear ${events.length} event${events.length !== 1 ? "s" : ""}?`, [{
|
|
201
|
+
text: "Cancel",
|
|
202
|
+
style: "cancel"
|
|
203
|
+
}, {
|
|
204
|
+
text: "Clear",
|
|
205
|
+
style: "destructive",
|
|
206
|
+
onPress: () => setEvents([])
|
|
207
|
+
}]);
|
|
208
|
+
}, [events.length, isPro]);
|
|
209
|
+
const handleTogglePattern = (0, _react.useCallback)(pattern => {
|
|
210
|
+
setIgnoredPatterns(prev => {
|
|
211
|
+
const next = new Set(prev);
|
|
212
|
+
if (next.has(pattern)) {
|
|
213
|
+
next.delete(pattern);
|
|
214
|
+
} else {
|
|
215
|
+
next.add(pattern);
|
|
216
|
+
}
|
|
217
|
+
return next;
|
|
218
|
+
});
|
|
219
|
+
}, []);
|
|
220
|
+
const handleAddPattern = (0, _react.useCallback)(pattern => {
|
|
221
|
+
setIgnoredPatterns(prev => new Set([...prev, pattern]));
|
|
222
|
+
}, []);
|
|
223
|
+
const handleToggleFilters = (0, _react.useCallback)(() => {
|
|
224
|
+
setShowFilters(!showFilters);
|
|
225
|
+
}, [showFilters]);
|
|
226
|
+
|
|
227
|
+
// Get all unique pathnames from events
|
|
228
|
+
const allEventPathnames = (0, _react.useMemo)(() => {
|
|
229
|
+
const pathnames = new Set();
|
|
230
|
+
events.forEach(event => {
|
|
231
|
+
if (event.pathname) {
|
|
232
|
+
pathnames.add(event.pathname);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return Array.from(pathnames).sort();
|
|
236
|
+
}, [events]);
|
|
237
|
+
|
|
238
|
+
// Filter events based on ignored patterns and search query
|
|
239
|
+
const filteredEvents = (0, _react.useMemo)(() => {
|
|
240
|
+
return events.filter(event => {
|
|
241
|
+
if (!event.pathname) return false;
|
|
242
|
+
|
|
243
|
+
// Filter out pathnames that match ignored patterns
|
|
244
|
+
const shouldIgnore = Array.from(ignoredPatterns).some(pattern => event.pathname.includes(pattern));
|
|
245
|
+
if (shouldIgnore) return false;
|
|
246
|
+
|
|
247
|
+
// Filter by search query (search in pathname and param values)
|
|
248
|
+
if (searchQuery.trim()) {
|
|
249
|
+
const query = searchQuery.toLowerCase();
|
|
250
|
+
const pathnameMatch = event.pathname.toLowerCase().includes(query);
|
|
251
|
+
const paramsMatch = Object.entries(event.params || {}).some(([key, value]) => {
|
|
252
|
+
const valueStr = Array.isArray(value) ? value.join(" ") : value;
|
|
253
|
+
return key.toLowerCase().includes(query) || valueStr.toLowerCase().includes(query);
|
|
254
|
+
});
|
|
255
|
+
return pathnameMatch || paramsMatch;
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
});
|
|
259
|
+
}, [events, ignoredPatterns, searchQuery]);
|
|
260
|
+
|
|
261
|
+
// Limit visible events for free tier
|
|
262
|
+
const visibleEvents = (0, _react.useMemo)(() => {
|
|
263
|
+
if (isPro) return filteredEvents;
|
|
264
|
+
return filteredEvents.slice(0, FREE_TIER_EVENT_LIMIT);
|
|
265
|
+
}, [filteredEvents, isPro]);
|
|
266
|
+
|
|
267
|
+
// Calculate locked event count
|
|
268
|
+
const lockedEventCount = (0, _react.useMemo)(() => {
|
|
269
|
+
if (isPro) return 0;
|
|
270
|
+
return Math.max(0, filteredEvents.length - FREE_TIER_EVENT_LIMIT);
|
|
271
|
+
}, [filteredEvents.length, isPro]);
|
|
272
|
+
|
|
273
|
+
// Calculate visit counts for each route (for duplicate detection)
|
|
274
|
+
const visitCounts = (0, _react.useMemo)(() => {
|
|
275
|
+
const counts = new Map();
|
|
276
|
+
const eventVisitNumbers = new Map();
|
|
277
|
+
|
|
278
|
+
// Iterate from oldest to newest to assign visit numbers correctly
|
|
279
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
280
|
+
const event = events[i];
|
|
281
|
+
const currentCount = counts.get(event.pathname) || 0;
|
|
282
|
+
const visitNumber = currentCount + 1;
|
|
283
|
+
counts.set(event.pathname, visitNumber);
|
|
284
|
+
eventVisitNumbers.set(i, visitNumber);
|
|
285
|
+
}
|
|
286
|
+
return eventVisitNumbers;
|
|
287
|
+
}, [events]);
|
|
288
|
+
|
|
289
|
+
// Prepare copy data - memoized so it only rebuilds when dependencies change
|
|
290
|
+
const copyAllEventsData = (0, _react.useMemo)(() => {
|
|
291
|
+
return {
|
|
292
|
+
summary: {
|
|
293
|
+
total: events.length,
|
|
294
|
+
filtered: filteredEvents.length,
|
|
295
|
+
listening: isListening,
|
|
296
|
+
ignoredPatterns: Array.from(ignoredPatterns),
|
|
297
|
+
timestamp: new Date().toISOString()
|
|
298
|
+
},
|
|
299
|
+
events: filteredEvents.map((event, index) => ({
|
|
300
|
+
index,
|
|
301
|
+
visitNumber: visitCounts.get(index) || 1,
|
|
302
|
+
pathname: event.pathname,
|
|
303
|
+
timestamp: event.timestamp,
|
|
304
|
+
timestampRelative: (0, _sharedUi.formatRelativeTime)(event.timestamp),
|
|
305
|
+
params: event.params,
|
|
306
|
+
segments: event.segments,
|
|
307
|
+
previousPathname: event.previousPathname,
|
|
308
|
+
timeSincePrevious: event.timeSincePrevious
|
|
309
|
+
}))
|
|
310
|
+
};
|
|
311
|
+
}, [events.length, filteredEvents, isListening, ignoredPatterns, visitCounts]);
|
|
312
|
+
if (!visible) return null;
|
|
313
|
+
const persistenceKey = enableSharedModalDimensions ? _sharedUi.devToolsStorageKeys.modal.root() : _sharedUi.devToolsStorageKeys.routeEvents.modal();
|
|
314
|
+
|
|
315
|
+
// No footer needed since we're showing a timeline
|
|
316
|
+
const footerNode = null;
|
|
317
|
+
const renderContent = () => {
|
|
318
|
+
if (activeTab === "routes") {
|
|
319
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_RoutesSitemap.RoutesSitemap, {
|
|
320
|
+
style: styles.contentWrapper
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (activeTab === "stack") {
|
|
324
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavigationStack.NavigationStack, {
|
|
325
|
+
style: styles.contentWrapper
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Events tab content
|
|
330
|
+
if (showFilters) {
|
|
331
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_RouteFilterViewV.RouteFilterViewV2, {
|
|
332
|
+
ignoredPatterns: ignoredPatterns,
|
|
333
|
+
onTogglePattern: handleTogglePattern,
|
|
334
|
+
onAddPattern: handleAddPattern,
|
|
335
|
+
availablePathnames: allEventPathnames
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Show search bar if there are events (even if filtered to 0)
|
|
340
|
+
const showSearchBar = events.length > 0;
|
|
341
|
+
if (filteredEvents.length === 0) {
|
|
342
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
343
|
+
style: styles.contentWrapper,
|
|
344
|
+
children: [showSearchBar && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
345
|
+
style: styles.searchContainer,
|
|
346
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SearchBar, {
|
|
347
|
+
value: searchQuery,
|
|
348
|
+
onChange: setSearchQuery,
|
|
349
|
+
placeholder: "Search pathname or params..."
|
|
350
|
+
})
|
|
351
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
352
|
+
style: styles.emptyState,
|
|
353
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Navigation, {
|
|
354
|
+
size: 48,
|
|
355
|
+
color: _sharedUi.buoyColors.textMuted
|
|
356
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
357
|
+
style: styles.emptyTitle,
|
|
358
|
+
children: searchQuery.trim() ? "No matching events" : isListening ? "No route events yet" : "Event listener is paused"
|
|
359
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
360
|
+
style: styles.emptySubtitle,
|
|
361
|
+
children: searchQuery.trim() ? "Try a different search term" : isListening ? "Navigation events will appear here" : "Press play to start monitoring"
|
|
362
|
+
})]
|
|
363
|
+
})]
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Show chronological timeline of events
|
|
368
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
369
|
+
style: styles.contentWrapper,
|
|
370
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
371
|
+
style: styles.searchContainer,
|
|
372
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SearchBar, {
|
|
373
|
+
value: searchQuery,
|
|
374
|
+
onChange: setSearchQuery,
|
|
375
|
+
placeholder: "Search pathname or params..."
|
|
376
|
+
})
|
|
377
|
+
}), lockedEventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
378
|
+
style: styles.lockedEventsBanner,
|
|
379
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
|
|
380
|
+
size: 14,
|
|
381
|
+
color: _sharedUi.buoyColors.warning
|
|
382
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
383
|
+
style: styles.lockedEventsBannerText,
|
|
384
|
+
children: [lockedEventCount, " older ", lockedEventCount === 1 ? 'event' : 'events', " locked"]
|
|
385
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
386
|
+
style: styles.lockedEventsBannerSubtext,
|
|
387
|
+
children: "Upgrade to Pro for full history"
|
|
388
|
+
})]
|
|
389
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_RouteEventsTimeline.RouteEventsTimeline, {
|
|
390
|
+
events: visibleEvents,
|
|
391
|
+
visitCounts: visitCounts,
|
|
392
|
+
onNavigate: handleNavigate
|
|
393
|
+
})]
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.JsModal, {
|
|
397
|
+
visible: visible,
|
|
398
|
+
onClose: onClose,
|
|
399
|
+
onMinimize: onMinimize,
|
|
400
|
+
persistenceKey: persistenceKey,
|
|
401
|
+
header: {
|
|
402
|
+
showToggleButton: true,
|
|
403
|
+
customContent: showFilters ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
404
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
405
|
+
onBack: () => setShowFilters(false)
|
|
406
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
407
|
+
title: "Filters"
|
|
408
|
+
})]
|
|
409
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
410
|
+
children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
411
|
+
onBack: onBack
|
|
412
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
413
|
+
title: "",
|
|
414
|
+
noMargin: true,
|
|
415
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
|
|
416
|
+
tabs: [{
|
|
417
|
+
key: "routes",
|
|
418
|
+
label: "Routes"
|
|
419
|
+
}, {
|
|
420
|
+
key: "events",
|
|
421
|
+
label: `Events${events.length > 0 && activeTab !== "events" ? ` (${events.length})` : ""}`
|
|
422
|
+
}, {
|
|
423
|
+
key: "stack",
|
|
424
|
+
label: "Stack"
|
|
425
|
+
}],
|
|
426
|
+
activeTab: activeTab,
|
|
427
|
+
onTabChange: tab => setActiveTab(tab)
|
|
428
|
+
})
|
|
429
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
|
|
430
|
+
children: activeTab === "events" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
431
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
|
|
432
|
+
value: copyAllEventsData,
|
|
433
|
+
buttonStyle: styles.iconButton
|
|
434
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
435
|
+
onPress: handleToggleFilters,
|
|
436
|
+
style: [styles.iconButton, ignoredPatterns.size > 0 && styles.activeFilterButton],
|
|
437
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
|
|
438
|
+
size: 14,
|
|
439
|
+
color: ignoredPatterns.size > 0 ? _sharedUi.buoyColors.primary : _sharedUi.buoyColors.textSecondary
|
|
440
|
+
})
|
|
441
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
442
|
+
onPress: handleToggleListening,
|
|
443
|
+
style: [styles.iconButton, isListening && styles.activeButton],
|
|
444
|
+
children: isListening ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pause, {
|
|
445
|
+
size: 14,
|
|
446
|
+
color: _sharedUi.buoyColors.success
|
|
447
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Play, {
|
|
448
|
+
size: 14,
|
|
449
|
+
color: _sharedUi.buoyColors.success
|
|
450
|
+
})
|
|
451
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
452
|
+
onPress: handleClearEvents,
|
|
453
|
+
style: styles.iconButton,
|
|
454
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
|
|
455
|
+
size: 14,
|
|
456
|
+
color: _sharedUi.buoyColors.error
|
|
457
|
+
})
|
|
458
|
+
})]
|
|
459
|
+
})
|
|
460
|
+
})]
|
|
461
|
+
})
|
|
462
|
+
},
|
|
463
|
+
onModeChange: handleModeChange,
|
|
464
|
+
enablePersistence: true,
|
|
465
|
+
initialMode: "bottomSheet",
|
|
466
|
+
enableGlitchEffects: true,
|
|
467
|
+
styles: {},
|
|
468
|
+
footer: footerNode,
|
|
469
|
+
footerHeight: footerNode ? 68 : 0,
|
|
470
|
+
children: [renderContent(), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
|
|
471
|
+
visible: showUpgradeModal,
|
|
472
|
+
onClose: () => setShowUpgradeModal(false),
|
|
473
|
+
featureName: "Route Event Management"
|
|
474
|
+
})]
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
const styles = _reactNative.StyleSheet.create({
|
|
478
|
+
iconButton: {
|
|
479
|
+
padding: 6,
|
|
480
|
+
borderRadius: 6,
|
|
481
|
+
backgroundColor: _sharedUi.buoyColors.input
|
|
482
|
+
},
|
|
483
|
+
activeButton: {
|
|
484
|
+
backgroundColor: _sharedUi.buoyColors.success + "1A"
|
|
485
|
+
},
|
|
486
|
+
activeFilterButton: {
|
|
487
|
+
backgroundColor: _sharedUi.buoyColors.primary + "1A"
|
|
488
|
+
},
|
|
489
|
+
emptyState: {
|
|
490
|
+
flex: 1,
|
|
491
|
+
justifyContent: "center",
|
|
492
|
+
alignItems: "center",
|
|
493
|
+
padding: 32
|
|
494
|
+
},
|
|
495
|
+
emptyTitle: {
|
|
496
|
+
color: _sharedUi.buoyColors.text,
|
|
497
|
+
fontSize: 16,
|
|
498
|
+
fontWeight: "600",
|
|
499
|
+
marginTop: 16,
|
|
500
|
+
marginBottom: 8,
|
|
501
|
+
fontFamily: "monospace",
|
|
502
|
+
letterSpacing: 0.5,
|
|
503
|
+
textTransform: "uppercase"
|
|
504
|
+
},
|
|
505
|
+
emptySubtitle: {
|
|
506
|
+
color: _sharedUi.buoyColors.textSecondary,
|
|
507
|
+
fontSize: 14,
|
|
508
|
+
textAlign: "center",
|
|
509
|
+
fontFamily: "monospace"
|
|
510
|
+
},
|
|
511
|
+
contentWrapper: {
|
|
512
|
+
flex: 1
|
|
513
|
+
},
|
|
514
|
+
statsContainer: {
|
|
515
|
+
paddingHorizontal: 16,
|
|
516
|
+
paddingVertical: 8,
|
|
517
|
+
backgroundColor: _sharedUi.buoyColors.card,
|
|
518
|
+
borderBottomWidth: 1,
|
|
519
|
+
borderBottomColor: _sharedUi.buoyColors.border
|
|
520
|
+
},
|
|
521
|
+
statsText: {
|
|
522
|
+
fontSize: 11,
|
|
523
|
+
color: _sharedUi.buoyColors.textSecondary,
|
|
524
|
+
fontFamily: "monospace",
|
|
525
|
+
textAlign: "center"
|
|
526
|
+
},
|
|
527
|
+
statsWarning: {
|
|
528
|
+
color: _sharedUi.buoyColors.warning,
|
|
529
|
+
fontWeight: "600"
|
|
530
|
+
},
|
|
531
|
+
searchContainer: {
|
|
532
|
+
paddingHorizontal: 16,
|
|
533
|
+
paddingVertical: 8,
|
|
534
|
+
backgroundColor: _sharedUi.buoyColors.base
|
|
535
|
+
},
|
|
536
|
+
lockedEventsBanner: {
|
|
537
|
+
flexDirection: "row",
|
|
538
|
+
alignItems: "center",
|
|
539
|
+
gap: 8,
|
|
540
|
+
backgroundColor: _sharedUi.buoyColors.warning + "15",
|
|
541
|
+
borderWidth: 1,
|
|
542
|
+
borderColor: _sharedUi.buoyColors.warning + "30",
|
|
543
|
+
borderRadius: 8,
|
|
544
|
+
paddingVertical: 10,
|
|
545
|
+
paddingHorizontal: 14,
|
|
546
|
+
marginHorizontal: 16,
|
|
547
|
+
marginBottom: 8
|
|
548
|
+
},
|
|
549
|
+
lockedEventsBannerText: {
|
|
550
|
+
fontSize: 12,
|
|
551
|
+
fontWeight: "600",
|
|
552
|
+
color: _sharedUi.buoyColors.warning,
|
|
553
|
+
fontFamily: "monospace"
|
|
554
|
+
},
|
|
555
|
+
lockedEventsBannerSubtext: {
|
|
556
|
+
fontSize: 11,
|
|
557
|
+
color: _sharedUi.buoyColors.textSecondary,
|
|
558
|
+
marginLeft: "auto"
|
|
559
|
+
}
|
|
560
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.RouteEventsTimeline = RouteEventsTimeline;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
+
var _RouteEventItemCompact = require("./RouteEventItemCompact");
|
|
11
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
|
+
/**
|
|
13
|
+
* RouteEventsTimeline - Chronological timeline of route navigation events
|
|
14
|
+
*
|
|
15
|
+
* Shows events in the order they happened (most recent first),
|
|
16
|
+
* providing a clear history of navigation actions.
|
|
17
|
+
*
|
|
18
|
+
* Uses two-level expansion pattern matching network/storage components.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function RouteEventsTimeline({
|
|
22
|
+
events,
|
|
23
|
+
visitCounts,
|
|
24
|
+
onNavigate
|
|
25
|
+
}) {
|
|
26
|
+
const [expandedIndex, setExpandedIndex] = (0, _react.useState)(null);
|
|
27
|
+
const handleItemPress = (0, _react.useCallback)(index => {
|
|
28
|
+
setExpandedIndex(prev => prev === index ? null : index);
|
|
29
|
+
}, []);
|
|
30
|
+
if (events.length === 0) {
|
|
31
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
32
|
+
style: styles.emptyState,
|
|
33
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Navigation, {
|
|
34
|
+
size: 48,
|
|
35
|
+
color: _sharedUi.buoyColors.textMuted
|
|
36
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
37
|
+
style: styles.emptyTitle,
|
|
38
|
+
children: "No events yet"
|
|
39
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
40
|
+
style: styles.emptySubtitle,
|
|
41
|
+
children: "Navigation events will appear here"
|
|
42
|
+
})]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
46
|
+
contentContainerStyle: styles.listContent,
|
|
47
|
+
children: events.map((event, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_RouteEventItemCompact.RouteEventItemCompact, {
|
|
48
|
+
event: event,
|
|
49
|
+
visitNumber: visitCounts.get(index) || 1,
|
|
50
|
+
isExpanded: expandedIndex === index,
|
|
51
|
+
onPress: () => handleItemPress(index),
|
|
52
|
+
onNavigate: onNavigate
|
|
53
|
+
}, `event-${index}-${event.timestamp}`))
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const styles = _reactNative.StyleSheet.create({
|
|
57
|
+
listContent: {
|
|
58
|
+
paddingVertical: 8
|
|
59
|
+
},
|
|
60
|
+
emptyState: {
|
|
61
|
+
flex: 1,
|
|
62
|
+
justifyContent: "center",
|
|
63
|
+
alignItems: "center",
|
|
64
|
+
padding: 32
|
|
65
|
+
},
|
|
66
|
+
emptyTitle: {
|
|
67
|
+
color: _sharedUi.buoyColors.text,
|
|
68
|
+
fontSize: 16,
|
|
69
|
+
fontWeight: "600",
|
|
70
|
+
marginTop: 16,
|
|
71
|
+
marginBottom: 8,
|
|
72
|
+
fontFamily: "monospace",
|
|
73
|
+
letterSpacing: 0.5,
|
|
74
|
+
textTransform: "uppercase"
|
|
75
|
+
},
|
|
76
|
+
emptySubtitle: {
|
|
77
|
+
color: _sharedUi.buoyColors.textSecondary,
|
|
78
|
+
fontSize: 14,
|
|
79
|
+
textAlign: "center",
|
|
80
|
+
fontFamily: "monospace"
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.RouteFilterViewV2 = RouteFilterViewV2;
|
|
7
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
9
|
+
function RouteFilterViewV2({
|
|
10
|
+
ignoredPatterns,
|
|
11
|
+
onTogglePattern,
|
|
12
|
+
onAddPattern,
|
|
13
|
+
availablePathnames = []
|
|
14
|
+
}) {
|
|
15
|
+
const filterConfig = {
|
|
16
|
+
addFilterSection: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
placeholder: "Enter pattern (e.g., /_sitemap)",
|
|
19
|
+
title: "ACTIVE FILTERS",
|
|
20
|
+
icon: _sharedUi.Filter
|
|
21
|
+
},
|
|
22
|
+
availableItemsSection: {
|
|
23
|
+
enabled: true,
|
|
24
|
+
title: "AVAILABLE ROUTES FROM EVENTS",
|
|
25
|
+
emptyMessage: "No routes available. Routes from navigation events will appear here.",
|
|
26
|
+
items: availablePathnames
|
|
27
|
+
},
|
|
28
|
+
howItWorksSection: {
|
|
29
|
+
enabled: true,
|
|
30
|
+
title: "HOW FILTERS WORK",
|
|
31
|
+
description: "Filtered routes will not appear in the route events list. Patterns match if the route contains the specified text.",
|
|
32
|
+
examples: ["• /_sitemap → filters /_sitemap routes", "• /api → filters /api/users, /api/posts", "• [id] → filters all routes with [id] param"],
|
|
33
|
+
icon: _sharedUi.Filter
|
|
34
|
+
},
|
|
35
|
+
onPatternToggle: onTogglePattern,
|
|
36
|
+
onPatternAdd: onAddPattern,
|
|
37
|
+
activePatterns: ignoredPatterns
|
|
38
|
+
};
|
|
39
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.DynamicFilterView, {
|
|
40
|
+
...filterConfig
|
|
41
|
+
});
|
|
42
|
+
}
|