@buoy-gg/benchmark 2.1.3 → 2.1.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/benchmarking/BenchmarkComparator.js +221 -1
- package/lib/commonjs/benchmarking/BenchmarkRecorder.js +497 -1
- package/lib/commonjs/benchmarking/BenchmarkStorage.js +235 -1
- package/lib/commonjs/benchmarking/index.js +83 -1
- package/lib/commonjs/benchmarking/types.js +13 -1
- package/lib/commonjs/components/BenchmarkCompareView.js +475 -1
- package/lib/commonjs/components/BenchmarkDetailView.js +346 -1
- package/lib/commonjs/components/BenchmarkModal.js +505 -1
- package/lib/commonjs/components/BenchmarkSessionCard.js +193 -1
- package/lib/commonjs/index.js +62 -1
- package/lib/commonjs/preset.js +86 -1
- package/lib/module/benchmarking/BenchmarkComparator.js +216 -1
- package/lib/module/benchmarking/BenchmarkRecorder.js +493 -1
- package/lib/module/benchmarking/BenchmarkStorage.js +227 -1
- package/lib/module/benchmarking/index.js +48 -1
- package/lib/module/benchmarking/types.js +13 -1
- package/lib/module/components/BenchmarkCompareView.js +469 -1
- package/lib/module/components/BenchmarkDetailView.js +340 -1
- package/lib/module/components/BenchmarkModal.js +499 -1
- package/lib/module/components/BenchmarkSessionCard.js +187 -1
- package/lib/module/index.js +39 -1
- package/lib/module/preset.js +81 -1
- package/lib/typescript/benchmarking/BenchmarkComparator.d.ts.map +1 -0
- package/lib/typescript/benchmarking/BenchmarkRecorder.d.ts.map +1 -0
- package/lib/typescript/benchmarking/BenchmarkStorage.d.ts.map +1 -0
- package/lib/typescript/benchmarking/index.d.ts.map +1 -0
- package/lib/typescript/benchmarking/types.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkCompareView.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkDetailView.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkModal.d.ts.map +1 -0
- package/lib/typescript/components/BenchmarkSessionCard.d.ts.map +1 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1,499 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BenchmarkModal
|
|
5
|
+
*
|
|
6
|
+
* Main modal component for the Benchmark dev tool.
|
|
7
|
+
* Provides recording controls, session list, detail views, and comparison.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
11
|
+
import { View, Text, TouchableOpacity, StyleSheet, FlatList, Alert } from "react-native";
|
|
12
|
+
import { JsModal, ModalHeader, macOSColors, devToolsStorageKeys, Trash2, RefreshCw, GitBranch, Edit3, Copy, copyToClipboard, PowerToggleButton } from "@buoy-gg/shared-ui";
|
|
13
|
+
import { benchmarkRecorder, BenchmarkStorage, BenchmarkComparator, createAsyncStorageAdapter } from "../benchmarking";
|
|
14
|
+
import { BenchmarkSessionCard } from "./BenchmarkSessionCard";
|
|
15
|
+
import { BenchmarkDetailView } from "./BenchmarkDetailView";
|
|
16
|
+
import { BenchmarkCompareView } from "./BenchmarkCompareView";
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
// Initialize storage
|
|
19
|
+
const storageAdapter = createAsyncStorageAdapter();
|
|
20
|
+
const storage = storageAdapter ? new BenchmarkStorage(storageAdapter) : null;
|
|
21
|
+
export function BenchmarkModal({
|
|
22
|
+
visible,
|
|
23
|
+
onClose,
|
|
24
|
+
onBack,
|
|
25
|
+
onMinimize,
|
|
26
|
+
enableSharedModalDimensions = false
|
|
27
|
+
}) {
|
|
28
|
+
// State
|
|
29
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
30
|
+
const [reports, setReports] = useState([]);
|
|
31
|
+
const [selectedReport, setSelectedReport] = useState(null);
|
|
32
|
+
const [viewMode, setViewMode] = useState("list");
|
|
33
|
+
const [selectionMode, setSelectionMode] = useState(false);
|
|
34
|
+
const [selectedIds, setSelectedIds] = useState(new Set());
|
|
35
|
+
const [compareReports, setCompareReports] = useState(null);
|
|
36
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
37
|
+
const hasLoadedState = useRef(false);
|
|
38
|
+
|
|
39
|
+
// Load reports on mount
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!visible || !storage) return;
|
|
42
|
+
const loadReports = async () => {
|
|
43
|
+
const list = await storage.listReports();
|
|
44
|
+
setReports(list);
|
|
45
|
+
};
|
|
46
|
+
loadReports();
|
|
47
|
+
}, [visible, refreshKey]);
|
|
48
|
+
|
|
49
|
+
// Subscribe to recorder state changes
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const unsubscribe = benchmarkRecorder.subscribe(event => {
|
|
52
|
+
if (event === "start") {
|
|
53
|
+
setIsRecording(true);
|
|
54
|
+
} else if (event === "stop") {
|
|
55
|
+
setIsRecording(false);
|
|
56
|
+
// Refresh list after stopping
|
|
57
|
+
setRefreshKey(k => k + 1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Sync initial state
|
|
62
|
+
setIsRecording(benchmarkRecorder.isRecording());
|
|
63
|
+
return unsubscribe;
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
// Handle start/stop recording
|
|
67
|
+
const handleToggleRecording = useCallback(async () => {
|
|
68
|
+
if (isRecording) {
|
|
69
|
+
// Stop and save
|
|
70
|
+
const report = benchmarkRecorder.stopSession();
|
|
71
|
+
if (report && storage) {
|
|
72
|
+
await storage.saveReport(report);
|
|
73
|
+
setRefreshKey(k => k + 1);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// Start new session
|
|
77
|
+
const timestamp = new Date().toLocaleString(undefined, {
|
|
78
|
+
month: "short",
|
|
79
|
+
day: "numeric",
|
|
80
|
+
hour: "2-digit",
|
|
81
|
+
minute: "2-digit"
|
|
82
|
+
});
|
|
83
|
+
benchmarkRecorder.startSession({
|
|
84
|
+
name: `Benchmark ${timestamp}`,
|
|
85
|
+
captureMemory: true,
|
|
86
|
+
verbose: false
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}, [isRecording]);
|
|
90
|
+
|
|
91
|
+
// Handle report press
|
|
92
|
+
const handleReportPress = useCallback(async metadata => {
|
|
93
|
+
if (selectionMode) {
|
|
94
|
+
// Toggle selection
|
|
95
|
+
setSelectedIds(prev => {
|
|
96
|
+
const next = new Set(prev);
|
|
97
|
+
if (next.has(metadata.id)) {
|
|
98
|
+
next.delete(metadata.id);
|
|
99
|
+
} else {
|
|
100
|
+
// Max 2 selections for comparison
|
|
101
|
+
if (next.size < 2) {
|
|
102
|
+
next.add(metadata.id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return next;
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
// Load and show detail
|
|
109
|
+
if (!storage) return;
|
|
110
|
+
const report = await storage.loadReport(metadata.id);
|
|
111
|
+
if (report) {
|
|
112
|
+
setSelectedReport(report);
|
|
113
|
+
setViewMode("detail");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}, [selectionMode]);
|
|
117
|
+
|
|
118
|
+
// Handle long press to enter selection mode
|
|
119
|
+
const handleReportLongPress = useCallback(metadata => {
|
|
120
|
+
setSelectionMode(true);
|
|
121
|
+
setSelectedIds(new Set([metadata.id]));
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
// Handle compare button
|
|
125
|
+
const handleCompare = useCallback(async () => {
|
|
126
|
+
if (selectedIds.size !== 2 || !storage) return;
|
|
127
|
+
const ids = Array.from(selectedIds);
|
|
128
|
+
const [report1, report2] = await Promise.all([storage.loadReport(ids[0]), storage.loadReport(ids[1])]);
|
|
129
|
+
if (report1 && report2) {
|
|
130
|
+
// Older report is baseline, newer is comparison
|
|
131
|
+
const [baseline, comparison] = report1.createdAt < report2.createdAt ? [report1, report2] : [report2, report1];
|
|
132
|
+
setCompareReports({
|
|
133
|
+
baseline,
|
|
134
|
+
comparison
|
|
135
|
+
});
|
|
136
|
+
setViewMode("compare");
|
|
137
|
+
setSelectionMode(false);
|
|
138
|
+
setSelectedIds(new Set());
|
|
139
|
+
}
|
|
140
|
+
}, [selectedIds]);
|
|
141
|
+
|
|
142
|
+
// Handle clear all
|
|
143
|
+
const handleClearAll = useCallback(async () => {
|
|
144
|
+
if (storage) {
|
|
145
|
+
await storage.clearAll();
|
|
146
|
+
setRefreshKey(k => k + 1);
|
|
147
|
+
}
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
// Handle delete selected
|
|
151
|
+
const handleDeleteSelected = useCallback(async () => {
|
|
152
|
+
if (selectedIds.size === 0 || !storage) return;
|
|
153
|
+
await Promise.all(Array.from(selectedIds).map(id => storage.deleteReport(id)));
|
|
154
|
+
setSelectedIds(new Set());
|
|
155
|
+
setSelectionMode(false);
|
|
156
|
+
setRefreshKey(k => k + 1);
|
|
157
|
+
}, [selectedIds]);
|
|
158
|
+
|
|
159
|
+
// Handle back navigation
|
|
160
|
+
const handleBack = useCallback(() => {
|
|
161
|
+
if (viewMode === "detail" || viewMode === "compare") {
|
|
162
|
+
setViewMode("list");
|
|
163
|
+
setSelectedReport(null);
|
|
164
|
+
setCompareReports(null);
|
|
165
|
+
} else if (selectionMode) {
|
|
166
|
+
setSelectionMode(false);
|
|
167
|
+
setSelectedIds(new Set());
|
|
168
|
+
} else if (onBack) {
|
|
169
|
+
onBack();
|
|
170
|
+
}
|
|
171
|
+
}, [viewMode, selectionMode, onBack]);
|
|
172
|
+
|
|
173
|
+
// Handle rename current report
|
|
174
|
+
const handleRename = useCallback(() => {
|
|
175
|
+
if (!selectedReport || !storage) return;
|
|
176
|
+
Alert.prompt("Rename Benchmark", "Enter a new name for this benchmark:", [{
|
|
177
|
+
text: "Cancel",
|
|
178
|
+
style: "cancel"
|
|
179
|
+
}, {
|
|
180
|
+
text: "Save",
|
|
181
|
+
onPress: async newName => {
|
|
182
|
+
if (newName && newName.trim()) {
|
|
183
|
+
await storage.updateReport(selectedReport.id, {
|
|
184
|
+
name: newName.trim()
|
|
185
|
+
});
|
|
186
|
+
// Update local state
|
|
187
|
+
setSelectedReport({
|
|
188
|
+
...selectedReport,
|
|
189
|
+
name: newName.trim()
|
|
190
|
+
});
|
|
191
|
+
setRefreshKey(k => k + 1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}], "plain-text", "");
|
|
195
|
+
}, [selectedReport]);
|
|
196
|
+
|
|
197
|
+
// Handle delete current report
|
|
198
|
+
const handleDeleteCurrent = useCallback(async () => {
|
|
199
|
+
if (!selectedReport || !storage) return;
|
|
200
|
+
await storage.deleteReport(selectedReport.id);
|
|
201
|
+
setViewMode("list");
|
|
202
|
+
setSelectedReport(null);
|
|
203
|
+
setRefreshKey(k => k + 1);
|
|
204
|
+
}, [selectedReport]);
|
|
205
|
+
|
|
206
|
+
// Handle copy compare results
|
|
207
|
+
const handleCopyCompare = useCallback(async () => {
|
|
208
|
+
if (!compareReports) return;
|
|
209
|
+
const {
|
|
210
|
+
baseline,
|
|
211
|
+
comparison
|
|
212
|
+
} = compareReports;
|
|
213
|
+
const result = BenchmarkComparator.compare(baseline, comparison);
|
|
214
|
+
const formatMs = ms => {
|
|
215
|
+
if (ms < 1) return `${(ms * 1000).toFixed(0)}μs`;
|
|
216
|
+
if (ms < 10) return `${ms.toFixed(2)}ms`;
|
|
217
|
+
if (ms < 100) return `${ms.toFixed(1)}ms`;
|
|
218
|
+
return `${ms.toFixed(0)}ms`;
|
|
219
|
+
};
|
|
220
|
+
const formatPercent = value => {
|
|
221
|
+
const sign = value >= 0 ? "+" : "";
|
|
222
|
+
return `${sign}${value.toFixed(1)}%`;
|
|
223
|
+
};
|
|
224
|
+
const lines = ["BENCHMARK COMPARISON RESULTS", "============================", "", `Overall: ${result.isImproved ? "IMPROVED" : "REGRESSED"} ${formatPercent(result.overallImprovement)}`, "", "COMPARING", "---------", `Baseline: ${baseline.name}`, ` Date: ${new Date(baseline.createdAt).toLocaleString()}`, `Comparison: ${comparison.name}`, ` Date: ${new Date(comparison.createdAt).toLocaleString()}`, "", "TIMING COMPARISON", "-----------------", `Measure Time: ${formatMs(baseline.stats.avgMeasureTime)} → ${formatMs(comparison.stats.avgMeasureTime)} (${formatPercent(result.measureTimeImprovement)})`, `Pipeline Time: ${formatMs(baseline.stats.avgTotalTime)} → ${formatMs(comparison.stats.avgTotalTime)} (${formatPercent(result.pipelineTimeImprovement)})`, `Filter Time: ${formatMs(baseline.stats.avgFilterTime)} → ${formatMs(comparison.stats.avgFilterTime)} (${formatPercent(result.filterTimeImprovement)})`, `Track Time: ${formatMs(baseline.stats.avgTrackTime)} → ${formatMs(comparison.stats.avgTrackTime)} (${formatPercent(result.trackTimeImprovement)})`, `Overlay Render: ${formatMs(baseline.stats.avgOverlayRenderTime)} → ${formatMs(comparison.stats.avgOverlayRenderTime)} (${formatPercent(result.overlayRenderImprovement)})`, "", "PERCENTILES", "-----------", `P50 (Median): ${formatMs(baseline.stats.p50TotalTime)} → ${formatMs(comparison.stats.p50TotalTime)}`, `P95: ${formatMs(baseline.stats.p95TotalTime)} → ${formatMs(comparison.stats.p95TotalTime)}`, `P99: ${formatMs(baseline.stats.p99TotalTime)} → ${formatMs(comparison.stats.p99TotalTime)}`, "", "BATCH STATISTICS", "----------------", `Total Batches: ${baseline.stats.batchCount} → ${comparison.stats.batchCount}`, `Nodes Processed: ${baseline.stats.totalNodesProcessed.toLocaleString()} → ${comparison.stats.totalNodesProcessed.toLocaleString()}`];
|
|
225
|
+
if (baseline.memoryDelta != null && comparison.memoryDelta != null) {
|
|
226
|
+
lines.push("", "MEMORY", "------", `Memory Delta: ${(baseline.memoryDelta / 1024 / 1024).toFixed(2)}MB → ${(comparison.memoryDelta / 1024 / 1024).toFixed(2)}MB`);
|
|
227
|
+
}
|
|
228
|
+
lines.push("", `Compared at: ${new Date(result.comparedAt).toLocaleString()}`);
|
|
229
|
+
await copyToClipboard(lines.join("\n"));
|
|
230
|
+
}, [compareReports]);
|
|
231
|
+
|
|
232
|
+
// Render list item
|
|
233
|
+
const renderItem = useCallback(({
|
|
234
|
+
item
|
|
235
|
+
}) => /*#__PURE__*/_jsx(BenchmarkSessionCard, {
|
|
236
|
+
metadata: item,
|
|
237
|
+
isSelected: selectedIds.has(item.id),
|
|
238
|
+
onPress: () => handleReportPress(item),
|
|
239
|
+
onLongPress: () => handleReportLongPress(item),
|
|
240
|
+
selectionMode: selectionMode
|
|
241
|
+
}), [selectionMode, selectedIds, handleReportPress, handleReportLongPress]);
|
|
242
|
+
const keyExtractor = useCallback(item => item.id, []);
|
|
243
|
+
|
|
244
|
+
// Render content
|
|
245
|
+
const renderContent = () => {
|
|
246
|
+
if (viewMode === "detail" && selectedReport) {
|
|
247
|
+
return /*#__PURE__*/_jsx(BenchmarkDetailView, {
|
|
248
|
+
report: selectedReport
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (viewMode === "compare" && compareReports) {
|
|
252
|
+
return /*#__PURE__*/_jsx(BenchmarkCompareView, {
|
|
253
|
+
baseline: compareReports.baseline,
|
|
254
|
+
comparison: compareReports.comparison
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// List view
|
|
259
|
+
if (reports.length === 0) {
|
|
260
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
261
|
+
style: styles.emptyState,
|
|
262
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
263
|
+
style: styles.emptyIcon,
|
|
264
|
+
children: "\uD83D\uDCCA"
|
|
265
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
266
|
+
style: styles.emptyTitle,
|
|
267
|
+
children: "No Benchmarks Yet"
|
|
268
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
269
|
+
style: styles.emptySubtitle,
|
|
270
|
+
children: "Press the record button to start capturing performance metrics"
|
|
271
|
+
})]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return /*#__PURE__*/_jsx(FlatList, {
|
|
275
|
+
data: reports,
|
|
276
|
+
renderItem: renderItem,
|
|
277
|
+
keyExtractor: keyExtractor,
|
|
278
|
+
contentContainerStyle: styles.listContent,
|
|
279
|
+
ItemSeparatorComponent: () => /*#__PURE__*/_jsx(View, {
|
|
280
|
+
style: styles.separator
|
|
281
|
+
}),
|
|
282
|
+
scrollEnabled: false
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
const persistenceKey = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : devToolsStorageKeys.benchmark.modal();
|
|
286
|
+
|
|
287
|
+
// Header title based on view mode
|
|
288
|
+
const headerTitle = useMemo(() => {
|
|
289
|
+
if (viewMode === "detail" && selectedReport) {
|
|
290
|
+
return selectedReport.name;
|
|
291
|
+
}
|
|
292
|
+
if (viewMode === "compare") {
|
|
293
|
+
return "Compare";
|
|
294
|
+
}
|
|
295
|
+
if (selectionMode) {
|
|
296
|
+
if (selectedIds.size === 0) {
|
|
297
|
+
return "Select 2 to compare";
|
|
298
|
+
}
|
|
299
|
+
if (selectedIds.size === 1) {
|
|
300
|
+
return "Select 1 more";
|
|
301
|
+
}
|
|
302
|
+
return `${selectedIds.size} Selected`;
|
|
303
|
+
}
|
|
304
|
+
return "Benchmarks";
|
|
305
|
+
}, [viewMode, selectedReport, selectionMode, selectedIds]);
|
|
306
|
+
|
|
307
|
+
// Determine back handler
|
|
308
|
+
const showBackButton = viewMode !== "list" || selectionMode || onBack;
|
|
309
|
+
|
|
310
|
+
// Early return AFTER all hooks to avoid "Rendered fewer hooks than expected" error
|
|
311
|
+
if (!visible) return null;
|
|
312
|
+
return /*#__PURE__*/_jsxs(JsModal, {
|
|
313
|
+
visible: visible,
|
|
314
|
+
onClose: onClose,
|
|
315
|
+
onBack: showBackButton ? handleBack : undefined,
|
|
316
|
+
onMinimize: onMinimize,
|
|
317
|
+
persistenceKey: persistenceKey,
|
|
318
|
+
header: {
|
|
319
|
+
showToggleButton: true,
|
|
320
|
+
customContent: /*#__PURE__*/_jsxs(ModalHeader, {
|
|
321
|
+
children: [showBackButton && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
322
|
+
onBack: handleBack
|
|
323
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
324
|
+
title: headerTitle
|
|
325
|
+
}), /*#__PURE__*/_jsxs(ModalHeader.Actions, {
|
|
326
|
+
children: [viewMode === "list" && !selectionMode && /*#__PURE__*/_jsxs(_Fragment, {
|
|
327
|
+
children: [/*#__PURE__*/_jsx(PowerToggleButton, {
|
|
328
|
+
isEnabled: isRecording,
|
|
329
|
+
onToggle: handleToggleRecording,
|
|
330
|
+
accessibilityLabel: "Toggle benchmark recording"
|
|
331
|
+
}), reports.length >= 2 && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
332
|
+
onPress: () => setSelectionMode(true),
|
|
333
|
+
style: styles.iconButton,
|
|
334
|
+
children: /*#__PURE__*/_jsx(GitBranch, {
|
|
335
|
+
size: 14,
|
|
336
|
+
color: macOSColors.semantic.info
|
|
337
|
+
})
|
|
338
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
339
|
+
onPress: () => setRefreshKey(k => k + 1),
|
|
340
|
+
style: styles.iconButton,
|
|
341
|
+
children: /*#__PURE__*/_jsx(RefreshCw, {
|
|
342
|
+
size: 14,
|
|
343
|
+
color: macOSColors.text.secondary
|
|
344
|
+
})
|
|
345
|
+
}), reports.length > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
346
|
+
onPress: handleClearAll,
|
|
347
|
+
style: styles.iconButton,
|
|
348
|
+
children: /*#__PURE__*/_jsx(Trash2, {
|
|
349
|
+
size: 14,
|
|
350
|
+
color: macOSColors.semantic.error
|
|
351
|
+
})
|
|
352
|
+
})]
|
|
353
|
+
}), viewMode === "detail" && selectedReport && /*#__PURE__*/_jsxs(_Fragment, {
|
|
354
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
355
|
+
onPress: handleRename,
|
|
356
|
+
style: styles.iconButton,
|
|
357
|
+
children: /*#__PURE__*/_jsx(Edit3, {
|
|
358
|
+
size: 14,
|
|
359
|
+
color: macOSColors.text.secondary
|
|
360
|
+
})
|
|
361
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
362
|
+
onPress: handleDeleteCurrent,
|
|
363
|
+
style: styles.iconButton,
|
|
364
|
+
children: /*#__PURE__*/_jsx(Trash2, {
|
|
365
|
+
size: 14,
|
|
366
|
+
color: macOSColors.semantic.error
|
|
367
|
+
})
|
|
368
|
+
})]
|
|
369
|
+
}), viewMode === "compare" && compareReports && /*#__PURE__*/_jsx(_Fragment, {
|
|
370
|
+
children: /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
371
|
+
onPress: handleCopyCompare,
|
|
372
|
+
style: styles.iconButton,
|
|
373
|
+
children: /*#__PURE__*/_jsx(Copy, {
|
|
374
|
+
size: 14,
|
|
375
|
+
color: macOSColors.text.secondary
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
}), selectionMode && /*#__PURE__*/_jsxs(_Fragment, {
|
|
379
|
+
children: [selectedIds.size === 2 && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
380
|
+
onPress: handleCompare,
|
|
381
|
+
style: [styles.iconButton, styles.compareButton],
|
|
382
|
+
children: /*#__PURE__*/_jsx(GitBranch, {
|
|
383
|
+
size: 14,
|
|
384
|
+
color: macOSColors.semantic.info
|
|
385
|
+
})
|
|
386
|
+
}), selectedIds.size > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
387
|
+
onPress: handleDeleteSelected,
|
|
388
|
+
style: styles.iconButton,
|
|
389
|
+
children: /*#__PURE__*/_jsx(Trash2, {
|
|
390
|
+
size: 14,
|
|
391
|
+
color: macOSColors.semantic.error
|
|
392
|
+
})
|
|
393
|
+
})]
|
|
394
|
+
})]
|
|
395
|
+
})]
|
|
396
|
+
})
|
|
397
|
+
},
|
|
398
|
+
enablePersistence: true,
|
|
399
|
+
initialMode: "bottomSheet",
|
|
400
|
+
enableGlitchEffects: true,
|
|
401
|
+
children: [isRecording && viewMode === "list" && /*#__PURE__*/_jsxs(View, {
|
|
402
|
+
style: styles.recordingBanner,
|
|
403
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
404
|
+
style: styles.recordingDot
|
|
405
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
406
|
+
style: styles.recordingText,
|
|
407
|
+
children: "Recording..."
|
|
408
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
409
|
+
onPress: handleToggleRecording,
|
|
410
|
+
style: styles.stopButton,
|
|
411
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
412
|
+
style: styles.stopButtonText,
|
|
413
|
+
children: "Stop"
|
|
414
|
+
})
|
|
415
|
+
})]
|
|
416
|
+
}), renderContent()]
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
const styles = StyleSheet.create({
|
|
420
|
+
iconButton: {
|
|
421
|
+
width: 32,
|
|
422
|
+
height: 32,
|
|
423
|
+
borderRadius: 8,
|
|
424
|
+
backgroundColor: macOSColors.background.hover,
|
|
425
|
+
borderWidth: 1,
|
|
426
|
+
borderColor: macOSColors.border.default,
|
|
427
|
+
alignItems: "center",
|
|
428
|
+
justifyContent: "center"
|
|
429
|
+
},
|
|
430
|
+
compareButton: {
|
|
431
|
+
backgroundColor: macOSColors.semantic.infoBackground,
|
|
432
|
+
borderColor: macOSColors.semantic.info
|
|
433
|
+
},
|
|
434
|
+
listContent: {
|
|
435
|
+
paddingVertical: 16
|
|
436
|
+
},
|
|
437
|
+
separator: {
|
|
438
|
+
height: 8
|
|
439
|
+
},
|
|
440
|
+
emptyState: {
|
|
441
|
+
flex: 1,
|
|
442
|
+
justifyContent: "center",
|
|
443
|
+
alignItems: "center",
|
|
444
|
+
padding: 32
|
|
445
|
+
},
|
|
446
|
+
emptyIcon: {
|
|
447
|
+
fontSize: 48,
|
|
448
|
+
marginBottom: 16
|
|
449
|
+
},
|
|
450
|
+
emptyTitle: {
|
|
451
|
+
fontSize: 16,
|
|
452
|
+
fontWeight: "600",
|
|
453
|
+
color: macOSColors.text.primary,
|
|
454
|
+
fontFamily: "monospace",
|
|
455
|
+
marginBottom: 8
|
|
456
|
+
},
|
|
457
|
+
emptySubtitle: {
|
|
458
|
+
fontSize: 13,
|
|
459
|
+
color: macOSColors.text.secondary,
|
|
460
|
+
fontFamily: "monospace",
|
|
461
|
+
textAlign: "center"
|
|
462
|
+
},
|
|
463
|
+
recordingBanner: {
|
|
464
|
+
flexDirection: "row",
|
|
465
|
+
alignItems: "center",
|
|
466
|
+
backgroundColor: macOSColors.semantic.errorBackground,
|
|
467
|
+
paddingHorizontal: 16,
|
|
468
|
+
paddingVertical: 12,
|
|
469
|
+
borderBottomWidth: 1,
|
|
470
|
+
borderColor: macOSColors.semantic.error
|
|
471
|
+
},
|
|
472
|
+
recordingDot: {
|
|
473
|
+
width: 8,
|
|
474
|
+
height: 8,
|
|
475
|
+
borderRadius: 4,
|
|
476
|
+
backgroundColor: macOSColors.semantic.error,
|
|
477
|
+
marginRight: 8
|
|
478
|
+
},
|
|
479
|
+
recordingText: {
|
|
480
|
+
flex: 1,
|
|
481
|
+
fontSize: 13,
|
|
482
|
+
fontWeight: "600",
|
|
483
|
+
color: macOSColors.semantic.error,
|
|
484
|
+
fontFamily: "monospace"
|
|
485
|
+
},
|
|
486
|
+
stopButton: {
|
|
487
|
+
paddingHorizontal: 12,
|
|
488
|
+
paddingVertical: 6,
|
|
489
|
+
backgroundColor: macOSColors.semantic.error,
|
|
490
|
+
borderRadius: 4
|
|
491
|
+
},
|
|
492
|
+
stopButtonText: {
|
|
493
|
+
fontSize: 12,
|
|
494
|
+
fontWeight: "600",
|
|
495
|
+
color: "#fff",
|
|
496
|
+
fontFamily: "monospace"
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
export default BenchmarkModal;
|