预览修改
This commit is contained in:
1 parent
ef23e33ad0
commit
90e5a2b81e
4 files changed
+268
-6
No files matched your search
@@ -14,6 +14,13 @@
|
||||
--accent: #4fc1ff;
|
||||
--border: #3c3c3c;
|
||||
--error: #f14c4c;
|
||||
/* 悬浮层填充与滚动条配色。Eagle 内置 Electron 22 / Chromium 108 不支持 color-mix(),
|
||||
这些必须写成 rgba(),否则整条颜色声明会被丢弃。 */
|
||||
--hover-fill: rgba(255, 255, 255, .07);
|
||||
--scroll-thumb: rgba(255, 255, 255, .34);
|
||||
--scroll-thumb-hover: rgba(255, 255, 255, .55);
|
||||
--scroll-thumb-active: rgba(255, 255, 255, .72);
|
||||
--scroll-track: rgba(255, 255, 255, .07);
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--bg: #f3f4f6;
|
||||
@@ -24,6 +31,11 @@
|
||||
--accent: #0078d4;
|
||||
--border: #ddd;
|
||||
--error: #d13438;
|
||||
--hover-fill: rgba(0, 0, 0, .06);
|
||||
--scroll-thumb: rgba(0, 0, 0, .30);
|
||||
--scroll-thumb-hover: rgba(0, 0, 0, .46);
|
||||
--scroll-thumb-active: rgba(0, 0, 0, .62);
|
||||
--scroll-track: rgba(0, 0, 0, .06);
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -182,6 +194,50 @@
|
||||
font: 12px/1.6 "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
color: var(--text-dim); white-space: pre; word-break: normal;
|
||||
}
|
||||
/* 纯文本 / 普通 JSON 预览允许拖动选择与 Ctrl+C 复制;画布与查看器控件仍保持不可选中 */
|
||||
#overlay.text-preview .title,
|
||||
#overlay.text-preview .msg {
|
||||
-webkit-user-select: text; user-select: text;
|
||||
}
|
||||
#overlay.text-preview .msg { cursor: text; }
|
||||
/* 预览区滚动条:不使用原生样式。宽度 14px、滑块可见 8px,保证看得清。
|
||||
颜色走 --scroll-* 变量(rgba 写死),不能用 color-mix():Eagle 内置 Chromium 108
|
||||
不支持该函数,声明会整条失效,滑块就变成看不见。也不要写 scrollbar-width /
|
||||
scrollbar-color,那两个标准属性在 108 上无效且易与 ::-webkit-scrollbar 混淆。 */
|
||||
#overlay.text-preview .box::-webkit-scrollbar { width: 14px; height: 14px; }
|
||||
#overlay.text-preview .box::-webkit-scrollbar-track { background: var(--scroll-track); }
|
||||
#overlay.text-preview .box::-webkit-scrollbar-thumb {
|
||||
min-height: 36px;
|
||||
border: 3px solid transparent; border-radius: 999px;
|
||||
background: var(--scroll-thumb);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
#overlay.text-preview .box::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scroll-thumb-hover);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
#overlay.text-preview .box::-webkit-scrollbar-thumb:active {
|
||||
background: var(--scroll-thumb-active);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
#overlay.text-preview .box::-webkit-scrollbar-corner { background: transparent; }
|
||||
/* JSON 字段树:折叠标记与计数只用符号 / 数字,无需新增界面文案 */
|
||||
#overlay.json-preview .msg { white-space: normal; }
|
||||
.jt-node > summary {
|
||||
list-style: none; cursor: pointer; border-radius: 4px;
|
||||
}
|
||||
.jt-node > summary::-webkit-details-marker { display: none; }
|
||||
.jt-node > summary:hover { background: var(--hover-fill); }
|
||||
.jt-node > summary::before {
|
||||
content: '▾'; display: inline-block; width: 14px; color: var(--text-dim);
|
||||
}
|
||||
.jt-node:not([open]) > summary::before { content: '▸'; }
|
||||
.jt-children { margin-left: 7px; padding-left: 11px; border-left: 1px solid var(--border); }
|
||||
.jt-leaf { padding-left: 14px; }
|
||||
.jt-key { color: var(--accent); }
|
||||
.jt-str { color: var(--text); }
|
||||
.jt-brace, .jt-count { color: var(--text-dim); }
|
||||
.jt-count { margin: 0 3px; font-size: 11px; }
|
||||
#overlay.hidden { display: none; }
|
||||
#loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 4; background: var(--bg); }
|
||||
#loading.hidden { display: none; }
|
||||
@@ -421,6 +477,8 @@
|
||||
|
||||
function showError(title, msg) {
|
||||
loadingEl.classList.add('hidden');
|
||||
overlay.classList.remove('text-preview');
|
||||
overlay.classList.remove('json-preview');
|
||||
overlay.classList.remove('hidden');
|
||||
document.getElementById('errorTitle').textContent = title;
|
||||
document.getElementById('errorMsg').textContent = msg;
|
||||
@@ -432,8 +490,78 @@
|
||||
timelineBarEl.style.display = 'none';
|
||||
animationPanel.style.display = 'none';
|
||||
statusEl.style.display = 'none';
|
||||
overlay.classList.add('text-preview');
|
||||
showError(title, text);
|
||||
overlay.classList.add('text-preview');
|
||||
}
|
||||
|
||||
// ---------- 普通 JSON:轻量字段树(点击折叠 / 展开) ----------
|
||||
const JSON_TREE_MAX_NODES = 20000; // 超过此规模退回纯文本,避免构建 DOM 卡顿
|
||||
const JSON_TREE_OPEN_DEPTH = 1; // 默认展开的层级,更深的折叠
|
||||
|
||||
function jsonNodeCount(value, budget) {
|
||||
let n = 0;
|
||||
const walk = (node) => {
|
||||
if (!node || typeof node !== 'object' || n > budget) return;
|
||||
for (const key of Object.keys(node)) { n++; walk(node[key]); if (n > budget) return; }
|
||||
};
|
||||
walk(value);
|
||||
return n;
|
||||
}
|
||||
|
||||
function jsonKeySpan(key) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'jt-key';
|
||||
span.textContent = (typeof key === 'number' ? '[' + key + ']: ' : JSON.stringify(key) + ': ');
|
||||
return span;
|
||||
}
|
||||
|
||||
function buildJsonTree(key, value, depth) {
|
||||
if (value === null || typeof value !== 'object') {
|
||||
const leaf = document.createElement('div');
|
||||
leaf.className = 'jt-leaf';
|
||||
if (key !== null) leaf.appendChild(jsonKeySpan(key));
|
||||
const val = document.createElement('span');
|
||||
val.textContent = typeof value === 'string' ? JSON.stringify(value) : String(value);
|
||||
if (typeof value === 'string') val.className = 'jt-str';
|
||||
leaf.appendChild(val);
|
||||
return leaf;
|
||||
}
|
||||
const isArray = Array.isArray(value);
|
||||
const keys = isArray ? value.map((_, i) => i) : Object.keys(value);
|
||||
const node = document.createElement('details');
|
||||
node.className = 'jt-node';
|
||||
if (depth <= JSON_TREE_OPEN_DEPTH) node.open = true;
|
||||
const summary = document.createElement('summary');
|
||||
if (key !== null) summary.appendChild(jsonKeySpan(key));
|
||||
const open = document.createElement('span');
|
||||
open.className = 'jt-brace';
|
||||
open.textContent = isArray ? '[' : '{';
|
||||
const count = document.createElement('span');
|
||||
count.className = 'jt-count';
|
||||
count.textContent = String(keys.length);
|
||||
const close = document.createElement('span');
|
||||
close.className = 'jt-brace';
|
||||
close.textContent = isArray ? ']' : '}';
|
||||
summary.append(open, count, close);
|
||||
node.appendChild(summary);
|
||||
const children = document.createElement('div');
|
||||
children.className = 'jt-children';
|
||||
for (const k of keys) children.appendChild(buildJsonTree(k, value[k], depth + 1));
|
||||
node.appendChild(children);
|
||||
return node;
|
||||
}
|
||||
|
||||
function showJsonPreview(title, value) {
|
||||
const box = document.getElementById('errorMsg');
|
||||
showTextPreview(title, '');
|
||||
if (jsonNodeCount(value, JSON_TREE_MAX_NODES) > JSON_TREE_MAX_NODES) {
|
||||
// 超大对象仍以纯文本呈现,保证打开速度与可选择性
|
||||
box.textContent = JSON.stringify(value, null, 2);
|
||||
return;
|
||||
}
|
||||
overlay.classList.add('json-preview');
|
||||
box.textContent = '';
|
||||
box.appendChild(buildJsonTree(null, value, 0));
|
||||
}
|
||||
|
||||
function setStatus(msg, holdMs = 0) {
|
||||
@@ -1063,10 +1191,13 @@
|
||||
let count = 0;
|
||||
try { count = fs.readSync(fd, buffer, 0, buffer.length, 0); } finally { fs.closeSync(fd); }
|
||||
let text = buffer.subarray(0, count).toString('utf8');
|
||||
const name = path.basename(filePath);
|
||||
let parsed;
|
||||
if (stat.size <= maxPreviewBytes) {
|
||||
try { text = JSON.stringify(JSON.parse(text), null, 2); } catch (e) { /* 保留 JSONC/原始文本 */ }
|
||||
try { parsed = JSON.parse(text); } catch (e) { /* 保留 JSONC/原始文本 */ }
|
||||
} else text += t('viewer.truncated1m');
|
||||
showTextPreview(path.basename(filePath), text);
|
||||
if (parsed !== null && typeof parsed === 'object') showJsonPreview(name, parsed);
|
||||
else showTextPreview(name, parsed === undefined ? text : JSON.stringify(parsed, null, 2));
|
||||
return;
|
||||
}
|
||||
const assets = utils.discoverAssets(filePath);
|
||||
|
||||
Reference in new issue
Block a user