From 47a0fe815041651b45c8e194bfc93185f3017b2e Mon Sep 17 00:00:00 2001 From: Youzini-afk <13153778771cx@gmail.com> Date: Fri, 10 Apr 2026 23:47:39 +0800 Subject: [PATCH] Restore mobile panel UI refinements --- style.css | 327 +++++++++++++++++++++++++------------- ui/graph-renderer.js | 56 +++++-- ui/panel.html | 118 ++++++++++++-- ui/panel.js | 363 +++++++++++++++++++++++-------------------- 4 files changed, 560 insertions(+), 304 deletions(-) diff --git a/style.css b/style.css index dfd93e3..77b999e 100644 --- a/style.css +++ b/style.css @@ -704,6 +704,12 @@ color: var(--bme-primary); } +#bme-graph-canvas, +#bme-mobile-graph-canvas, +#bme-fullscreen-graph-canvas { + touch-action: none; +} + #bme-graph-canvas { flex: 1; width: 100%; @@ -3691,26 +3697,84 @@ margin-bottom: 2px; } -/* --- Node Detail Panel (sidebar overlay) --- */ -.bme-node-detail { +/* --- Node detail:桌面右侧抽屉 / 移动端图谱内居中悬浮 + 遮罩 --- */ +.bme-node-detail-scrim { + position: absolute; + inset: 0; + z-index: 9; + margin: 0; + padding: 0; + border: none; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(3px); + -webkit-backdrop-filter: blur(3px); + cursor: pointer; +} + +.bme-node-detail-scrim[hidden] { + display: none !important; +} + +/* 桌面图谱工作区(主画布):节点详情仍为右侧滑出面板 */ +#bme-node-detail { position: absolute; top: 0; right: 0; + left: auto; width: 280px; height: 100%; + max-height: none; background: var(--bme-surface-container); border-left: 1px solid var(--bme-border); + border-radius: 0; padding: 12px; overflow-y: auto; + box-shadow: none; transform: translateX(100%); + opacity: 1; + visibility: visible; + pointer-events: auto; transition: transform 0.2s ease; z-index: 10; } -.bme-node-detail.open { +#bme-node-detail.open { transform: translateX(0); } +/* 移动端「图谱」Tab:节点详情居中悬浮窗 */ +#bme-mobile-node-detail { + position: absolute; + left: 50%; + top: 50%; + right: auto; + width: min(400px, calc(100% - 24px)); + max-height: min(78vh, 640px); + height: auto; + background: var(--bme-surface-container); + border: 1px solid var(--bme-border); + border-radius: 14px; + padding: 12px; + overflow-y: auto; + box-shadow: 0 20px 48px rgba(0, 0, 0, 0.5); + transform: translate(-50%, -48%) scale(0.96); + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: + opacity 0.22s ease, + transform 0.22s ease, + visibility 0.22s; + z-index: 10; +} + +#bme-mobile-node-detail.open { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + visibility: visible; + pointer-events: auto; +} + .bme-node-detail-header { display: flex; align-items: center; @@ -3848,13 +3912,149 @@ border-radius: 2px; } -/* 移动端图谱预览 - 桌面端默认隐藏 */ +/* 移动端图谱预览 - 桌面端默认隐藏(旧组件已移除) */ .bme-mobile-graph-preview, -.bme-mobile-graph-status, -.bme-mobile-graph-section { +.bme-mobile-graph-status { display: none; } +/* --- Graph Sub-Tabs (Mobile Graph Pane) --- */ + +.bme-graph-subtabs { + display: flex; + gap: 4px; + padding: 10px 12px 6px; + background: var(--bme-surface-container); + border-bottom: 1px solid var(--bme-border); + flex-shrink: 0; +} + +.bme-graph-subtab { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 7px 10px; + border: none; + border-radius: 20px; + background: rgba(255, 255, 255, 0.04); + color: var(--bme-on-surface-dim); + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; +} + +.bme-graph-subtab i { + font-size: 12px; +} + +.bme-graph-subtab:hover { + background: rgba(255, 255, 255, 0.08); + color: var(--bme-on-surface); +} + +.bme-graph-subtab.active { + background: var(--bme-primary); + color: #fff; + box-shadow: 0 2px 8px rgba(233, 69, 96, 0.35); +} + +.bme-graph-view-content { + flex: 1; + min-height: 0; + overflow: hidden; + position: relative; +} + +.bme-mobile-graph-pane { + display: none; + width: 100%; + height: 100%; + flex-direction: column; + overflow: auto; +} + +.bme-mobile-graph-pane.active { + display: flex; +} + +.bme-mobile-canvas-wrap { + flex: 1; + position: relative; + min-height: 0; +} + +.bme-mobile-canvas-wrap canvas { + display: block; + width: 100%; + height: 100%; +} + +.bme-mobile-graph-float-controls { + position: absolute; + top: 10px; + right: 10px; + display: flex; + flex-direction: column; + gap: 6px; + z-index: 5; +} + +.bme-mobile-graph-float-controls button { + width: 36px; + height: 36px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(10, 10, 15, 0.65); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + color: var(--bme-on-surface); + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.15s ease; +} + +.bme-mobile-graph-float-controls button:hover { + background: rgba(233, 69, 96, 0.25); + border-color: var(--bme-primary); + color: var(--bme-primary); +} + +/* Graph pane layout */ +#bme-pane-graph { + display: none; + flex-direction: column; + height: 100%; + overflow: hidden; + position: relative; +} + +#bme-pane-graph.active { + display: flex; +} + +#bme-pane-graph .bme-graph-legend { + flex-shrink: 0; + padding: 6px 12px; +} + +#bme-pane-graph .bme-graph-statusbar { + flex-shrink: 0; + padding: 4px 12px; + font-size: 10px; +} + +#bme-pane-graph .bme-cognition-workspace { + padding: 12px; + overflow-y: auto; +} + /* ═══════ ⑤ 高级设置折叠 (desktop+mobile) ═══════ */ .bme-advanced-settings { @@ -3954,29 +4154,21 @@ gap: 4px; } - /* ⑥ 图谱 tab 移动端全屏覆盖 */ - .bme-panel-main.mobile-visible { - display: flex; - position: absolute; - inset: 0; - z-index: 20; - border-radius: 0; - } - - .bme-panel-main.mobile-visible ~ .bme-panel-tabbar { - z-index: 21; - } - - /* ② 底部 Tabbar safe-area + 触控增大 */ + /* ② 底部 Tabbar safe-area + 6 列 icon-only */ .bme-panel-tabbar { display: flex; padding-bottom: env(safe-area-inset-bottom, 0px); } .bme-panel-tabbar .bme-tab-btn { - min-height: 52px; - gap: 3px; - font-size: 10px; + min-height: 44px; + gap: 2px; + font-size: 0; + padding: 6px 2px; + } + + .bme-panel-tabbar .bme-tab-btn span { + display: none; } .bme-panel-tabbar .bme-tab-btn i { @@ -4289,9 +4481,10 @@ font-size: 22px; } - /* 节点详情移动端全宽 */ - .bme-node-detail { - width: 100%; + /* 移动端图谱内悬浮窗略收窄(桌面 #bme-node-detail 在窄屏下不可见) */ + #bme-mobile-node-detail { + width: min(400px, calc(100% - 16px)); + max-height: min(82vh, 680px); } /* 图例 */ @@ -4299,88 +4492,6 @@ font-size: 9px; } - /* 移动端图谱区重设计 */ - .bme-mobile-graph-section { - display: block; - margin: 12px 0 0; - border: 1px solid var(--bme-border); - border-radius: 12px; - background: var(--bme-surface-lowest); - overflow: hidden; - } - - .bme-mobile-graph-tabs { - display: flex; - border-bottom: 1px solid var(--bme-border); - } - - .bme-mobile-graph-tab { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 10px 8px; - border: none; - background: transparent; - color: var(--bme-on-surface-dim); - font-size: 12px; - font-weight: 600; - cursor: pointer; - transition: all 0.15s ease; - position: relative; - } - - .bme-mobile-graph-tab:hover { - color: var(--bme-on-surface); - background: rgba(255, 255, 255, 0.04); - } - - .bme-mobile-graph-tab.active { - color: var(--bme-primary); - } - - .bme-mobile-graph-tab.active::after { - content: ""; - position: absolute; - bottom: 0; - left: 20%; - right: 20%; - height: 2px; - background: var(--bme-primary); - border-radius: 2px 2px 0 0; - } - - .bme-mobile-view-pane { - display: none; - padding: 12px; - min-height: 120px; - } - - .bme-mobile-view-pane.active { - display: block; - } - - .bme-mobile-fullscreen-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - width: 100%; - padding: 12px; - border: none; - border-top: 1px solid var(--bme-border); - background: rgba(255, 255, 255, 0.02); - color: var(--bme-primary); - font-size: 12px; - font-weight: 600; - cursor: pointer; - transition: background 0.15s ease; - } - - .bme-mobile-fullscreen-btn:hover { - background: rgba(255, 255, 255, 0.06); - } } /* ═══════ Help Tip (? 图标 + 气泡) ═══════ */ diff --git a/ui/graph-renderer.js b/ui/graph-renderer.js index 824706a..a2deddc 100644 --- a/ui/graph-renderer.js +++ b/ui/graph-renderer.js @@ -205,6 +205,8 @@ export class GraphRenderer { this.isDragging = false; this.isPanning = false; this.lastMouse = { x: 0, y: 0 }; + /** @type {{ startX: number, startY: number, lastX: number, lastY: number, nodeCandidate: object|null, moved: boolean } | null} */ + this._touchSession = null; this.animId = null; @@ -771,19 +773,55 @@ export class GraphRenderer { c.addEventListener('wheel', (e) => this._onWheel(e), { passive: false }); c.addEventListener('dblclick', (e) => this._onDoubleClick(e)); + // 触摸:单指始终平移画布,松手时在未移动过的情况下视为「点击」选中节点(避免拖动画布时误拖节点) c.addEventListener('touchstart', (e) => { - if (e.touches.length === 1) { - const t = e.touches[0]; - this._onMouseDown({ clientX: t.clientX, clientY: t.clientY, button: 0 }); + if (e.touches.length !== 1) { + this._touchSession = null; + return; } - }, { passive: true }); + e.preventDefault(); + const t = e.touches[0]; + const { x, y } = this._canvasToWorld(t.clientX, t.clientY); + this._touchSession = { + startX: t.clientX, + startY: t.clientY, + lastX: t.clientX, + lastY: t.clientY, + nodeCandidate: this._findNodeAt(x, y), + moved: false, + }; + }, { passive: false }); c.addEventListener('touchmove', (e) => { - if (e.touches.length === 1) { - const t = e.touches[0]; - this._onMouseMove({ clientX: t.clientX, clientY: t.clientY }); + if (!this._touchSession || e.touches.length !== 1) return; + e.preventDefault(); + const t = e.touches[0]; + const dx = t.clientX - this._touchSession.lastX; + const dy = t.clientY - this._touchSession.lastY; + const fromStartX = t.clientX - this._touchSession.startX; + const fromStartY = t.clientY - this._touchSession.startY; + if (Math.abs(fromStartX) > 5 || Math.abs(fromStartY) > 5) { + this._touchSession.moved = true; } - }, { passive: true }); - c.addEventListener('touchend', () => this._onMouseUp({})); + this.offsetX += dx; + this.offsetY += dy; + this._touchSession.lastX = t.clientX; + this._touchSession.lastY = t.clientY; + this._render(); + }, { passive: false }); + c.addEventListener('touchend', (e) => { + if (!this._touchSession) return; + const sess = this._touchSession; + this._touchSession = null; + if (!sess.moved && sess.nodeCandidate) { + this.selectedNode = sess.nodeCandidate; + if (this.onNodeSelect) this.onNodeSelect(sess.nodeCandidate); + if (this.onNodeClick) this.onNodeClick(sess.nodeCandidate); + this._render(); + } + }); + c.addEventListener('touchcancel', () => { + this._touchSession = null; + }); } _canvasToWorld(clientX, clientY) { diff --git a/ui/panel.html b/ui/panel.html index 07cf088..8eaf33a 100644 --- a/ui/panel.html +++ b/ui/panel.html @@ -250,22 +250,6 @@ -
-
- - -
-
-
- -
-
最近提取
@@ -483,6 +467,104 @@ + +
+
+ + + +
+ +
+
+
+ + +
+ + + +
+
+
+
+ READY + NODES: 0 | EDGES: 0 +
+
+ +
+
+
+
+
+
+ 角色认知清单 +
+
+
+
+
+
+ 空间控制台 +
+
+
+ 任务监视器 +
+
+
+
+
+
+ +
+
+
+
+ + +
+
+

节点详情

+
+ + + +
+
+
+
+
@@ -2775,6 +2857,10 @@
+