reactoradar 1.6.7 → 1.6.10
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/AGENTS.md +253 -230
- package/app.js +18 -19
- package/bin/setup.js +77 -15
- package/init.js +19 -7
- package/package.json +1 -1
- package/panels/console.js +7 -6
- package/panels/ga4.js +4 -1
- package/panels/native.js +4 -0
- package/panels/network.js +5 -1
- package/panels/performance.js +9 -2
- package/panels/react.js +2 -0
- package/panels/redux.js +4 -1
- package/panels/settings.js +15 -56
- package/panels/sources.js +7 -0
- package/panels/storage.js +7 -1
- package/sdk/RNDebugSDK.js +114 -40
package/bin/setup.js
CHANGED
|
@@ -181,6 +181,7 @@ function findStoreFile(projectDir) {
|
|
|
181
181
|
];
|
|
182
182
|
const storeNames = ['store.ts', 'store.js', 'store.tsx', 'store.jsx', 'index.ts', 'index.js', 'index.tsx'];
|
|
183
183
|
|
|
184
|
+
// 1. Check common store file locations
|
|
184
185
|
for (const dir of searchDirs) {
|
|
185
186
|
for (const name of storeNames) {
|
|
186
187
|
const p = path.join(projectDir, dir, name);
|
|
@@ -193,18 +194,32 @@ function findStoreFile(projectDir) {
|
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
//
|
|
197
|
+
// 2. Check App.tsx / App.js (common pattern: store created in root component)
|
|
198
|
+
const rootAppFiles = ['src/App.tsx', 'src/App.js', 'App.tsx', 'App.js', 'app/App.tsx', 'app/App.js'];
|
|
199
|
+
for (const f of rootAppFiles) {
|
|
200
|
+
const p = path.join(projectDir, f);
|
|
201
|
+
if (fileExists(p)) {
|
|
202
|
+
const content = fs.readFileSync(p, 'utf8');
|
|
203
|
+
if (content.includes('configureStore') || content.includes('createStore')) {
|
|
204
|
+
return f;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 3. Deep search: recursively find files with actual createStore()/configureStore() calls
|
|
210
|
+
// Skip files that merely reference configureStore as a parameter/type name
|
|
197
211
|
try {
|
|
198
212
|
const glob = (dir, depth) => {
|
|
199
213
|
if (depth > 4) return null;
|
|
200
214
|
let entries;
|
|
201
215
|
try { entries = fs.readdirSync(path.join(projectDir, dir), { withFileTypes: true }); } catch { return null; }
|
|
202
216
|
for (const e of entries) {
|
|
203
|
-
if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
|
|
217
|
+
if (e.name === 'node_modules' || e.name.startsWith('.') || e.name === 'debug') continue;
|
|
204
218
|
const rel = path.join(dir, e.name);
|
|
205
|
-
if (e.isFile() && /\.(ts|js|tsx|jsx)$/.test(e.name)
|
|
219
|
+
if (e.isFile() && /\.(ts|js|tsx|jsx)$/.test(e.name)) {
|
|
206
220
|
const content = fs.readFileSync(path.join(projectDir, rel), 'utf8');
|
|
207
|
-
|
|
221
|
+
// Must contain actual call: configureStore({ or createStore( — not just the word as a type/param
|
|
222
|
+
if (/configureStore\s*\(/.test(content) || /createStore\s*\(/.test(content)) {
|
|
208
223
|
return rel;
|
|
209
224
|
}
|
|
210
225
|
}
|
|
@@ -265,12 +280,20 @@ async function install(projectDir) {
|
|
|
265
280
|
process.exit(1);
|
|
266
281
|
}
|
|
267
282
|
|
|
268
|
-
// Read SDK, patch
|
|
283
|
+
// Read SDK, patch HOST_OVERRIDE for real device support
|
|
284
|
+
// The SDK auto-detects Android emulator vs iOS simulator at runtime.
|
|
285
|
+
// For real devices, we set HOST_OVERRIDE to the Mac's LAN IP.
|
|
269
286
|
let sdkContent = fs.readFileSync(sdkSrc, 'utf8');
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
287
|
+
const isRealDevice = reason.includes('device') || reason.includes('LAN IP');
|
|
288
|
+
if (isRealDevice && host !== '127.0.0.1' && host !== '10.0.2.2') {
|
|
289
|
+
sdkContent = sdkContent.replace(
|
|
290
|
+
/const HOST_OVERRIDE = null;/,
|
|
291
|
+
`const HOST_OVERRIDE = '${host}';`
|
|
292
|
+
);
|
|
293
|
+
log('HOST_OVERRIDE set to', C.bold + host + C.reset, '(real device LAN IP)');
|
|
294
|
+
} else {
|
|
295
|
+
log('HOST auto-detect enabled', C.dim + '(Android: 10.0.2.2, iOS: 127.0.0.1)' + C.reset);
|
|
296
|
+
}
|
|
274
297
|
try {
|
|
275
298
|
fs.writeFileSync(sdkDest, sdkContent);
|
|
276
299
|
} catch (e) {
|
|
@@ -383,12 +406,51 @@ ${SDK_MARKER_END}
|
|
|
383
406
|
warn('Could not auto-patch', storeFile, '— wire Redux manually');
|
|
384
407
|
}
|
|
385
408
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
409
|
+
} else if (storeContent.includes('createStore')) {
|
|
410
|
+
// Legacy createStore — try to auto-patch by adding reduxMiddleware to middleware array
|
|
411
|
+
let patched = storeContent;
|
|
412
|
+
let didPatch = false;
|
|
413
|
+
|
|
414
|
+
// Pattern 1: middleware array exists — push reduxMiddleware into it
|
|
415
|
+
// e.g. const middleware = []; or const middleware = [sagaMiddleware];
|
|
416
|
+
if (/(?:const|let|var)\s+middleware\s*=\s*\[/.test(storeContent) && !storeContent.includes('reduxMiddleware') && !storeContent.includes('RNDebugSDK')) {
|
|
417
|
+
// Add the require + push after the middleware array declaration
|
|
418
|
+
patched = patched.replace(
|
|
419
|
+
/((?:const|let|var)\s+middleware\s*=\s*\[[^\]]*\];?)/,
|
|
420
|
+
`$1\n if (__DEV__) { try { const { reduxMiddleware } = require('${relSDK}'); if (reduxMiddleware) middleware.push(reduxMiddleware); } catch {} }`
|
|
421
|
+
);
|
|
422
|
+
if (patched !== storeContent) {
|
|
423
|
+
didPatch = true;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Pattern 2: applyMiddleware(...middleware) exists but no middleware array — inject inline
|
|
428
|
+
if (!didPatch && storeContent.includes('applyMiddleware') && !storeContent.includes('reduxMiddleware') && !storeContent.includes('RNDebugSDK')) {
|
|
429
|
+
// Find the if (__DEV__) block near middleware setup and add there
|
|
430
|
+
// Or add before the createStore call
|
|
431
|
+
const createStoreMatch = storeContent.match(/createStore\s*\(/);
|
|
432
|
+
if (createStoreMatch) {
|
|
433
|
+
const idx = storeContent.indexOf(createStoreMatch[0]);
|
|
434
|
+
const insertPoint = storeContent.lastIndexOf('\n', idx);
|
|
435
|
+
if (insertPoint > 0) {
|
|
436
|
+
patched = storeContent.slice(0, insertPoint) +
|
|
437
|
+
`\n if (__DEV__) { try { const { reduxMiddleware } = require('${relSDK}'); if (reduxMiddleware) middleware.push(reduxMiddleware); } catch {} }` +
|
|
438
|
+
storeContent.slice(insertPoint);
|
|
439
|
+
if (patched !== storeContent) didPatch = true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (didPatch) {
|
|
445
|
+
fs.writeFileSync(storePath, patched);
|
|
446
|
+
log('Patched', C.bold + storeFile + C.reset, '— Redux middleware wired (legacy createStore)');
|
|
447
|
+
} else {
|
|
448
|
+
warn('Legacy createStore found at', C.bold + storeFile + C.reset);
|
|
449
|
+
console.log(C.dim + ' Could not auto-patch. Add manually:' + C.reset);
|
|
450
|
+
console.log(C.dim + ` if (__DEV__) { try { const { reduxMiddleware } = require('${relSDK}'); middleware.push(reduxMiddleware); } catch {} }` + C.reset);
|
|
451
|
+
console.log(C.dim + ' Add this BEFORE the createStore() call in your middleware setup.' + C.reset);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
392
454
|
} else {
|
|
393
455
|
warn('Redux detected but store file not found automatically');
|
|
394
456
|
console.log(C.dim + ' Add to your store setup:' + C.reset);
|
package/init.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
// ─── CDP Button ───────────────────────────────────────────────────────────────
|
|
7
7
|
$('btnCDP')?.addEventListener('click', () => {
|
|
8
|
-
window
|
|
8
|
+
// Tell main process to open the CDP DevTools window with the best available target
|
|
9
|
+
window.electronAPI?.openCDPTarget(null); // null = use latest known target
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
// ─── Screenshot Button ────────────────────────────────────────────────────────
|
|
@@ -37,7 +38,6 @@ if (window.electronAPI) {
|
|
|
37
38
|
window.electronAPI.on('network-event', handleNetworkEvent);
|
|
38
39
|
window.electronAPI.on('storage-event', handleStorageEvent);
|
|
39
40
|
|
|
40
|
-
window.electronAPI.on('console-event', addConsoleLog);
|
|
41
41
|
window.electronAPI.on('ga4-event', handleGA4Event);
|
|
42
42
|
|
|
43
43
|
window.electronAPI.on('perf-event', event => {
|
|
@@ -66,6 +66,7 @@ if (window.electronAPI) {
|
|
|
66
66
|
|
|
67
67
|
// Cmd+F — focus the search input for the active panel
|
|
68
68
|
function _handleFind() {
|
|
69
|
+
// If network detail is open, focus the detail search
|
|
69
70
|
if (state.activePanel === 'network' && state.network.selectedId) {
|
|
70
71
|
const wrap = $('detailSearchWrap');
|
|
71
72
|
const input = $('detailSearchInput');
|
|
@@ -88,12 +89,14 @@ if (window.electronAPI) {
|
|
|
88
89
|
const el = $(inputId);
|
|
89
90
|
if (el) { el.focus(); el.select(); }
|
|
90
91
|
}
|
|
92
|
+
// Also show/focus Console bottom find bar
|
|
91
93
|
if (state.activePanel === 'console') {
|
|
92
94
|
const bar = $('consoleFindBar');
|
|
93
95
|
if (bar) { bar.style.display = 'flex'; $('consoleFindInput')?.focus(); }
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
window.electronAPI.on('focus-search', _handleFind);
|
|
99
|
+
// Direct keyboard fallback — Electron menu accelerators can miss in some contexts
|
|
97
100
|
document.addEventListener('keydown', (e) => {
|
|
98
101
|
if ((e.metaKey || e.ctrlKey) && e.key === 'f') {
|
|
99
102
|
e.preventDefault();
|
|
@@ -104,6 +107,7 @@ if (window.electronAPI) {
|
|
|
104
107
|
window.electronAPI.on('app-version', (version, isPackaged) => {
|
|
105
108
|
state._appVersion = version;
|
|
106
109
|
state._isPackaged = !!isPackaged;
|
|
110
|
+
// Update anywhere the version is displayed
|
|
107
111
|
document.querySelectorAll('#aboutVersion').forEach(el => el.textContent = 'v' + version);
|
|
108
112
|
});
|
|
109
113
|
|
|
@@ -121,21 +125,27 @@ if (window.electronAPI) {
|
|
|
121
125
|
window.electronAPI?.openCDPTarget(null);
|
|
122
126
|
});
|
|
123
127
|
|
|
128
|
+
// Theme toggle from menu shortcut (Cmd+Shift+T)
|
|
124
129
|
window.electronAPI.on('theme-changed', theme => {
|
|
125
130
|
document.documentElement.setAttribute('data-theme', theme);
|
|
126
131
|
setStoredTheme(theme);
|
|
127
132
|
document.querySelectorAll('#themeSwitcher .theme-card')
|
|
128
133
|
.forEach(b => b.classList.toggle('active', b.dataset.theme === theme));
|
|
129
134
|
});
|
|
135
|
+
|
|
136
|
+
// Console event IPC
|
|
137
|
+
window.electronAPI.on('console-event', addConsoleLog);
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
// ─── Memory Monitor ──────────────────────────────────────────────────────────
|
|
141
|
+
// Check memory usage periodically and warn user before it causes blank screen
|
|
133
142
|
let _memoryWarningShown = false;
|
|
134
143
|
setInterval(() => {
|
|
135
144
|
if (!window.performance || !performance.memory) return;
|
|
136
145
|
const used = performance.memory.usedJSHeapSize;
|
|
137
146
|
const limit = performance.memory.jsHeapSizeLimit;
|
|
138
147
|
const pct = used / limit;
|
|
148
|
+
// Warn at 70% usage
|
|
139
149
|
if (pct > 0.7 && !_memoryWarningShown) {
|
|
140
150
|
_memoryWarningShown = true;
|
|
141
151
|
const banner = document.createElement('div');
|
|
@@ -147,6 +157,7 @@ setInterval(() => {
|
|
|
147
157
|
+ `<button class="memory-warn-btn" id="memWarnDismiss">Dismiss</button>`;
|
|
148
158
|
document.body.prepend(banner);
|
|
149
159
|
$('memWarnClear')?.addEventListener('click', () => {
|
|
160
|
+
// Clear all panel data
|
|
150
161
|
state.console.logs = []; _consolePending = [];
|
|
151
162
|
_lastLogMsg = ''; _lastLogRow = null; _lastLogCount = 1;
|
|
152
163
|
$('cBadge').textContent = '0'; renderConsole();
|
|
@@ -158,17 +169,19 @@ setInterval(() => {
|
|
|
158
169
|
});
|
|
159
170
|
$('memWarnDismiss')?.addEventListener('click', () => { banner.remove(); });
|
|
160
171
|
}
|
|
172
|
+
// Reset flag when memory drops
|
|
161
173
|
if (pct < 0.5) _memoryWarningShown = false;
|
|
162
|
-
}, 30000);
|
|
174
|
+
}, 30000); // Check every 30 seconds
|
|
175
|
+
|
|
176
|
+
// Apply saved theme + font size + font family + app name on load
|
|
163
177
|
|
|
164
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
165
|
-
// Apply saved settings on load
|
|
166
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
178
|
applyTheme(getStoredTheme());
|
|
168
179
|
applyFontSize(getStoredFontSize());
|
|
169
180
|
applyFontFamily(getStoredFontFamily());
|
|
170
181
|
applyAppName(getStoredAppName());
|
|
171
182
|
applyTabVisibility();
|
|
183
|
+
|
|
184
|
+
// Send stored metro port to backend
|
|
172
185
|
window.electronAPI?.setMetroPort(getStoredMetroPort());
|
|
173
186
|
|
|
174
187
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -182,6 +195,5 @@ initMemoryPanel();
|
|
|
182
195
|
initReduxPanel();
|
|
183
196
|
initStoragePanel();
|
|
184
197
|
initReactPanel();
|
|
185
|
-
initSourcesPanel();
|
|
186
198
|
initNativeLogsPanel();
|
|
187
199
|
initSettingsPanel();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.6.
|
|
4
|
+
"version": "1.6.10",
|
|
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": {
|
package/panels/console.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// ─── Console Panel
|
|
2
|
-
|
|
1
|
+
// ─── Console Panel + Shared Renderers ─────────────────────────────────────
|
|
3
2
|
// Load saved log level filters from localStorage
|
|
4
3
|
function getStoredLogLevels() {
|
|
5
4
|
try {
|
|
@@ -14,6 +13,7 @@ function setStoredLogLevels(levels) {
|
|
|
14
13
|
|
|
15
14
|
function initConsolePanel() {
|
|
16
15
|
const panel = $('panel-console');
|
|
16
|
+
if (!panel) return;
|
|
17
17
|
const levels = getStoredLogLevels();
|
|
18
18
|
state.console.levelFilters = levels;
|
|
19
19
|
state.console.showRedux = !!levels.redux;
|
|
@@ -321,10 +321,10 @@ function flushConsoleBatch() {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
// NOTE: console-event IPC listener is registered in init.js (not here)
|
|
325
|
-
// to keep all IPC registrations in one place and avoid duplicate listener issues.
|
|
326
324
|
|
|
327
|
-
//
|
|
325
|
+
// NOTE: console-event IPC listener is registered in init.js
|
|
326
|
+
|
|
327
|
+
// ─── Object Tree Renderer (Chrome DevTools-like) ─────────────────────────────
|
|
328
328
|
// Builds interactive, collapsible DOM nodes for objects/arrays.
|
|
329
329
|
|
|
330
330
|
// Collect all entries for an object: own data properties + prototype getter values.
|
|
@@ -709,7 +709,7 @@ function buildLogRow(l) {
|
|
|
709
709
|
return div;
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
// ─── Shared
|
|
712
|
+
// ─── Shared context menu helper ──────────────────────────────────────────────
|
|
713
713
|
function showContextMenu(e, items) {
|
|
714
714
|
document.querySelectorAll('.ctx-menu').forEach(el => el.remove());
|
|
715
715
|
const menu = document.createElement('div');
|
|
@@ -786,3 +786,4 @@ function renderConsole() {
|
|
|
786
786
|
list.appendChild(frag);
|
|
787
787
|
list.scrollTop = list.scrollHeight;
|
|
788
788
|
}
|
|
789
|
+
|
package/panels/ga4.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
// ─── GA4 Events Panel ──────────────────────────────────────────────────────
|
|
1
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// GA4 EVENT INSPECTOR
|
|
3
|
+
// GA4 EVENT INSPECTOR
|
|
3
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
5
|
const ga4State = { events: [], selected: -1, searchFilter: '', sortDir: 'desc' };
|
|
5
6
|
|
|
6
7
|
function initGA4Panel() {
|
|
7
8
|
const panel = $('panel-ga4');
|
|
9
|
+
if (!panel) return;
|
|
8
10
|
panel.innerHTML = `
|
|
9
11
|
<div class="panel-toolbar">
|
|
10
12
|
<span class="panel-label">GA4 Events</span>
|
|
@@ -326,3 +328,4 @@ function renderGA4Summary() {
|
|
|
326
328
|
summary.appendChild(chip);
|
|
327
329
|
});
|
|
328
330
|
}
|
|
331
|
+
|
package/panels/native.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// ─── Native Logs Panel ─────────────────────────────────────────────────────
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// NATIVE LOGS PANEL
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
5
|
const _nativeState = { logs: [], connected: false, platform: null, levelFilter: 'all', searchFilter: '' };
|
|
3
6
|
const MAX_NATIVE_LOGS = 2000;
|
|
4
7
|
|
|
@@ -254,3 +257,4 @@ function _renderNativeLogs() {
|
|
|
254
257
|
list.innerHTML = '';
|
|
255
258
|
_nativeState.logs.forEach(log => _appendNativeLog(log));
|
|
256
259
|
}
|
|
260
|
+
|
package/panels/network.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// ─── Network Panel ─────────────────────────────────────────────────────────
|
|
2
|
-
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// NETWORK PANEL (Chrome DevTools-style)
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
5
|
const NET_COLS = [
|
|
4
6
|
{ key: 'name', label: 'Name', width: 380, min: 150 },
|
|
5
7
|
{ key: 'status', label: 'Status', width: 60, min: 40 },
|
|
@@ -12,6 +14,7 @@ const NET_COLS = [
|
|
|
12
14
|
|
|
13
15
|
function initNetworkPanel() {
|
|
14
16
|
const panel = $('panel-network');
|
|
17
|
+
if (!panel) return;
|
|
15
18
|
panel.innerHTML = `
|
|
16
19
|
<div class="panel-toolbar">
|
|
17
20
|
<span class="panel-label">Network</span>
|
|
@@ -966,3 +969,4 @@ function buildCurlCommand(r) {
|
|
|
966
969
|
}
|
|
967
970
|
return cmd;
|
|
968
971
|
}
|
|
972
|
+
|
package/panels/performance.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// ─── Performance + Memory
|
|
1
|
+
// ─── Performance + Memory Panels ───────────────────────────────────────────
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// PERFORMANCE PANEL — FPS, render timing, JS thread
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
5
|
const perfState = { fps: [], jsThread: [], uiThread: [], recording: false, data: [] };
|
|
3
6
|
|
|
4
7
|
function initPerformancePanel() {
|
|
5
8
|
const panel = $('panel-performance');
|
|
9
|
+
if (!panel) return;
|
|
6
10
|
panel.innerHTML = `
|
|
7
11
|
<div class="panel-toolbar">
|
|
8
12
|
<span class="panel-label">Performance</span>
|
|
@@ -130,9 +134,12 @@ function handlePerfEvent(event) {
|
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
//
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// MEMORY PANEL — Heap snapshot summary via Hermes CDP
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
140
|
function initMemoryPanel() {
|
|
135
141
|
const panel = $('panel-memory');
|
|
142
|
+
if (!panel) return;
|
|
136
143
|
panel.innerHTML = `
|
|
137
144
|
<div class="panel-toolbar">
|
|
138
145
|
<span class="panel-label">Memory</span>
|
package/panels/react.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// ─── React Tree Panel ──────────────────────────────────────────────────────
|
|
2
2
|
function initReactPanel() {
|
|
3
3
|
const panel = $('panel-react');
|
|
4
|
+
if (!panel) return;
|
|
4
5
|
panel.innerHTML = `
|
|
5
6
|
<div class="panel-toolbar">
|
|
6
7
|
<span class="panel-label">React Tree</span>
|
|
@@ -19,3 +20,4 @@ function initReactPanel() {
|
|
|
19
20
|
window.electronAPI?.openReactDevTools();
|
|
20
21
|
});
|
|
21
22
|
}
|
|
23
|
+
|
package/panels/redux.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
// ─── Redux Panel ───────────────────────────────────────────────────────────
|
|
1
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// REDUX PANEL
|
|
3
|
+
// REDUX PANEL
|
|
3
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
5
|
function initReduxPanel() {
|
|
5
6
|
const panel = $('panel-redux');
|
|
7
|
+
if (!panel) return;
|
|
6
8
|
panel.innerHTML = `
|
|
7
9
|
<div class="panel-toolbar">
|
|
8
10
|
<span class="panel-label">Redux</span>
|
|
@@ -436,3 +438,4 @@ function renderRedux() {
|
|
|
436
438
|
selEl.scrollIntoView({ block: 'nearest', behavior: 'auto' });
|
|
437
439
|
}
|
|
438
440
|
}
|
|
441
|
+
|
package/panels/settings.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// ─── Settings Panel
|
|
2
|
-
|
|
3
|
-
// ─── Theme helpers ───────────────────────────────────────────────────────────
|
|
1
|
+
// ─── Settings Panel + Shared Utilities ─────────────────────────────────────
|
|
4
2
|
function getStoredTheme() {
|
|
5
3
|
try { return localStorage.getItem('rn-debug-theme') || 'dark'; } catch { return 'dark'; }
|
|
6
4
|
}
|
|
@@ -303,6 +301,7 @@ function applyFontSize(size) {
|
|
|
303
301
|
|
|
304
302
|
function initSettingsPanel() {
|
|
305
303
|
const panel = $('panel-settings');
|
|
304
|
+
if (!panel) return;
|
|
306
305
|
const current = getStoredTheme();
|
|
307
306
|
const currentSize = getStoredFontSize();
|
|
308
307
|
panel.innerHTML = `
|
|
@@ -594,13 +593,12 @@ function _loadVersionHistory() {
|
|
|
594
593
|
} catch { /* skip bad date */ }
|
|
595
594
|
}
|
|
596
595
|
|
|
597
|
-
// Build action
|
|
596
|
+
// Build action button based on install type
|
|
598
597
|
let actionHtml = '';
|
|
599
598
|
if (isCurrent) {
|
|
600
599
|
actionHtml = '<span class="version-installed">Installed</span>';
|
|
601
600
|
} else if (isPackaged) {
|
|
602
|
-
|
|
603
|
-
actionHtml = '<div class="version-dl-wrap"><button class="version-install-btn" title="Download this version">Download ▾</button></div>';
|
|
601
|
+
actionHtml = '<button class="version-install-btn" title="Download .dmg for this version">Download</button>';
|
|
604
602
|
} else {
|
|
605
603
|
actionHtml = `<button class="version-npm-btn" title="Copy npm install command">npx @${esc(r.version)}</button>`;
|
|
606
604
|
}
|
|
@@ -615,57 +613,14 @@ function _loadVersionHistory() {
|
|
|
615
613
|
<button class="version-notes-btn" title="View release notes">Notes</button>
|
|
616
614
|
</div>`;
|
|
617
615
|
|
|
618
|
-
// DMG
|
|
619
|
-
const dlWrap = row.querySelector('.version-dl-wrap');
|
|
616
|
+
// DMG download button — opens the .dmg asset or release page
|
|
620
617
|
const installBtn = row.querySelector('.version-install-btn');
|
|
621
|
-
if (installBtn
|
|
622
|
-
installBtn.addEventListener('click', (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
const menu = document.createElement('div');
|
|
628
|
-
menu.className = 'version-dl-menu';
|
|
629
|
-
|
|
630
|
-
// .dmg link
|
|
631
|
-
if (r.dmgUrl) {
|
|
632
|
-
const dmgItem = document.createElement('div');
|
|
633
|
-
dmgItem.className = 'version-dl-item';
|
|
634
|
-
dmgItem.innerHTML = `<span class="version-dl-icon">💿</span><div><div class="version-dl-name">.dmg Installer</div><div class="version-dl-hint">macOS installer (Apple Silicon)</div></div>`;
|
|
635
|
-
dmgItem.addEventListener('click', () => { window.electronAPI.openExternal(r.dmgUrl); menu.remove(); });
|
|
636
|
-
menu.appendChild(dmgItem);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// .zip link
|
|
640
|
-
if (r.zipUrl) {
|
|
641
|
-
const zipItem = document.createElement('div');
|
|
642
|
-
zipItem.className = 'version-dl-item';
|
|
643
|
-
zipItem.innerHTML = `<span class="version-dl-icon">📦</span><div><div class="version-dl-name">.zip Archive</div><div class="version-dl-hint">Portable zip archive</div></div>`;
|
|
644
|
-
zipItem.addEventListener('click', () => { window.electronAPI.openExternal(r.zipUrl); menu.remove(); });
|
|
645
|
-
menu.appendChild(zipItem);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// GitHub release page fallback
|
|
649
|
-
if (r.htmlUrl) {
|
|
650
|
-
const ghItem = document.createElement('div');
|
|
651
|
-
ghItem.className = 'version-dl-item';
|
|
652
|
-
ghItem.innerHTML = `<span class="version-dl-icon">🔗</span><div><div class="version-dl-name">GitHub Release</div><div class="version-dl-hint">View all assets on GitHub</div></div>`;
|
|
653
|
-
ghItem.addEventListener('click', () => { window.electronAPI.openExternal(r.htmlUrl); menu.remove(); });
|
|
654
|
-
menu.appendChild(ghItem);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// No assets fallback
|
|
658
|
-
if (!r.dmgUrl && !r.zipUrl && !r.htmlUrl) {
|
|
659
|
-
menu.innerHTML = '<div style="padding:8px 12px;color:var(--text-dim);font-size:10px">No downloads available</div>';
|
|
618
|
+
if (installBtn) {
|
|
619
|
+
installBtn.addEventListener('click', () => {
|
|
620
|
+
const url = r.dmgUrl || r.htmlUrl || '';
|
|
621
|
+
if (url) {
|
|
622
|
+
window.electronAPI.openExternal(url);
|
|
660
623
|
}
|
|
661
|
-
|
|
662
|
-
dlWrap.appendChild(menu);
|
|
663
|
-
|
|
664
|
-
// Close on outside click
|
|
665
|
-
setTimeout(() => {
|
|
666
|
-
const close = (ev) => { if (!menu.contains(ev.target) && ev.target !== installBtn) { menu.remove(); document.removeEventListener('click', close); } };
|
|
667
|
-
document.addEventListener('click', close);
|
|
668
|
-
}, 0);
|
|
669
624
|
});
|
|
670
625
|
}
|
|
671
626
|
|
|
@@ -707,7 +662,8 @@ function _loadVersionHistory() {
|
|
|
707
662
|
});
|
|
708
663
|
}
|
|
709
664
|
|
|
710
|
-
|
|
665
|
+
|
|
666
|
+
// ─── Update Banner & Changelog (shared, used by IPC handlers) ──────────────
|
|
711
667
|
// Reusable — called from IPC handler AND from initSettingsPanel
|
|
712
668
|
function _applyUpdateBanner() {
|
|
713
669
|
const info = state._updateAvailable;
|
|
@@ -830,3 +786,6 @@ async function _showChangelog(version) {
|
|
|
830
786
|
if (body) body.innerHTML = '<div style="color:var(--red);padding:20px;text-align:center">Could not fetch release notes</div>';
|
|
831
787
|
}
|
|
832
788
|
}
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
|
package/panels/sources.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// ─── Sources Panel ─────────────────────────────────────────────────────────
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// SOURCES PANEL (placeholder — use JS Debugger button for breakpoints)
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
5
|
function initSourcesPanel() {
|
|
3
6
|
const panel = $('panel-sources');
|
|
7
|
+
if (!panel) return;
|
|
4
8
|
panel.innerHTML = `
|
|
5
9
|
<div class="panel-toolbar">
|
|
6
10
|
<span class="panel-label">Sources</span>
|
|
@@ -280,3 +284,6 @@ async function loadSourceFile(filepath) {
|
|
|
280
284
|
function updateSourcesPanel(targets) {
|
|
281
285
|
// No-op: file list is populated by fetchSourceFileList from Metro source map
|
|
282
286
|
}
|
|
287
|
+
|
|
288
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
289
|
+
// PERFORMANCE PANEL — FPS, render timing, JS thread
|
package/panels/storage.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
// ─── AsyncStorage Panel ────────────────────────────────────────────────────
|
|
1
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
-
// ASYNC STORAGE PANEL
|
|
3
|
+
// ASYNC STORAGE PANEL
|
|
3
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
4
5
|
function initStoragePanel() {
|
|
5
6
|
const panel = $('panel-storage');
|
|
7
|
+
if (!panel) return;
|
|
6
8
|
panel.innerHTML = `
|
|
7
9
|
<div class="panel-toolbar">
|
|
8
10
|
<span class="panel-label">AsyncStorage</span>
|
|
@@ -183,3 +185,7 @@ function formatSize(bytes) {
|
|
|
183
185
|
if (bytes < 1024) return `${bytes}b`;
|
|
184
186
|
return `${(bytes/1024).toFixed(1)}kb`;
|
|
185
187
|
}
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
// REACT TREE PANEL
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────────────
|