Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/src/lib/refView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface ForceNode extends SimulationNodeDatum {
sort: string
color: string
radius: number
degree?: number
cluster?: number
state?: string
}
Expand Down
26 changes: 22 additions & 4 deletions web/src/panels/workspace/NetworkView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export const NetworkView = memo(function NetworkView() {
}

// Dashed outline for non-atoms (degree >= 1)
const degree = (node as any).degree as number | undefined
const degree = node.degree
if (degree != null && degree >= 1) {
ctx.beginPath()
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI)
Expand Down Expand Up @@ -299,7 +299,12 @@ export const NetworkView = memo(function NetworkView() {
.subject((event) => {
const w = screenToWorld(event.x, event.y)
const node = hitTestNode(nodesRef.current, w.x, w.y)
if (node) return { x: node.x, y: node.y, node }
if (node) {
// subject 必须用屏幕坐标:d3-drag 后续以 subject + 屏幕位移算
// event.x,若这里返回世界坐标会与屏幕位移混用 → 节点一拖即飞。
const t = transformRef.current
return { x: (node.x ?? 0) * t.k + t.x, y: (node.y ?? 0) * t.k + t.y, node }
}
return null
})
.on('start', (event) => {
Expand Down Expand Up @@ -427,20 +432,26 @@ export const NetworkView = memo(function NetworkView() {
} catch {}

const activeMode = usePluginStore.getState().getActiveNetworkMode()
// 在 effect 内读取最新模式,避免用到闭包里 stale 的 networkMode。
const networkMode = usePluginStore.getState().isAnyModeActive()
const url = activeMode
? `${API_BASE}${activeMode.endpoint}?path=${encodeURIComponent(path)}&source=${sourceFilter}&merge=${mergeProofs}&size=${sizeBy}&color=${colorBy}&cluster=${clusterBy}`
: `${API_BASE}/api/astrolabe/ref-graph?path=${encodeURIComponent(path)}`

// 竞态守卫:effect 重跑/卸载后,先发的旧请求回来不再写入。
let alive = true
fetch(url)
.then(r => r.json())
.then(data => {
if (!alive) return
let forceNodes: ForceNode[]
let forceLinks: ForceLink[]

if (networkMode) {
// Backend already computed nodes with radius/color/cluster
forceNodes = (data.nodes || []).map((n: any) => ({
id: n.id, name: n.title || n.id, sort: n.sort || '', color: n.color, radius: n.radius,
...(n.degree !== undefined ? { degree: n.degree } : {}),
...(n.cluster !== undefined ? { cluster: n.cluster } : {}),
...(n.state ? { state: n.state } : {}),
}))
Expand All @@ -456,6 +467,7 @@ export const NetworkView = memo(function NetworkView() {
fetch(`${API_BASE}${activeMode!.endpoint}?path=${p}&source=all&size=uniform&color=${colorBy}`)
.then(r => r.ok ? r.json() : null)
.then(allData => {
if (!alive) return
const colorMap: Record<string, string> = {}
for (const n of (allData?.nodes || data.nodes || [])) colorMap[n.id] = n.color
;(window as any).__skeletonColors = colorMap
Expand All @@ -468,7 +480,7 @@ export const NetworkView = memo(function NetworkView() {
const refNodes = buildRefViewNodes(data.nodes || [])
const refLinks = buildRefViewLinks(data.links || [])
forceNodes = refNodes.map(n => ({
id: n.id, name: n.name || n.id, sort: n.sort || `degree-${n.degree}`, color: n.color, radius: n.radius,
id: n.id, name: n.name || n.id, sort: n.sort || `degree-${n.degree}`, color: n.color, radius: n.radius, degree: n.degree,
}))
forceLinks = refLinks.map(l => ({
id: `ref-${l.source}-${l.target}-${l.position}`,
Expand Down Expand Up @@ -517,11 +529,12 @@ export const NetworkView = memo(function NetworkView() {
sim.alpha(1).restart()
})
.catch(err => console.warn('[NetworkView] fetch failed:', err))
return () => { alive = false }
}, [loadKey])

// ── Style-only update: re-fetch skeleton graph and update radius/color in place ──
useEffect(() => {
if (styleKey === 0 || !networkMode) return
if (styleKey === 0 || !usePluginStore.getState().isAnyModeActive()) return
const path = new URLSearchParams(window.location.search).get('path')
if (!path) return

Expand All @@ -533,9 +546,12 @@ export const NetworkView = memo(function NetworkView() {

const modeForStyle = usePluginStore.getState().getActiveNetworkMode()
if (!modeForStyle) return
// 竞态守卫:同加载路径。
let alive = true
fetch(`${API_BASE}${modeForStyle.endpoint}?path=${encodeURIComponent(path)}&source=${sourceFilter}&merge=${mergeProofs}&size=${sizeBy}&color=${colorBy}&cluster=${clusterBy}`)
.then(r => r.json())
.then(data => {
if (!alive) return
const newNodes = data.nodes || []
const newEdges = data.edges || []
const nodeMap: Record<string, any> = {}
Expand All @@ -557,6 +573,7 @@ export const NetworkView = memo(function NetworkView() {
fetch(`${API_BASE}${modeForStyle.endpoint}?path=${p2}&source=all&size=uniform&color=${colorBy}`)
.then(r => r.ok ? r.json() : null)
.then(allData => {
if (!alive) return
const fullMap: Record<string, string> = {}
for (const n of (allData?.nodes || [])) fullMap[n.id] = n.color
// Merge with current nodes (they may have updated colors)
Expand Down Expand Up @@ -609,6 +626,7 @@ export const NetworkView = memo(function NetworkView() {
renderRef.current()
})
.catch(() => {})
return () => { alive = false }
}, [styleKey])

// ── flyTo on external selection ──
Expand Down
Loading