reactoradar 1.5.9 → 1.6.0
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 +103 -31
- package/app.js +900 -153
- package/bin/setup.js +82 -6
- package/index.html +4 -0
- package/main.js +61 -9
- package/package.json +10 -2
- package/preload.js +3 -1
- package/styles.css +117 -13
package/bin/setup.js
CHANGED
|
@@ -85,23 +85,79 @@ function detectPlatform() {
|
|
|
85
85
|
const xcrun = tryExec('xcrun simctl list devices booted 2>/dev/null');
|
|
86
86
|
const hasIOSSim = xcrun.includes('Booted');
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
// Check if iOS real device is connected via USB
|
|
89
|
+
let hasIOSDevice = false;
|
|
90
|
+
// Method 1: idevice_id (from libimobiledevice — brew install libimobiledevice)
|
|
91
|
+
const idevice = tryExec('idevice_id -l 2>/dev/null');
|
|
92
|
+
if (idevice && idevice.trim().length > 0) hasIOSDevice = true;
|
|
93
|
+
// Method 2: system_profiler (slower but always available on macOS)
|
|
94
|
+
if (!hasIOSDevice) {
|
|
95
|
+
const profiler = tryExec('system_profiler SPUSBDataType 2>/dev/null');
|
|
96
|
+
if (profiler && (profiler.includes('iPhone') || profiler.includes('iPad'))) hasIOSDevice = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { hasAndroidEmu, hasAndroidDevice, hasIOSSim, hasIOSDevice };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getLanIP() {
|
|
103
|
+
// Get the Mac's LAN IP address for real device connections
|
|
104
|
+
const os = require('os');
|
|
105
|
+
const interfaces = os.networkInterfaces();
|
|
106
|
+
for (const name of Object.keys(interfaces)) {
|
|
107
|
+
for (const iface of interfaces[name]) {
|
|
108
|
+
// Skip internal/loopback and IPv6
|
|
109
|
+
if (iface.internal || iface.family !== 'IPv4') continue;
|
|
110
|
+
// Prefer en0 (Wi-Fi) or en1
|
|
111
|
+
if (iface.address && iface.address.startsWith('192.168.') || iface.address.startsWith('10.') || iface.address.startsWith('172.')) {
|
|
112
|
+
return iface.address;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
function pickHost(platform) {
|
|
92
|
-
|
|
120
|
+
const hasRealDevice = platform.hasAndroidDevice || platform.hasIOSDevice;
|
|
121
|
+
const hasSimOrEmu = platform.hasIOSSim || platform.hasAndroidEmu;
|
|
122
|
+
|
|
123
|
+
// Real device (iOS or Android) — needs LAN IP or adb reverse
|
|
124
|
+
if (platform.hasIOSDevice && !hasSimOrEmu) {
|
|
125
|
+
const lanIP = getLanIP();
|
|
126
|
+
if (lanIP) return { host: lanIP, reason: `iOS device detected via USB (LAN IP: ${lanIP})` };
|
|
127
|
+
return { host: '127.0.0.1', reason: 'iOS device detected but LAN IP not found — ensure Mac and device are on same WiFi' };
|
|
128
|
+
}
|
|
129
|
+
if (platform.hasAndroidDevice && !hasSimOrEmu) {
|
|
130
|
+
return { host: '10.0.2.2', reason: 'Android device detected (using adb reverse)' };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Simulator/Emulator only
|
|
134
|
+
if (platform.hasIOSSim && !platform.hasAndroidEmu && !hasRealDevice) {
|
|
93
135
|
return { host: '127.0.0.1', reason: 'iOS Simulator detected' };
|
|
94
136
|
}
|
|
95
|
-
if (platform.hasAndroidEmu && !platform.hasIOSSim) {
|
|
137
|
+
if (platform.hasAndroidEmu && !platform.hasIOSSim && !hasRealDevice) {
|
|
96
138
|
return { host: '10.0.2.2', reason: 'Android Emulator detected' };
|
|
97
139
|
}
|
|
98
|
-
|
|
99
|
-
|
|
140
|
+
|
|
141
|
+
// Mixed: real device + simulator/emulator
|
|
142
|
+
if (hasRealDevice && hasSimOrEmu) {
|
|
143
|
+
// If iOS device + iOS sim: prefer sim (127.0.0.1), user can switch for device
|
|
144
|
+
if (platform.hasIOSSim) {
|
|
145
|
+
return { host: '127.0.0.1', reason: 'iOS Sim + real device detected (defaulting to Sim — change HOST for device)' };
|
|
146
|
+
}
|
|
147
|
+
// Android device + emu: adb reverse handles both
|
|
148
|
+
return { host: '10.0.2.2', reason: 'Android Emu + device detected (adb reverse handles both)' };
|
|
100
149
|
}
|
|
150
|
+
|
|
151
|
+
// Both sim + emu
|
|
101
152
|
if (platform.hasIOSSim && platform.hasAndroidEmu) {
|
|
102
153
|
return { host: '127.0.0.1', reason: 'Both iOS Sim + Android Emu detected (defaulting to iOS, Android uses adb reverse)' };
|
|
103
154
|
}
|
|
104
|
-
|
|
155
|
+
|
|
156
|
+
// Nothing running — try to detect LAN IP for real device use later
|
|
157
|
+
const lanIP = getLanIP();
|
|
158
|
+
if (lanIP) {
|
|
159
|
+
return { host: '127.0.0.1', reason: `No running devices (default). For real device use HOST: ${lanIP}` };
|
|
160
|
+
}
|
|
105
161
|
return { host: '127.0.0.1', reason: 'No running devices detected (default)' };
|
|
106
162
|
}
|
|
107
163
|
|
|
@@ -422,6 +478,26 @@ ${SDK_MARKER_END}
|
|
|
422
478
|
console.log(C.dim + ' adb reverse tcp:9090 tcp:9090 && adb reverse tcp:9091 tcp:9091 && adb reverse tcp:9092 tcp:9092' + C.reset);
|
|
423
479
|
console.log();
|
|
424
480
|
}
|
|
481
|
+
if (platform.hasIOSDevice) {
|
|
482
|
+
const lanIP = getLanIP();
|
|
483
|
+
console.log(C.bold + ' iOS Real Device:' + C.reset);
|
|
484
|
+
if (lanIP) {
|
|
485
|
+
console.log(' HOST is set to ' + C.cyan + lanIP + C.reset + ' (your Mac\'s LAN IP)');
|
|
486
|
+
console.log(' Ensure your device is on the ' + C.bold + 'same WiFi network' + C.reset + ' as this Mac');
|
|
487
|
+
} else {
|
|
488
|
+
console.log(C.yellow + ' Could not detect LAN IP. Manually set HOST in src/debug/RNDebugSDK.js' + C.reset);
|
|
489
|
+
console.log(' to your Mac\'s IP (e.g., 192.168.1.x). Find it with: ' + C.cyan + 'ifconfig en0' + C.reset);
|
|
490
|
+
}
|
|
491
|
+
console.log();
|
|
492
|
+
}
|
|
493
|
+
// Show LAN IP tip even when no device is connected (for future reference)
|
|
494
|
+
if (!platform.hasIOSDevice && !platform.hasAndroidDevice) {
|
|
495
|
+
const lanIP = getLanIP();
|
|
496
|
+
if (lanIP) {
|
|
497
|
+
console.log(C.dim + ' For real device debugging, change HOST in src/debug/RNDebugSDK.js to: ' + C.cyan + lanIP + C.reset);
|
|
498
|
+
console.log();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
425
501
|
console.log(C.dim + ' To remove: npx reactoradar remove' + C.reset);
|
|
426
502
|
console.log();
|
|
427
503
|
}
|
package/index.html
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
<span id="deviceText">Waiting for device...</span>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="titlebar-actions">
|
|
24
|
+
<button class="tb-btn" id="btnScreenshot" title="Screenshot (⌘S)">
|
|
25
|
+
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" style="vertical-align:middle;margin-right:3px"><rect x="2" y="4" width="16" height="13" rx="2" stroke="currentColor" stroke-width="1.5"/><circle cx="10" cy="11" r="3" stroke="currentColor" stroke-width="1.5"/><path d="M7 4V3a1 1 0 011-1h4a1 1 0 011 1v1" stroke="currentColor" stroke-width="1.5"/></svg>
|
|
26
|
+
Screenshot
|
|
27
|
+
</button>
|
|
24
28
|
<button class="tb-btn primary" id="btnCDP" title="Open JS Debugger (⌘D)">JS Debugger ↗</button>
|
|
25
29
|
</div>
|
|
26
30
|
</header>
|
package/main.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { app, BrowserWindow, ipcMain, Menu, shell, nativeTheme, nativeImage } = require('electron');
|
|
3
|
+
const { app, BrowserWindow, ipcMain, Menu, shell, nativeTheme, nativeImage, dialog } = require('electron');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const http = require('http');
|
|
6
6
|
const https = require('https');
|
|
7
7
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
8
|
+
let autoUpdater = null;
|
|
9
|
+
try { autoUpdater = require('electron-updater').autoUpdater; } catch {}
|
|
8
10
|
|
|
9
11
|
// ─── Ports ────────────────────────────────────────────────────────────────────
|
|
10
12
|
const PORTS = {
|
|
@@ -154,6 +156,40 @@ function _semverCompare(a, b) {
|
|
|
154
156
|
|
|
155
157
|
function checkForUpdates() {
|
|
156
158
|
const currentVersion = require('./package.json').version;
|
|
159
|
+
|
|
160
|
+
// ─── Electron Auto-Updater (for .dmg installs) ────────────────────────────
|
|
161
|
+
// Downloads and installs updates from GitHub Releases automatically.
|
|
162
|
+
if (autoUpdater && app.isPackaged) {
|
|
163
|
+
autoUpdater.autoDownload = true;
|
|
164
|
+
autoUpdater.autoInstallOnAppQuit = true;
|
|
165
|
+
|
|
166
|
+
autoUpdater.on('update-available', (info) => {
|
|
167
|
+
console.log(`[AutoUpdate] New version available: ${info.version}`);
|
|
168
|
+
const payload = { current: currentVersion, latest: info.version, autoUpdate: true };
|
|
169
|
+
[500, 2000].forEach(delay => setTimeout(() => _send('update-available', payload), delay));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
autoUpdater.on('update-downloaded', (info) => {
|
|
173
|
+
console.log(`[AutoUpdate] Update downloaded: ${info.version}`);
|
|
174
|
+
_send('update-downloaded', { version: info.version });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
autoUpdater.on('error', (err) => {
|
|
178
|
+
console.warn('[AutoUpdate] Error:', err?.message);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Check after a short delay to not block startup
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
try { autoUpdater.checkForUpdates(); } catch {}
|
|
184
|
+
}, 5000);
|
|
185
|
+
// Also check periodically (every 2 hours)
|
|
186
|
+
setInterval(() => {
|
|
187
|
+
try { autoUpdater.checkForUpdates(); } catch {}
|
|
188
|
+
}, 2 * 60 * 60 * 1000);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Fallback: npm registry check (for npx users) ─────────────────────────
|
|
157
193
|
https.get('https://registry.npmjs.org/reactoradar/latest', (res) => {
|
|
158
194
|
let data = '';
|
|
159
195
|
res.on('data', d => data += d);
|
|
@@ -161,20 +197,15 @@ function checkForUpdates() {
|
|
|
161
197
|
try {
|
|
162
198
|
const latest = JSON.parse(data).version;
|
|
163
199
|
if (latest && _semverCompare(latest, currentVersion) > 0) {
|
|
164
|
-
|
|
165
|
-
const payload = { current: currentVersion, latest };
|
|
200
|
+
const payload = { current: currentVersion, latest, autoUpdate: false };
|
|
166
201
|
[500, 2000, 5000].forEach(delay => {
|
|
167
|
-
setTimeout(() =>
|
|
168
|
-
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
169
|
-
_send('update-available', payload);
|
|
170
|
-
}
|
|
171
|
-
}, delay);
|
|
202
|
+
setTimeout(() => _send('update-available', payload), delay);
|
|
172
203
|
});
|
|
173
204
|
console.log(`[Update] New version available: ${latest} (current: ${currentVersion})`);
|
|
174
205
|
}
|
|
175
206
|
} catch {}
|
|
176
207
|
});
|
|
177
|
-
}).on('error', () => {});
|
|
208
|
+
}).on('error', () => {});
|
|
178
209
|
}
|
|
179
210
|
|
|
180
211
|
// ─── CDP DevTools Window (JS breakpoints, Sources, Console) ──────────────────
|
|
@@ -522,6 +553,27 @@ function setupIPC() {
|
|
|
522
553
|
}
|
|
523
554
|
});
|
|
524
555
|
|
|
556
|
+
ipcMain.on('install-update', () => {
|
|
557
|
+
if (autoUpdater) {
|
|
558
|
+
autoUpdater.quitAndInstall(false, true);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
ipcMain.on('capture-screenshot', async () => {
|
|
563
|
+
try {
|
|
564
|
+
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
565
|
+
const image = await mainWindow.webContents.capturePage();
|
|
566
|
+
const png = image.toPNG();
|
|
567
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
568
|
+
const filePath = path.join(app.getPath('downloads'), `ReactoRadar-${ts}.png`);
|
|
569
|
+
require('fs').writeFileSync(filePath, png);
|
|
570
|
+
shell.showItemInFolder(filePath);
|
|
571
|
+
console.log(`[Screenshot] Saved to ${filePath}`);
|
|
572
|
+
} catch (e) {
|
|
573
|
+
console.error('[Screenshot] Failed:', e.message);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
525
577
|
ipcMain.on('set-theme', (_, theme) => {
|
|
526
578
|
nativeTheme.themeSource = theme === 'light' ? 'light' : 'dark';
|
|
527
579
|
const bg = theme === 'light' ? '#f5f6f8' : '#0a0b0e';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"description": "macOS debugger for React Native — Console, Sources, Network, Performance, Memory, Redux, AsyncStorage, React tree. Supports RN 0.74+ with Hermes and New Architecture.",
|
|
6
6
|
"main": "main.js",
|
|
7
7
|
"bin": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"pack": "electron-builder --mac --dir"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"electron-updater": "^6.8.3",
|
|
53
54
|
"react-devtools-core": "^5.3.1",
|
|
54
55
|
"ws": "^8.17.0"
|
|
55
56
|
},
|
|
@@ -59,8 +60,15 @@
|
|
|
59
60
|
"electron-devtools-installer": "^3.2.0"
|
|
60
61
|
},
|
|
61
62
|
"build": {
|
|
62
|
-
"appId": "com.
|
|
63
|
+
"appId": "com.reactoradar.app",
|
|
63
64
|
"productName": "ReactoRadar",
|
|
65
|
+
"publish": [
|
|
66
|
+
{
|
|
67
|
+
"provider": "github",
|
|
68
|
+
"owner": "sharanagouda",
|
|
69
|
+
"repo": "react-native-debugger"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
64
72
|
"mac": {
|
|
65
73
|
"category": "public.app-category.developer-tools",
|
|
66
74
|
"icon": "ReactoRadar.icns",
|
package/preload.js
CHANGED
|
@@ -10,7 +10,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
10
10
|
const allowed = [
|
|
11
11
|
'ports', 'cdp-targets', 'redux-event', 'storage-event', 'network-event',
|
|
12
12
|
'console-event', 'perf-event', 'ga4-event', 'redux-connected', 'storage-connected', 'network-connected',
|
|
13
|
-
'react-dt-status', 'trigger-open-cdp', 'clear-all-ui', 'theme-changed', 'update-available', 'app-version', 'focus-search',
|
|
13
|
+
'react-dt-status', 'trigger-open-cdp', 'clear-all-ui', 'theme-changed', 'update-available', 'update-downloaded', 'app-version', 'focus-search',
|
|
14
14
|
];
|
|
15
15
|
if (allowed.includes(channel)) {
|
|
16
16
|
ipcRenderer.removeAllListeners(channel);
|
|
@@ -29,4 +29,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
29
29
|
setMetroPort: (port) => ipcRenderer.send('set-metro-port', port),
|
|
30
30
|
readSourceFile: (filepath) => ipcRenderer.invoke('read-source-file', filepath),
|
|
31
31
|
openExternal: (url) => ipcRenderer.send('open-external', url),
|
|
32
|
+
installUpdate: () => ipcRenderer.send('install-update'),
|
|
33
|
+
captureScreenshot: () => ipcRenderer.send('capture-screenshot'),
|
|
32
34
|
});
|
package/styles.css
CHANGED
|
@@ -180,7 +180,7 @@ html, body, #app { height: 100%; overflow: hidden; }
|
|
|
180
180
|
body {
|
|
181
181
|
background: var(--bg);
|
|
182
182
|
color: var(--text);
|
|
183
|
-
font-family: '
|
|
183
|
+
font-family: 'SFMono-Regular', 'SF Mono', Menlo, Monaco, monospace;
|
|
184
184
|
font-size: 12px;
|
|
185
185
|
-webkit-font-smoothing: antialiased;
|
|
186
186
|
user-select: text;
|
|
@@ -773,7 +773,27 @@ body {
|
|
|
773
773
|
.net-initiator { color: var(--text-dim); font-size: 10px; }
|
|
774
774
|
.net-size { color: var(--text-dim); font-size: 10px; text-align: right; }
|
|
775
775
|
.net-time { color: var(--text-dim); font-size: 10px; text-align: right; }
|
|
776
|
-
.net-time.slow { color: var(--orange); }
|
|
776
|
+
.net-time.slow { color: var(--orange); font-weight: 700; }
|
|
777
|
+
.net-time.very-slow { color: var(--red); font-weight: 700; }
|
|
778
|
+
.net-row.slow { background: rgba(255,165,0,.04); }
|
|
779
|
+
.net-row.slow:hover { background: rgba(255,165,0,.08); }
|
|
780
|
+
.net-row.very-slow { background: rgba(255,94,114,.04); }
|
|
781
|
+
.net-row.very-slow:hover { background: rgba(255,94,114,.08); }
|
|
782
|
+
.net-row.slow .net-path { color: var(--orange); }
|
|
783
|
+
.net-row.very-slow .net-path { color: var(--red); }
|
|
784
|
+
|
|
785
|
+
/* Status filter buttons */
|
|
786
|
+
.net-status-filters { display: flex; gap: 4px; margin-left: 8px; }
|
|
787
|
+
.net-status-btn { font-size: 10px; padding: 2px 8px; border-radius: 3px; border: 1px solid var(--border); background: transparent; color: var(--text-dim); cursor: pointer; }
|
|
788
|
+
.net-status-btn:hover { background: var(--bg3); }
|
|
789
|
+
.net-status-btn.active { background: var(--bg4); color: var(--text); border-color: var(--accent); }
|
|
790
|
+
.net-slow-btn.active { border-color: var(--orange); color: var(--orange); }
|
|
791
|
+
|
|
792
|
+
/* Stats bar at bottom of network panel */
|
|
793
|
+
.net-stats-bar { display: flex; align-items: center; gap: 6px; padding: 4px 12px; font-size: 10px; color: var(--text-dim); background: var(--bg2); border-top: 1px solid var(--border); flex-shrink: 0; user-select: text; }
|
|
794
|
+
.net-stats-sep { color: var(--border); }
|
|
795
|
+
.net-stats-bar .warn { color: var(--orange); font-weight: 700; }
|
|
796
|
+
.net-stats-bar .err { color: var(--red); font-weight: 700; }
|
|
777
797
|
|
|
778
798
|
/* Waterfall bar */
|
|
779
799
|
.net-waterfall { position: relative; height: 14px; }
|
|
@@ -924,7 +944,10 @@ body {
|
|
|
924
944
|
.rdx-entry-header:hover { background: var(--bg3); }
|
|
925
945
|
.rdx-entry.selected .rdx-entry-header { border-left: 3px solid var(--accent); }
|
|
926
946
|
.rdx-index { font-size: 9px; color: var(--text-dim); min-width: 20px; text-align: right; flex-shrink: 0; }
|
|
927
|
-
.rdx-type {
|
|
947
|
+
.rdx-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-bright); font-weight: 500; }
|
|
948
|
+
.rdx-type-cat { font-weight: 700; }
|
|
949
|
+
.rdx-type-name { color: var(--text-bright); font-weight: 500; }
|
|
950
|
+
.rdx-header-right { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
928
951
|
.rdx-changes {
|
|
929
952
|
font-size: 8px; font-weight: 700; padding: 1px 5px; border-radius: 8px;
|
|
930
953
|
background: rgba(255,94,114,.12); color: var(--red); flex-shrink: 0;
|
|
@@ -968,12 +991,16 @@ body {
|
|
|
968
991
|
.rdx-diff-old { color: var(--red); text-decoration: line-through; opacity: 0.8; word-break: break-all; }
|
|
969
992
|
.rdx-diff-arrow { color: var(--text-dim); font-size: 10px; flex-shrink: 0; }
|
|
970
993
|
.rdx-diff-new { color: var(--green); font-weight: 600; word-break: break-all; }
|
|
971
|
-
.rdx-state-label { font-size: 10px; font-weight: 700; padding: 4px
|
|
994
|
+
.rdx-state-label { font-size: 10px; font-weight: 700; padding: 4px 0 2px; letter-spacing: 0.5px; }
|
|
972
995
|
.rdx-state-label.prev { color: var(--red); }
|
|
973
996
|
.rdx-state-label.curr { color: var(--green); }
|
|
974
|
-
.rdx-
|
|
975
|
-
.rdx-
|
|
976
|
-
.rdx-
|
|
997
|
+
.rdx-diff-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 4px; }
|
|
998
|
+
.rdx-diff-col { min-width: 0; overflow: auto; max-height: 400px; padding: 4px; border-radius: 4px; font-size: 11px; }
|
|
999
|
+
.rdx-diff-col.prev { background: rgba(255,94,114,.04); border: 1px solid rgba(255,94,114,.12); }
|
|
1000
|
+
.rdx-diff-col.curr { background: rgba(61,214,140,.04); border: 1px solid rgba(61,214,140,.12); }
|
|
1001
|
+
.rdx-close-btn { position: absolute; top: 6px; right: 8px; background: var(--bg3); border: 1px solid var(--border); color: var(--text-dim); font-size: 11px; width: 22px; height: 22px; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 1; }
|
|
1002
|
+
.rdx-close-btn:hover { background: var(--bg4); color: var(--text); }
|
|
1003
|
+
.rdx-entry-detail { position: relative; }
|
|
977
1004
|
.rdx-highlight { font-weight: 600; }
|
|
978
1005
|
.rdx-diff-sign { font-weight: 700; font-size: 11px; flex-shrink: 0; width: 14px; text-align: center; }
|
|
979
1006
|
.rdx-diff-row.removed .rdx-diff-sign { color: var(--red); }
|
|
@@ -1133,6 +1160,23 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1133
1160
|
padding: 24px;
|
|
1134
1161
|
max-width: 480px;
|
|
1135
1162
|
}
|
|
1163
|
+
.settings-two-col {
|
|
1164
|
+
display: grid;
|
|
1165
|
+
grid-template-columns: 1fr 1fr;
|
|
1166
|
+
gap: 24px;
|
|
1167
|
+
padding: 20px 24px;
|
|
1168
|
+
}
|
|
1169
|
+
.settings-col-left, .settings-col-right {
|
|
1170
|
+
min-width: 0;
|
|
1171
|
+
}
|
|
1172
|
+
.settings-shortcut-grid {
|
|
1173
|
+
display: grid;
|
|
1174
|
+
grid-template-columns: auto 1fr;
|
|
1175
|
+
gap: 6px 12px;
|
|
1176
|
+
font-size: 11px;
|
|
1177
|
+
}
|
|
1178
|
+
.sc-key { color: var(--text-mid); font-weight: 600; background: var(--bg3); padding: 2px 6px; border-radius: 3px; text-align: center; font-size: 10px; }
|
|
1179
|
+
.sc-label { color: var(--text-dim); }
|
|
1136
1180
|
.settings-section {
|
|
1137
1181
|
margin-bottom: 28px;
|
|
1138
1182
|
}
|
|
@@ -1448,9 +1492,9 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1448
1492
|
.ga4-row:hover { background: var(--bg3); }
|
|
1449
1493
|
.ga4-row.selected { background: var(--bg4); border-left: 2px solid var(--accent); }
|
|
1450
1494
|
|
|
1451
|
-
.ga4-cell { padding: 0
|
|
1452
|
-
.ga4-time { width:
|
|
1453
|
-
.ga4-name { flex: 1; color: var(--text-bright); font-weight:
|
|
1495
|
+
.ga4-cell { padding: 0 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1496
|
+
.ga4-time { width: 100px; color: var(--text-dim); font-size: inherit; flex-shrink: 0; padding-right: 12px; }
|
|
1497
|
+
.ga4-name { flex: 1; color: var(--text-bright); font-weight: 600; min-width: 0; font-size: 1.1em; }
|
|
1454
1498
|
|
|
1455
1499
|
/* Detail pane */
|
|
1456
1500
|
.ga4-detail-header {
|
|
@@ -1468,7 +1512,7 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1468
1512
|
.ga4-detail-info { margin-bottom: 8px; }
|
|
1469
1513
|
.ga4-detail-row { display: flex; gap: 12px; padding: 3px 0; font-size: 11px; }
|
|
1470
1514
|
.ga4-detail-key { color: var(--text-dim); min-width: 100px; flex-shrink: 0; }
|
|
1471
|
-
.ga4-detail-val { color: var(--text); word-break: break-
|
|
1515
|
+
.ga4-detail-val { color: var(--text); word-break: break-word; overflow-wrap: break-word; white-space: pre-wrap; line-height: 1.5; }
|
|
1472
1516
|
.ga4-detail-sep { height: 1px; background: var(--border); margin: 8px 0; }
|
|
1473
1517
|
|
|
1474
1518
|
.ga4-param-row {
|
|
@@ -1481,10 +1525,11 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1481
1525
|
}
|
|
1482
1526
|
.ga4-param-row:last-child { border-bottom: none; }
|
|
1483
1527
|
.ga4-param-key { color: var(--accent); width: 180px; min-width: 180px; flex-shrink: 0; font-weight: 500; padding-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1484
|
-
.ga4-param-val { color: var(--text); flex: 1; min-width: 0; }
|
|
1485
|
-
.ga4-param-val .ov-node, .ga4-param-val .ov-leaf { white-space:
|
|
1528
|
+
.ga4-param-val { color: var(--text); flex: 1; min-width: 0; word-break: break-word; overflow-wrap: break-word; white-space: pre-wrap; line-height: 1.5; }
|
|
1529
|
+
.ga4-param-val .ov-node, .ga4-param-val .ov-leaf { white-space: pre-wrap; word-break: break-word; }
|
|
1486
1530
|
.ga4-param-val .ov-key { white-space: nowrap; }
|
|
1487
1531
|
.ga4-param-val .ov-header { flex-wrap: nowrap; }
|
|
1532
|
+
.ga4-param-val .ov-str { word-break: break-word; overflow-wrap: break-word; white-space: pre-wrap; }
|
|
1488
1533
|
|
|
1489
1534
|
/* Summary bar */
|
|
1490
1535
|
.ga4-summary {
|
|
@@ -1562,3 +1607,62 @@ mark { background: rgba(79,172,255,.2); color: var(--accent); border-radius: 2px
|
|
|
1562
1607
|
[data-theme="light"] .net-cell {
|
|
1563
1608
|
border-right-color: #bcc1d0;
|
|
1564
1609
|
}
|
|
1610
|
+
|
|
1611
|
+
/* ── Toast Notifications ──────────────────────────────────────────────────── */
|
|
1612
|
+
.toast-container { position: fixed; bottom: 16px; right: 16px; z-index: 9999; display: flex; flex-direction: column; gap: 6px; max-width: 360px; pointer-events: none; }
|
|
1613
|
+
.toast { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 6px; font-size: 11px; background: var(--bg3); border: 1px solid var(--border); color: var(--text); box-shadow: 0 4px 16px rgba(0,0,0,.3); pointer-events: all; animation: toastIn 0.2s ease-out; }
|
|
1614
|
+
.toast-error { border-left: 3px solid var(--red); }
|
|
1615
|
+
.toast-warn { border-left: 3px solid var(--orange); }
|
|
1616
|
+
.toast-info { border-left: 3px solid var(--accent); }
|
|
1617
|
+
.toast-msg { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1618
|
+
.toast-action { color: var(--accent); font-weight: 600; cursor: pointer; font-size: 10px; flex-shrink: 0; }
|
|
1619
|
+
.toast-action:hover { text-decoration: underline; }
|
|
1620
|
+
.toast-close { color: var(--text-dim); cursor: pointer; font-size: 10px; flex-shrink: 0; margin-left: 4px; }
|
|
1621
|
+
.toast-close:hover { color: var(--text); }
|
|
1622
|
+
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
1623
|
+
|
|
1624
|
+
/* ── Context Menu Separator ────────────────────────────────────────────────── */
|
|
1625
|
+
.ctx-sep { height: 1px; background: var(--border); margin: 4px 6px; }
|
|
1626
|
+
|
|
1627
|
+
/* ── Hidden URLs Dropdown ─────────────────────────────────────────────────── */
|
|
1628
|
+
.net-hidden-wrap { display: inline-flex; }
|
|
1629
|
+
.net-hidden-btn { font-size: 10px !important; color: var(--text-dim) !important; }
|
|
1630
|
+
.net-hidden-dropdown { position: absolute; top: 100%; right: 0; z-index: 100; min-width: 320px; max-width: 480px; max-height: 300px; overflow-y: auto; background: var(--bg2); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,.3); padding: 6px 0; }
|
|
1631
|
+
.net-hidden-title { display: flex; align-items: center; justify-content: space-between; padding: 4px 10px 6px; font-size: 10px; font-weight: 600; color: var(--text-dim); border-bottom: 1px solid var(--border); margin-bottom: 4px; }
|
|
1632
|
+
.net-hidden-clear { font-size: 9px; background: transparent; border: 1px solid var(--border); color: var(--red); border-radius: 3px; padding: 2px 6px; cursor: pointer; }
|
|
1633
|
+
.net-hidden-clear:hover { background: rgba(255,94,114,.1); }
|
|
1634
|
+
.net-hidden-row { display: flex; align-items: center; gap: 8px; padding: 4px 10px; font-size: 10px; }
|
|
1635
|
+
.net-hidden-row:hover { background: var(--bg3); }
|
|
1636
|
+
.net-hidden-url { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-mid); }
|
|
1637
|
+
.net-hidden-unhide { font-size: 9px; background: transparent; border: 1px solid var(--border); color: var(--accent); border-radius: 3px; padding: 2px 6px; cursor: pointer; flex-shrink: 0; }
|
|
1638
|
+
.net-hidden-unhide:hover { background: rgba(61,136,255,.1); }
|
|
1639
|
+
|
|
1640
|
+
/* ── Support Button ────────────────────────────────────────────────────────── */
|
|
1641
|
+
.support-btn { background: linear-gradient(135deg, #ff813f, #ff5e72); color: #fff; border: none; padding: 8px 20px; border-radius: 8px; font-size: 12px; font-weight: 700; cursor: pointer; transition: all 0.15s; letter-spacing: 0.3px; }
|
|
1642
|
+
.support-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(255,94,114,.3); }
|
|
1643
|
+
.support-btn:active { transform: translateY(0); }
|
|
1644
|
+
|
|
1645
|
+
/* ── Tab Visibility Grid (Settings) ────────────────────────────────────────── */
|
|
1646
|
+
.tab-visibility-grid { display: flex; flex-direction: column; gap: 3px; max-width: 240px; }
|
|
1647
|
+
.tab-vis-item { display: flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 4px; border: 1px solid var(--border); cursor: pointer; transition: all 0.12s; user-select: none; }
|
|
1648
|
+
.tab-vis-item.active { background: var(--bg3); border-color: var(--accent); }
|
|
1649
|
+
.tab-vis-item.inactive { opacity: 0.5; background: transparent; }
|
|
1650
|
+
.tab-vis-item:hover { background: var(--bg3); }
|
|
1651
|
+
.tab-vis-check { accent-color: var(--accent); cursor: pointer; width: 13px; height: 13px; }
|
|
1652
|
+
.tab-vis-icon { font-size: 11px; width: 16px; text-align: center; }
|
|
1653
|
+
.tab-vis-label { font-size: 10px; font-weight: 500; color: var(--text); flex: 1; }
|
|
1654
|
+
.tab-vis-required { font-size: 7px; color: var(--text-dim); background: var(--bg4); padding: 1px 4px; border-radius: 3px; }
|
|
1655
|
+
.tab-vis-item.dragging { opacity: 0.4; border-style: dashed; }
|
|
1656
|
+
.tab-vis-item.drag-over { border-color: var(--accent); border-width: 2px; }
|
|
1657
|
+
.tab-vis-drag { cursor: grab; color: var(--text-dim); font-size: 10px; padding: 0 2px; }
|
|
1658
|
+
.tab-vis-drag:active { cursor: grabbing; }
|
|
1659
|
+
|
|
1660
|
+
/* ── Memory Warning Banner ─────────────────────────────────────────────────── */
|
|
1661
|
+
.memory-warning { display: flex; align-items: center; gap: 10px; padding: 6px 14px; background: rgba(255,165,0,.15); border-bottom: 1px solid rgba(255,165,0,.3); color: var(--orange); font-size: 11px; font-weight: 500; z-index: 9998; }
|
|
1662
|
+
.memory-warn-btn { font-size: 10px; padding: 3px 10px; border-radius: 4px; border: 1px solid var(--orange); background: transparent; color: var(--orange); cursor: pointer; font-weight: 600; }
|
|
1663
|
+
.memory-warn-btn:hover { background: rgba(255,165,0,.15); }
|
|
1664
|
+
.memory-warn-btn:first-of-type { background: var(--orange); color: #000; }
|
|
1665
|
+
.memory-warn-btn:first-of-type:hover { opacity: 0.9; }
|
|
1666
|
+
|
|
1667
|
+
/* ── Console Log Grouping ─────────────────────────────────────────────────── */
|
|
1668
|
+
.log-group-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 16px; padding: 0 4px; border-radius: 8px; background: var(--accent); color: #fff; font-size: 9px; font-weight: 700; flex-shrink: 0; margin-right: 4px; }
|