reactoradar 1.4.1 → 1.5.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/app.js +124 -18
- package/assets/generate_icons.py +175 -0
- package/assets/icon.iconset/icon_128x128.png +0 -0
- package/assets/icon.iconset/icon_128x128@2x.png +0 -0
- package/assets/icon.iconset/icon_16x16.png +0 -0
- package/assets/icon.iconset/icon_16x16@2x.png +0 -0
- package/assets/icon.iconset/icon_256x256.png +0 -0
- package/assets/icon.iconset/icon_256x256@2x.png +0 -0
- package/assets/icon.iconset/icon_32x32.png +0 -0
- package/assets/icon.iconset/icon_32x32@2x.png +0 -0
- package/assets/icon.iconset/icon_512x512.png +0 -0
- package/assets/icon.iconset/icon_512x512@2x.png +0 -0
- package/main.js +8 -2
- package/package.json +1 -1
- package/preload.js +1 -1
- package/styles.css +36 -0
package/app.js
CHANGED
|
@@ -6,13 +6,13 @@ const state = {
|
|
|
6
6
|
activePanel: 'console',
|
|
7
7
|
ports: {},
|
|
8
8
|
|
|
9
|
-
console: { logs: [], levelFilters: { log: true, info: true, warn: true, error: true, debug: true }, searchFilter: '' },
|
|
9
|
+
console: { logs: [], levelFilters: { log: true, info: true, warn: true, error: true, debug: true }, searchFilter: '', showRedux: false },
|
|
10
10
|
|
|
11
11
|
network: {
|
|
12
12
|
requests: {},
|
|
13
13
|
order: [],
|
|
14
14
|
statusFilter: 'all',
|
|
15
|
-
typeFilter: '
|
|
15
|
+
typeFilter: 'fetch',
|
|
16
16
|
searchFilter: '',
|
|
17
17
|
throttle: 'none',
|
|
18
18
|
enabled: true,
|
|
@@ -26,6 +26,7 @@ const state = {
|
|
|
26
26
|
states: [],
|
|
27
27
|
selected: -1,
|
|
28
28
|
searchFilter: '',
|
|
29
|
+
sortDir: 'asc',
|
|
29
30
|
},
|
|
30
31
|
|
|
31
32
|
storage: {
|
|
@@ -233,6 +234,27 @@ if (window.electronAPI) {
|
|
|
233
234
|
|
|
234
235
|
window.electronAPI.on('clear-all-ui', clearAll);
|
|
235
236
|
|
|
237
|
+
// Cmd+F — focus the search input for the active panel
|
|
238
|
+
window.electronAPI.on('focus-search', () => {
|
|
239
|
+
const searchMap = {
|
|
240
|
+
console: 'consoleSearch',
|
|
241
|
+
network: 'netSearchInput',
|
|
242
|
+
ga4: 'ga4Search',
|
|
243
|
+
redux: 'reduxSearch',
|
|
244
|
+
storage: 'storageSearch',
|
|
245
|
+
};
|
|
246
|
+
const inputId = searchMap[state.activePanel];
|
|
247
|
+
if (inputId) {
|
|
248
|
+
const el = $(inputId);
|
|
249
|
+
if (el) { el.focus(); el.select(); }
|
|
250
|
+
}
|
|
251
|
+
// Also show/focus Console bottom find bar
|
|
252
|
+
if (state.activePanel === 'console') {
|
|
253
|
+
const bar = $('consoleFindBar');
|
|
254
|
+
if (bar) { bar.style.display = 'flex'; $('consoleFindInput')?.focus(); }
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
236
258
|
window.electronAPI.on('app-version', (version) => {
|
|
237
259
|
state._appVersion = version;
|
|
238
260
|
const el = $('aboutVersion');
|
|
@@ -299,7 +321,7 @@ function getStoredLogLevels() {
|
|
|
299
321
|
const saved = localStorage.getItem('rn-debug-log-levels');
|
|
300
322
|
if (saved) return JSON.parse(saved);
|
|
301
323
|
} catch {}
|
|
302
|
-
return { log: true, info: true, warn: true, error: true, debug: true };
|
|
324
|
+
return { log: true, info: true, warn: true, error: true, debug: true, redux: false };
|
|
303
325
|
}
|
|
304
326
|
function setStoredLogLevels(levels) {
|
|
305
327
|
try { localStorage.setItem('rn-debug-log-levels', JSON.stringify(levels)); } catch {}
|
|
@@ -309,6 +331,7 @@ function initConsolePanel() {
|
|
|
309
331
|
const panel = $('panel-console');
|
|
310
332
|
const levels = getStoredLogLevels();
|
|
311
333
|
state.console.levelFilters = levels;
|
|
334
|
+
state.console.showRedux = !!levels.redux;
|
|
312
335
|
|
|
313
336
|
panel.innerHTML = `
|
|
314
337
|
<div class="panel-toolbar">
|
|
@@ -325,6 +348,8 @@ function initConsolePanel() {
|
|
|
325
348
|
<label class="console-level-option"><input type="checkbox" data-level="warn" ${levels.warn ? 'checked' : ''} /><span class="lvl-dot" style="background:var(--yellow)"></span>Warn</label>
|
|
326
349
|
<label class="console-level-option"><input type="checkbox" data-level="error" ${levels.error ? 'checked' : ''} /><span class="lvl-dot" style="background:var(--red)"></span>Error</label>
|
|
327
350
|
<label class="console-level-option"><input type="checkbox" data-level="debug" ${levels.debug ? 'checked' : ''} /><span class="lvl-dot" style="background:var(--accent2)"></span>Debug</label>
|
|
351
|
+
<div style="border-top:1px solid var(--border);margin:4px 0"></div>
|
|
352
|
+
<label class="console-level-option"><input type="checkbox" data-level="redux" ${levels.redux ? 'checked' : ''} /><span class="lvl-dot" style="background:var(--green)"></span>Redux Actions</label>
|
|
328
353
|
</div>
|
|
329
354
|
</div>
|
|
330
355
|
</div>
|
|
@@ -335,6 +360,13 @@ function initConsolePanel() {
|
|
|
335
360
|
<div class="label">No logs yet</div>
|
|
336
361
|
<div class="hint">Add RNDebugSDK.js to your app</div>
|
|
337
362
|
</div>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="console-find-bar" id="consoleFindBar" style="display:none">
|
|
365
|
+
<input id="consoleFindInput" class="console-find-input" placeholder="Find in logs... (Cmd+F)" />
|
|
366
|
+
<span id="consoleFindCount" class="console-find-count"></span>
|
|
367
|
+
<button class="console-find-btn" id="consoleFindPrev" title="Previous">▲</button>
|
|
368
|
+
<button class="console-find-btn" id="consoleFindNext" title="Next">▼</button>
|
|
369
|
+
<button class="console-find-btn" id="consoleFindClose" title="Close (Esc)">✕</button>
|
|
338
370
|
</div>`;
|
|
339
371
|
|
|
340
372
|
// Search filter
|
|
@@ -362,6 +394,7 @@ function initConsolePanel() {
|
|
|
362
394
|
const level = checkbox.dataset.level;
|
|
363
395
|
if (level) {
|
|
364
396
|
state.console.levelFilters[level] = checkbox.checked;
|
|
397
|
+
if (level === 'redux') state.console.showRedux = checkbox.checked;
|
|
365
398
|
setStoredLogLevels(state.console.levelFilters);
|
|
366
399
|
updateLevelBtnText();
|
|
367
400
|
renderConsole();
|
|
@@ -376,20 +409,67 @@ function initConsolePanel() {
|
|
|
376
409
|
$('cBadge').textContent = '0';
|
|
377
410
|
renderConsole();
|
|
378
411
|
});
|
|
412
|
+
|
|
413
|
+
// Find bar (Cmd+F)
|
|
414
|
+
let _findMatches = [];
|
|
415
|
+
let _findIdx = -1;
|
|
416
|
+
|
|
417
|
+
function doFind(term) {
|
|
418
|
+
// Clear previous highlights
|
|
419
|
+
document.querySelectorAll('.console-find-highlight').forEach(el => {
|
|
420
|
+
el.replaceWith(el.textContent);
|
|
421
|
+
});
|
|
422
|
+
_findMatches = [];
|
|
423
|
+
_findIdx = -1;
|
|
424
|
+
if (!term) { $('consoleFindCount').textContent = ''; return; }
|
|
425
|
+
|
|
426
|
+
const rows = document.querySelectorAll('#consoleList .log-row');
|
|
427
|
+
rows.forEach(row => {
|
|
428
|
+
const text = row.textContent.toLowerCase();
|
|
429
|
+
if (text.includes(term.toLowerCase())) _findMatches.push(row);
|
|
430
|
+
});
|
|
431
|
+
$('consoleFindCount').textContent = _findMatches.length ? `${_findMatches.length} found` : 'No matches';
|
|
432
|
+
if (_findMatches.length) { _findIdx = 0; _findMatches[0].scrollIntoView({ block: 'nearest' }); _findMatches[0].style.outline = '1px solid var(--accent)'; }
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function findNav(dir) {
|
|
436
|
+
if (!_findMatches.length) return;
|
|
437
|
+
if (_findMatches[_findIdx]) _findMatches[_findIdx].style.outline = '';
|
|
438
|
+
_findIdx = (_findIdx + dir + _findMatches.length) % _findMatches.length;
|
|
439
|
+
_findMatches[_findIdx].scrollIntoView({ block: 'nearest' });
|
|
440
|
+
_findMatches[_findIdx].style.outline = '1px solid var(--accent)';
|
|
441
|
+
$('consoleFindCount').textContent = `${_findIdx + 1}/${_findMatches.length}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
$('consoleFindInput').addEventListener('input', (e) => doFind(e.target.value));
|
|
445
|
+
$('consoleFindPrev').addEventListener('click', () => findNav(-1));
|
|
446
|
+
$('consoleFindNext').addEventListener('click', () => findNav(1));
|
|
447
|
+
$('consoleFindClose').addEventListener('click', () => {
|
|
448
|
+
$('consoleFindBar').style.display = 'none';
|
|
449
|
+
if (_findMatches[_findIdx]) _findMatches[_findIdx].style.outline = '';
|
|
450
|
+
_findMatches = []; _findIdx = -1;
|
|
451
|
+
$('consoleFindInput').value = '';
|
|
452
|
+
$('consoleFindCount').textContent = '';
|
|
453
|
+
});
|
|
454
|
+
$('consoleFindInput').addEventListener('keydown', (e) => {
|
|
455
|
+
if (e.key === 'Escape') $('consoleFindClose').click();
|
|
456
|
+
if (e.key === 'Enter') findNav(e.shiftKey ? -1 : 1);
|
|
457
|
+
});
|
|
379
458
|
}
|
|
380
459
|
|
|
381
460
|
function updateLevelBtnText() {
|
|
382
461
|
const levels = state.console.levelFilters;
|
|
383
|
-
const
|
|
384
|
-
const
|
|
462
|
+
const logLevels = { log: levels.log, info: levels.info, warn: levels.warn, error: levels.error, debug: levels.debug };
|
|
463
|
+
const allOn = Object.values(logLevels).every(v => v);
|
|
464
|
+
const allOff = Object.values(logLevels).every(v => !v);
|
|
385
465
|
const btn = $('consoleLevelBtn');
|
|
386
466
|
if (!btn) return;
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
else
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
467
|
+
let text = '';
|
|
468
|
+
if (allOn) text = 'All Levels';
|
|
469
|
+
else if (allOff) text = 'None';
|
|
470
|
+
else text = Object.entries(logLevels).filter(([, v]) => v).map(([k]) => k.charAt(0).toUpperCase() + k.slice(1)).join(', ');
|
|
471
|
+
if (levels.redux) text += (text ? ' + ' : '') + 'Redux';
|
|
472
|
+
btn.textContent = text + ' ▾';
|
|
393
473
|
}
|
|
394
474
|
|
|
395
475
|
// Console is fed via IPC (network-event handled in IPC section above)
|
|
@@ -873,8 +953,8 @@ function initNetworkPanel() {
|
|
|
873
953
|
<div class="net-filter-bar" id="netFilterBar">
|
|
874
954
|
<input id="netSearchInput" class="net-search-input" placeholder="Filter URLs..." />
|
|
875
955
|
<div class="net-type-filters" id="netTypeFilters">
|
|
876
|
-
<button class="net-type-btn
|
|
877
|
-
<button class="net-type-btn" data-type="fetch">Fetch/XHR</button>
|
|
956
|
+
<button class="net-type-btn" data-type="all">All</button>
|
|
957
|
+
<button class="net-type-btn active" data-type="fetch">Fetch/XHR</button>
|
|
878
958
|
<button class="net-type-btn" data-type="js">JS</button>
|
|
879
959
|
<button class="net-type-btn" data-type="css">CSS</button>
|
|
880
960
|
<button class="net-type-btn" data-type="img">Img</button>
|
|
@@ -1726,6 +1806,7 @@ function initReduxPanel() {
|
|
|
1726
1806
|
<input id="reduxSearch" class="net-search-input" style="margin-left:12px" placeholder="Filter actions..." />
|
|
1727
1807
|
<div class="ml-auto" style="display:flex;align-items:center;gap:8px">
|
|
1728
1808
|
<button class="panel-clear-btn" id="reduxClear" title="Clear redux">Clear</button>
|
|
1809
|
+
<button class="panel-clear-btn" id="reduxSort" title="Toggle sort order">Time ▲</button>
|
|
1729
1810
|
<div class="time-travel-bar" style="border:none;padding:0;margin:0">
|
|
1730
1811
|
<button class="tt-btn" onclick="reduxJumpTo(state.redux.selected-1)">◀</button>
|
|
1731
1812
|
<span class="tt-label" id="ttLabel">—/—</span>
|
|
@@ -1753,6 +1834,12 @@ function initReduxPanel() {
|
|
|
1753
1834
|
$('rBadge').textContent = '0';
|
|
1754
1835
|
renderRedux();
|
|
1755
1836
|
});
|
|
1837
|
+
|
|
1838
|
+
$('reduxSort').addEventListener('click', () => {
|
|
1839
|
+
state.redux.sortDir = state.redux.sortDir === 'desc' ? 'asc' : 'desc';
|
|
1840
|
+
$('reduxSort').textContent = state.redux.sortDir === 'desc' ? 'Time \u25BC' : 'Time \u25B2';
|
|
1841
|
+
renderRedux();
|
|
1842
|
+
});
|
|
1756
1843
|
}
|
|
1757
1844
|
|
|
1758
1845
|
window.reduxJumpTo = idx => {
|
|
@@ -1786,11 +1873,24 @@ function handleReduxEvent(event) {
|
|
|
1786
1873
|
allKeys.forEach(k => { if (!_deepEqual(prevState[k], nextState[k])) changedKeys.push(k); });
|
|
1787
1874
|
}
|
|
1788
1875
|
|
|
1789
|
-
|
|
1876
|
+
const actionEntry = { type: action?.type || '?', payload: action, ts: event.ts, index: idx, changedKeys };
|
|
1877
|
+
state.redux.actions.push(actionEntry);
|
|
1790
1878
|
state.redux.states.push(nextState);
|
|
1791
1879
|
state.redux.selected = idx;
|
|
1792
1880
|
$('rBadge').textContent = state.redux.actions.length;
|
|
1793
1881
|
renderRedux();
|
|
1882
|
+
|
|
1883
|
+
// Also add to console logs if Redux is enabled in console dropdown
|
|
1884
|
+
if (state.console.showRedux) {
|
|
1885
|
+
const msg = `[Redux] ${actionEntry.type}` + (changedKeys.length ? ` (changed: ${changedKeys.join(', ')})` : '');
|
|
1886
|
+
addConsoleLog({
|
|
1887
|
+
level: 'redux',
|
|
1888
|
+
message: msg,
|
|
1889
|
+
args: [{ t: 'string', v: `[Redux] ${actionEntry.type}` }, { t: 'object', v: action }],
|
|
1890
|
+
ts: event.ts,
|
|
1891
|
+
_isRedux: true,
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1794
1894
|
}
|
|
1795
1895
|
|
|
1796
1896
|
function renderRedux() {
|
|
@@ -1798,8 +1898,9 @@ function renderRedux() {
|
|
|
1798
1898
|
const empty = $('reduxEmpty');
|
|
1799
1899
|
if (!content) return;
|
|
1800
1900
|
|
|
1801
|
-
const { actions, states, selected, searchFilter } = state.redux;
|
|
1802
|
-
|
|
1901
|
+
const { actions, states, selected, searchFilter, sortDir } = state.redux;
|
|
1902
|
+
let visible = searchFilter ? actions.filter(a => a.type.toLowerCase().includes(searchFilter)) : [...actions];
|
|
1903
|
+
if (sortDir === 'desc') visible = [...visible].reverse();
|
|
1803
1904
|
|
|
1804
1905
|
empty.style.display = visible.length ? 'none' : 'flex';
|
|
1805
1906
|
content.querySelectorAll('.rdx-entry').forEach(e => e.remove());
|
|
@@ -1893,8 +1994,13 @@ function renderRedux() {
|
|
|
1893
1994
|
});
|
|
1894
1995
|
|
|
1895
1996
|
content.appendChild(frag);
|
|
1896
|
-
|
|
1897
|
-
if (
|
|
1997
|
+
// Auto-scroll: if asc (latest at bottom), scroll to bottom; otherwise scroll selected into view
|
|
1998
|
+
if (state.redux.sortDir === 'asc') {
|
|
1999
|
+
content.scrollTop = content.scrollHeight;
|
|
2000
|
+
} else {
|
|
2001
|
+
const selEl = content.querySelector('.rdx-entry.selected');
|
|
2002
|
+
if (selEl) selEl.scrollIntoView({ block: 'nearest' });
|
|
2003
|
+
}
|
|
1898
2004
|
}
|
|
1899
2005
|
|
|
1900
2006
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ReactoRadar Icon Generator
|
|
4
|
+
Generates all macOS iconset sizes from icon.svg, then compiles icon.icns.
|
|
5
|
+
|
|
6
|
+
Requirements:
|
|
7
|
+
pip install cairosvg pillow
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python3 generate_icons.py
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import struct
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import zlib
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Config
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
SCRIPT_DIR = Path(__file__).parent.resolve()
|
|
25
|
+
SVG_SRC = SCRIPT_DIR / "icon.svg"
|
|
26
|
+
ICONSET = SCRIPT_DIR / "icon.iconset"
|
|
27
|
+
ICNS_OUT = SCRIPT_DIR / "icon.icns"
|
|
28
|
+
PNG_OUT = SCRIPT_DIR / "icon.png" # 1024×1024, for Electron dev mode
|
|
29
|
+
|
|
30
|
+
SIZES = [
|
|
31
|
+
# (filename, pixels)
|
|
32
|
+
("icon_16x16.png", 16),
|
|
33
|
+
("icon_16x16@2x.png", 32),
|
|
34
|
+
("icon_32x32.png", 32),
|
|
35
|
+
("icon_32x32@2x.png", 64),
|
|
36
|
+
("icon_128x128.png", 128),
|
|
37
|
+
("icon_128x128@2x.png", 256),
|
|
38
|
+
("icon_256x256.png", 256),
|
|
39
|
+
("icon_256x256@2x.png", 512),
|
|
40
|
+
("icon_512x512.png", 512),
|
|
41
|
+
("icon_512x512@2x.png", 1024),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Helpers
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def render_svg_to_png(svg_path: Path, out_path: Path, size: int) -> None:
|
|
49
|
+
"""Render SVG → PNG at the given square pixel size."""
|
|
50
|
+
try:
|
|
51
|
+
import cairosvg
|
|
52
|
+
cairosvg.svg2png(
|
|
53
|
+
url=str(svg_path),
|
|
54
|
+
write_to=str(out_path),
|
|
55
|
+
output_width=size,
|
|
56
|
+
output_height=size,
|
|
57
|
+
)
|
|
58
|
+
except ImportError:
|
|
59
|
+
# Fallback: Inkscape CLI
|
|
60
|
+
result = subprocess.run(
|
|
61
|
+
["inkscape", "--export-type=png",
|
|
62
|
+
f"--export-filename={out_path}",
|
|
63
|
+
f"--export-width={size}",
|
|
64
|
+
f"--export-height={size}",
|
|
65
|
+
str(svg_path)],
|
|
66
|
+
capture_output=True, text=True
|
|
67
|
+
)
|
|
68
|
+
if result.returncode != 0:
|
|
69
|
+
print(f" inkscape error: {result.stderr}", file=sys.stderr)
|
|
70
|
+
raise RuntimeError("SVG rendering failed")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_icns_with_iconutil(iconset_dir: Path, icns_path: Path) -> bool:
|
|
74
|
+
"""Use macOS iconutil if available."""
|
|
75
|
+
if shutil.which("iconutil"):
|
|
76
|
+
result = subprocess.run(
|
|
77
|
+
["iconutil", "-c", "icns", str(iconset_dir), "-o", str(icns_path)],
|
|
78
|
+
capture_output=True, text=True
|
|
79
|
+
)
|
|
80
|
+
if result.returncode == 0:
|
|
81
|
+
return True
|
|
82
|
+
print(f" iconutil error: {result.stderr}", file=sys.stderr)
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _png_bytes(path: Path) -> bytes:
|
|
87
|
+
return path.read_bytes()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _icns_chunk(tag: bytes, data: bytes) -> bytes:
|
|
91
|
+
"""Pack one ICNS chunk: 4-byte OSType + 4-byte length (includes header) + data."""
|
|
92
|
+
length = 8 + len(data)
|
|
93
|
+
return tag + struct.pack(">I", length) + data
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ICNS OSType tags for each size
|
|
97
|
+
_ICNS_TAGS = {
|
|
98
|
+
16: b"icp4",
|
|
99
|
+
32: b"icp5",
|
|
100
|
+
64: b"icp6",
|
|
101
|
+
128: b"ic07",
|
|
102
|
+
256: b"ic08",
|
|
103
|
+
512: b"ic09",
|
|
104
|
+
1024: b"ic10",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def build_icns_pure_python(iconset_dir: Path, icns_path: Path) -> None:
|
|
109
|
+
"""Pure-Python ICNS writer (fallback when iconutil unavailable)."""
|
|
110
|
+
chunks = b""
|
|
111
|
+
# Deduplicate: use only the @2x names (they have the higher-res pixels)
|
|
112
|
+
# Actually we want one entry per unique pixel size, highest quality first.
|
|
113
|
+
seen = {}
|
|
114
|
+
for fname, size in SIZES:
|
|
115
|
+
if size not in seen:
|
|
116
|
+
seen[size] = iconset_dir / fname
|
|
117
|
+
|
|
118
|
+
for size in sorted(seen):
|
|
119
|
+
tag = _ICNS_TAGS.get(size)
|
|
120
|
+
if tag is None:
|
|
121
|
+
continue
|
|
122
|
+
png_path = seen[size]
|
|
123
|
+
if not png_path.exists():
|
|
124
|
+
print(f" warning: {png_path.name} missing, skipping")
|
|
125
|
+
continue
|
|
126
|
+
chunks += _icns_chunk(tag, _png_bytes(png_path))
|
|
127
|
+
|
|
128
|
+
total = 8 + len(chunks)
|
|
129
|
+
icns_path.write_bytes(b"icns" + struct.pack(">I", total) + chunks)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# Main
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
def main():
|
|
137
|
+
print("=== ReactoRadar Icon Generator ===\n")
|
|
138
|
+
|
|
139
|
+
if not SVG_SRC.exists():
|
|
140
|
+
print(f"ERROR: {SVG_SRC} not found.", file=sys.stderr)
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
# 1. Create iconset directory
|
|
144
|
+
ICONSET.mkdir(exist_ok=True)
|
|
145
|
+
print(f"Output directory: {ICONSET}\n")
|
|
146
|
+
|
|
147
|
+
# 2. Render each size
|
|
148
|
+
for fname, size in SIZES:
|
|
149
|
+
out = ICONSET / fname
|
|
150
|
+
print(f" Rendering {fname:30s} ({size:4d}px) ...", end=" ", flush=True)
|
|
151
|
+
render_svg_to_png(SVG_SRC, out, size)
|
|
152
|
+
kb = out.stat().st_size // 1024
|
|
153
|
+
print(f"done ({kb} KB)")
|
|
154
|
+
|
|
155
|
+
# 3. Copy 1024×1024 as icon.png (Electron dev-mode asset)
|
|
156
|
+
shutil.copy2(ICONSET / "icon_512x512@2x.png", PNG_OUT)
|
|
157
|
+
print(f"\nCopied icon.png ({PNG_OUT.stat().st_size // 1024} KB)")
|
|
158
|
+
|
|
159
|
+
# 4. Build ICNS
|
|
160
|
+
print(f"\nBuilding {ICNS_OUT.name} ...")
|
|
161
|
+
if not build_icns_with_iconutil(ICONSET, ICNS_OUT):
|
|
162
|
+
print(" iconutil not available – using pure-Python writer")
|
|
163
|
+
build_icns_pure_python(ICONSET, ICNS_OUT)
|
|
164
|
+
print(f" Written: {ICNS_OUT} ({ICNS_OUT.stat().st_size // 1024} KB)")
|
|
165
|
+
|
|
166
|
+
print("\n✅ All done!\n")
|
|
167
|
+
print("Files generated:")
|
|
168
|
+
print(f" {ICONSET}/ ← all PNG sizes")
|
|
169
|
+
print(f" {ICNS_OUT} ← for Electron builder (assets/icon.icns)")
|
|
170
|
+
print(f" {PNG_OUT} ← for Electron dev mode (assets/icon.png)")
|
|
171
|
+
print(f" {SVG_SRC} ← master source")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
main()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/main.js
CHANGED
|
@@ -38,10 +38,10 @@ app.whenReady().then(async () => {
|
|
|
38
38
|
|
|
39
39
|
await createMainWindow();
|
|
40
40
|
|
|
41
|
-
// Send version to renderer
|
|
41
|
+
// Send version to renderer (delay to ensure IPC listeners are registered)
|
|
42
42
|
const appVersion = require('./package.json').version;
|
|
43
43
|
mainWindow?.webContents.on('did-finish-load', () => {
|
|
44
|
-
mainWindow?.webContents.send('app-version', appVersion);
|
|
44
|
+
setTimeout(() => mainWindow?.webContents.send('app-version', appVersion), 500);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// Check for updates (non-blocking)
|
|
@@ -505,6 +505,12 @@ function buildMenu() {
|
|
|
505
505
|
{ role: 'copy' },
|
|
506
506
|
{ role: 'paste' },
|
|
507
507
|
{ role: 'selectAll' },
|
|
508
|
+
{ type: 'separator' },
|
|
509
|
+
{
|
|
510
|
+
label: 'Find',
|
|
511
|
+
accelerator: 'Cmd+F',
|
|
512
|
+
click: () => { mainWindow?.webContents.send('focus-search'); },
|
|
513
|
+
},
|
|
508
514
|
],
|
|
509
515
|
},
|
|
510
516
|
{
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactoradar",
|
|
3
3
|
"productName": "ReactoRadar",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.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": {
|
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',
|
|
13
|
+
'react-dt-status', 'trigger-open-cdp', 'clear-all-ui', 'theme-changed', 'update-available', 'app-version', 'focus-search',
|
|
14
14
|
];
|
|
15
15
|
if (allowed.includes(channel)) {
|
|
16
16
|
ipcRenderer.removeAllListeners(channel);
|
package/styles.css
CHANGED
|
@@ -369,6 +369,41 @@ body {
|
|
|
369
369
|
.tab:hover:not(.active) { color: var(--text); }
|
|
370
370
|
.ml-auto { margin-left: auto; }
|
|
371
371
|
|
|
372
|
+
/* Console find bar (Cmd+F) */
|
|
373
|
+
.console-find-bar {
|
|
374
|
+
display: flex;
|
|
375
|
+
align-items: center;
|
|
376
|
+
gap: 6px;
|
|
377
|
+
padding: 4px 10px;
|
|
378
|
+
background: var(--bg2);
|
|
379
|
+
border-top: 1px solid var(--border);
|
|
380
|
+
flex-shrink: 0;
|
|
381
|
+
}
|
|
382
|
+
.console-find-input {
|
|
383
|
+
flex: 1;
|
|
384
|
+
padding: 3px 8px;
|
|
385
|
+
border: 1px solid var(--border2);
|
|
386
|
+
border-radius: 4px;
|
|
387
|
+
background: var(--bg3);
|
|
388
|
+
color: var(--text);
|
|
389
|
+
font-family: inherit;
|
|
390
|
+
font-size: 11px;
|
|
391
|
+
outline: none;
|
|
392
|
+
}
|
|
393
|
+
.console-find-input:focus { border-color: var(--accent); }
|
|
394
|
+
.console-find-count { font-size: 10px; color: var(--text-dim); flex-shrink: 0; min-width: 40px; }
|
|
395
|
+
.console-find-btn {
|
|
396
|
+
border: none;
|
|
397
|
+
background: transparent;
|
|
398
|
+
color: var(--text-dim);
|
|
399
|
+
font-size: 11px;
|
|
400
|
+
cursor: pointer;
|
|
401
|
+
padding: 2px 6px;
|
|
402
|
+
border-radius: 3px;
|
|
403
|
+
}
|
|
404
|
+
.console-find-btn:hover { background: var(--bg3); color: var(--text); }
|
|
405
|
+
.console-find-highlight { background: rgba(245,200,66,.3); border-radius: 2px; }
|
|
406
|
+
|
|
372
407
|
/* Panel clear button (used in Console, Network, GA4, Redux) */
|
|
373
408
|
.panel-clear-btn {
|
|
374
409
|
padding: 3px 10px;
|
|
@@ -490,6 +525,7 @@ body {
|
|
|
490
525
|
.lvl-warn { background: rgba(245,200,66,.12); color: var(--yellow); }
|
|
491
526
|
.lvl-error { background: rgba(255,94,114,.15); color: var(--red); }
|
|
492
527
|
.lvl-debug { background: rgba(155,127,255,.12); color: var(--accent2); }
|
|
528
|
+
.lvl-redux { background: rgba(61,214,140,.12); color: var(--green); }
|
|
493
529
|
|
|
494
530
|
.log-body-wrap {
|
|
495
531
|
display: flex;
|