summaryrefslogtreecommitdiff
path: root/pages
diff options
context:
space:
mode:
Diffstat (limited to 'pages')
-rw-r--r--pages/index.tsx386
1 files changed, 300 insertions, 86 deletions
diff --git a/pages/index.tsx b/pages/index.tsx
index e298695..75efbfa 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -21,7 +21,7 @@ import { GraphData, NodeObject, LinkObject } from 'force-graph'
import { useWindowSize } from '@react-hook/window-size'
import { useAnimation } from '@lilib/hooks'
-import { Box, useTheme } from '@chakra-ui/react'
+import { Box, useDisclosure, useTheme } from '@chakra-ui/react'
import {
initialPhysics,
@@ -31,13 +31,16 @@ import {
initialMouse,
algos,
TagColors,
+ colorList,
} from '../components/config'
import { Tweaks } from '../components/tweaks'
+import { ContextMenu } from '../components/contextmenu'
import { ThemeContext, ThemeContextProps } from '../util/themecontext'
import SpriteText from 'three-spritetext'
import ReconnectingWebSocket from 'reconnecting-websocket'
+import { sendJson } from 'next/dist/next-server/server/api-utils'
// react-force-graph fails on import when server-rendered
// https://github.com/vasturiano/react-force-graph/issues/155
@@ -92,8 +95,14 @@ export function GraphPage() {
const nodeByIdRef = useRef<NodeById>({})
const linksByNodeIdRef = useRef<LinksByNodeId>({})
const tagsRef = useRef<Tags>([])
+ const graphRef = useRef<any>(null)
+
+ const currentGraphDataRef = useRef<GraphData>({ nodes: [], links: [] })
const updateGraphData = (orgRoamGraphData: OrgRoamGraphReponse) => {
+ const currentGraphData = currentGraphDataRef.current
+ const oldNodeById = nodeByIdRef.current
+ const oldLinksByNodeId = linksByNodeIdRef.current
tagsRef.current = orgRoamGraphData.tags ?? []
const nodesByFile = orgRoamGraphData.nodes.reduce<NodesByFile>((acc, node) => {
return {
@@ -122,12 +131,40 @@ export function GraphPage() {
nodeByIdRef.current = Object.fromEntries(orgRoamGraphData.nodes.map((node) => [node.id, node]))
const dirtyLinks = [...orgRoamGraphData.links, ...fileLinks]
- const links = dirtyLinks.filter((link) => {
+ const nonExistantNodes: OrgRoamNode[] = []
+ const links = dirtyLinks.map((link) => {
const sourceId = link.source as string
const targetId = link.target as string
- return nodeByIdRef.current[sourceId] && nodeByIdRef.current[targetId]
+ if (!nodeByIdRef.current[sourceId]) {
+ nonExistantNodes.push({
+ id: sourceId,
+ tags: ['bad'],
+ properties: { FILELESS: 'yes', bad: 'yes' },
+ file: '',
+ title: sourceId,
+ level: 0,
+ })
+ return { ...link, type: 'bad' }
+ }
+ if (!nodeByIdRef.current[targetId]) {
+ nonExistantNodes.push({
+ id: targetId,
+ tags: ['bad'],
+ properties: { FILELESS: 'yes', bad: 'yes' },
+ file: '',
+ title: targetId,
+ level: 0,
+ })
+ return { ...link, type: 'bad' }
+ }
+ return link
})
+ nodeByIdRef.current = {
+ ...nodeByIdRef.current,
+ ...Object.fromEntries(nonExistantNodes.map((node) => [node.id, node])),
+ }
+
linksByNodeIdRef.current = links.reduce<LinksByNodeId>((acc, link) => {
return {
...acc,
@@ -136,17 +173,108 @@ export function GraphPage() {
}
}, {})
- const orgRoamGraphDataWithFileLinks = {
- ...orgRoamGraphData,
+ const nodes = [...orgRoamGraphData.nodes, ...nonExistantNodes]
+ const orgRoamGraphDataWithFileLinksAndBadNdes = {
+ nodes,
links,
}
- // react-force-graph modifies the graph data implicitly,
- // so we make sure there's no overlap between the objects we pass it and
- // nodeByIdRef, linksByNodeIdRef
- const orgRoamGraphDataClone = JSON.parse(JSON.stringify(orgRoamGraphDataWithFileLinks))
- setGraphData(orgRoamGraphDataClone)
+ if (!currentGraphData.nodes.length) {
+ // react-force-graph modifies the graph data implicitly,
+ // so we make sure there's no overlap between the objects we pass it and
+ // nodeByIdRef, linksByNodeIdRef
+ const orgRoamGraphDataClone = JSON.parse(
+ JSON.stringify(orgRoamGraphDataWithFileLinksAndBadNdes),
+ )
+ console.log(orgRoamGraphDataClone)
+ currentGraphDataRef.current = orgRoamGraphDataClone
+ setGraphData(orgRoamGraphDataClone)
+ return
+ }
+
+ const newNodes = [
+ ...currentGraphData.nodes.map((node: NodeObject) => {
+ const newNode = nodeByIdRef.current[node?.id!] ?? false
+ if (!newNode) {
+ return
+ }
+ return { ...node, ...newNode }
+ }),
+ ...Object.keys(nodeByIdRef.current)
+ .filter((id) => !oldNodeById[id])
+ .map((id) => {
+ console.log(id)
+ return nodeByIdRef.current[id] as NodeObject
+ }),
+ ]
+
+ const nodeIndex = newNodes.reduce<{ [id: string]: number }>((acc, node, index) => {
+ const id = node?.id as string
+ return {
+ ...acc,
+ [id]: index,
+ }
+ }, {})
+ console.log(newNodes)
+ console.log(nodeIndex)
+ /* const currentGraphIndexByLink = currentGraphData.links.reduce<{[key: string]: number}>((acc, link, index) => {
+* const [source, target] = normalizeLinkEnds(link)
+* const sourceTarget=source+target
+* return {
+* ...acc,
+* [sourceTarget]: index
+* }
+},{}) */
+ /* const newLinks = [
+ * ...currentGraphData!.links.filter((link) => {
+ * const [source, target] = normalizeLinkEnds(link)
+ * if (!nodeByIdRef.current[source] || !nodeByIdRef.current[target]) {
+ * return false
+ * }
+ * if (
+ * !linksByNodeIdRef.current[source]!.some(
+ * (link) => link.target === target || link.source === target,
+ * ) &&
+ * !linksByNodeIdRef.current[target]!.some(
+ * (link) => link.target === source || link.source === source,
+ * )
+ * ) {
+ * return false
+ * }
+ * return true
+ * }),
+ * ...Object.keys(linksByNodeIdRef.current).flatMap((id) => {
+ * if (!oldLinksByNodeId[id]!) {
+ * return linksByNodeIdRef.current![id!]!
+ * }
+ * return (
+ * linksByNodeIdRef.current![id]!.filter((link) => {
+ * const [source, target] = normalizeLinkEnds(link)
+ * return !oldLinksByNodeId[id]!.some(
+ * (oldLink) => oldLink.source === source && oldLink.target === target,
+ * )!
+ * }) ?? []
+ * )
+ * }),
+ * ]
+ */
+ const newerLinks = links.map((link) => {
+ const [source, target] = normalizeLinkEnds(link)
+ return {
+ ...link,
+ source: newNodes[nodeIndex![source]],
+ target: newNodes[nodeIndex![target]],
+ }
+ })
+ const fg = graphRef.current
+ setGraphData({ nodes: newNodes as NodeObject[], links: newerLinks })
}
+ useEffect(() => {
+ if (!graphData) {
+ return
+ }
+ currentGraphDataRef.current = graphData
+ }, [graphData])
const { setEmacsTheme } = useContext(ThemeContext)
@@ -156,7 +284,6 @@ export function GraphPage() {
const scopeRef = useRef<Scope>({ nodeIds: [] })
const behaviorRef = useRef(initialBehavior)
behaviorRef.current = behavior
- const graphRef = useRef<any>(null)
const WebSocketRef = useRef<any>(null)
scopeRef.current = scope
@@ -166,6 +293,9 @@ export function GraphPage() {
speed: number = 2000,
padding: number = 200,
) => {
+ if (command === 'color') {
+ return
+ }
const fg = graphRef.current
const sr = scopeRef.current
const bh = behaviorRef.current
@@ -269,7 +399,7 @@ export function GraphPage() {
}
return (
- <Box display="flex" alignItems="flex-start" flexDirection="row" height="100%">
+ <Box display="flex" alignItems="flex-start" flexDirection="row" height="100%" overflow="hidden">
<Tweaks
{...{
physics,
@@ -289,7 +419,7 @@ export function GraphPage() {
}}
tags={tagsRef.current}
/>
- <Box position="absolute" alignItems="top">
+ <Box position="absolute" alignItems="top" overflow="hidden">
<Graph
ref={graphRef}
nodeById={nodeByIdRef.current!}
@@ -360,21 +490,48 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const { emacsTheme } = useContext<ThemeContextProps>(ThemeContext)
- const handleClick = (click: string, node: NodeObject) => {
+ const handleLocal = (node: OrgRoamNode, add: string) => {
+ if (scope.nodeIds.includes(node.id as string)) {
+ return
+ }
+ if (add === 'replace') {
+ setScope({ nodeIds: [node.id] })
+ return
+ }
+ setScope((currentScope: Scope) => ({
+ ...currentScope,
+ nodeIds: [...currentScope.nodeIds, node.id as string],
+ }))
+ return
+ }
+
+ const sendMessageToEmacs = (command: string, data: {}) => {
+ webSocket.send(JSON.stringify({ command: command, data: data }))
+ }
+ const openNodeInEmacs = (node: OrgRoamNode) => {
+ sendMessageToEmacs('open', { id: node.id })
+ }
+
+ const deleteNodeInEmacs = (node: OrgRoamNode) => {
+ if (node.level !== 0) {
+ return
+ }
+ sendMessageToEmacs('delete', { id: node.id, file: node.file })
+ }
+
+ const createNodeInEmacs = (node: OrgRoamNode) => {
+ sendMessageToEmacs('create', { id: node.id, title: node.title, ref: node.properties.ROAM_REFS })
+ }
+
+ const handleClick = (click: string, node: OrgRoamNode) => {
switch (click) {
//mouse.highlight:
case mouse.local: {
- if (scope.nodeIds.includes(node.id as string)) {
- break
- }
- setScope((currentScope: Scope) => ({
- ...currentScope,
- nodeIds: [...currentScope.nodeIds, node.id as string],
- }))
+ handleLocal(node, behavior.localSame)
break
}
case mouse.follow: {
- webSocket.send(node.id)
+ openNodeInEmacs(node)
break
}
default:
@@ -423,8 +580,13 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const filteredLinksByNodeId = useRef<LinksByNodeId>({})
const filteredGraphData = useMemo(() => {
hiddenNodeIdsRef.current = {}
- const filteredNodes = graphData.nodes
- .filter((nodeArg) => {
+ const filteredNodes = graphData?.nodes
+ ?.filter((nodeArg) => {
+ //sometimes there will be some undefined nodes in the mix
+ // should probably fix the actual issue, but this is a fix
+ if (!nodeArg) {
+ return
+ }
const node = nodeArg as OrgRoamNode
if (
filter.tagsBlacklist.length &&
@@ -440,15 +602,18 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
hiddenNodeIdsRef.current = { ...hiddenNodeIdsRef.current, [node.id]: node }
return false
}
- if (filter.fileless_cites && node.properties.FILELESS) {
+ if (filter.filelessCites && node.properties.FILELESS) {
+ hiddenNodeIdsRef.current = { ...hiddenNodeIdsRef.current, [node.id]: node }
+ return false
+ }
+ if (filter.bad && node.properties.bad) {
hiddenNodeIdsRef.current = { ...hiddenNodeIdsRef.current, [node.id]: node }
return false
}
return true
})
- .filter((nodeArg) => {
- const node = nodeArg as OrgRoamNode
- const links = linksByNodeId[node.id as string] ?? []
+ .filter((node) => {
+ const links = linksByNodeId[node?.id as string] ?? []
const unhiddenLinks = links.filter(
(link) =>
!hiddenNodeIdsRef.current[link.source] && !hiddenNodeIdsRef.current[link.target],
@@ -472,7 +637,12 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const filteredNodeIds = filteredNodes.map((node) => node.id as string)
const filteredLinks = graphData.links.filter((link) => {
const [sourceId, targetId] = normalizeLinkEnds(link)
- if (filter.tagsBlacklist.length || filter.tagsWhitelist.length || filter.fileless_cites) {
+ if (
+ filter.bad ||
+ filter.tagsBlacklist.length ||
+ filter.tagsWhitelist.length ||
+ filter.filelessCites
+ ) {
return (
filteredNodeIds.includes(sourceId as string) &&
filteredNodeIds.includes(targetId as string)
@@ -538,7 +708,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
physics.collision ? d3.forceCollide().radius(physics.collisionStrength) : null,
)
})()
- })
+ }, [physics])
// Normally the graph doesn't update when you just change the physics parameters
// This forces the graph to make a small update when you do
@@ -588,41 +758,17 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}
const highlightColors = useMemo(() => {
- const allColors = visuals.nodeColorScheme.concat(
- visuals.linkColorScheme || [],
- visuals.linkHighlight || [],
- visuals.nodeHighlight || [],
- visuals.citeNodeColor || [],
- visuals.citeLinkColor || [],
- visuals.citeLinkHighlightColor || [],
- visuals.refNodeColor || [],
- visuals.refLinkColor || [],
- visuals.refLinkHighlightColor || [],
- )
-
return Object.fromEntries(
- allColors.map((color) => {
+ colorList.map((color) => {
const color1 = getThemeColor(color)
- const crisscross = allColors.map((color2) => [
+ const crisscross = colorList.map((color2) => [
color2,
d3int.interpolate(color1, getThemeColor(color2)),
])
return [color, Object.fromEntries(crisscross)]
}),
)
- }, [
- visuals.nodeColorScheme,
- visuals.linkHighlight,
- visuals.nodeHighlight,
- visuals.linkColorScheme,
- visuals.refLinkColor,
- visuals.refNodeColor,
- visuals.citeNodeColor,
- visuals.refLinkHighlightColor,
- visuals.citeLinkHighlightColor,
- visuals.citeLinkColor,
- emacsTheme,
- ])
+ }, [emacsTheme])
const previouslyHighlightedNodes = useMemo(() => {
const previouslyHighlightedLinks = linksByNodeId[lastHoverNode.current?.id!] ?? []
@@ -662,11 +808,13 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
if (!needsHighlighting && !visuals.linkColorScheme) {
const nodeColor = getLinkNodeColor(sourceId, targetId)
- return getThemeColor(nodeColor)
+ return highlightColors[nodeColor][visuals.backgroundColor](visuals.highlightFade * opacity)
}
if (!needsHighlighting) {
- return getThemeColor(visuals.linkColorScheme)
+ return highlightColors[visuals.linkColorScheme][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
}
if (!visuals.linkHighlight && !visuals.linkColorScheme) {
@@ -686,6 +834,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}
const getNodeColor = (node: OrgRoamNode) => {
+ const isHighlightingHappening = !!highlightedNodes.length
const needsHighlighting = highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
// if we are matching the node color and don't have a highlight color
// or we don't have our own scheme and we're not being highlighted
@@ -694,16 +843,26 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}
if (tagColors && node.tags.some((tag) => tagColors[tag])) {
const tagColor = tagColors[node.tags.filter((tag) => tagColors[tag])[0]]
- return getThemeColor(tagColor)
+ return highlightColors[tagColor][visuals.backgroundColor](visuals.highlightFade * opacity)
}
if (visuals.citeNodeColor && node.properties.ROAM_REFS && node.properties.FILELESS) {
- return getThemeColor(visuals.citeNodeColor)
+ return needsHighlighting
+ ? getThemeColor(visuals.citeNodeColor)
+ : highlightColors[visuals.citeNodeColor][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
}
if (visuals.refNodeColor && node.properties.ROAM_REFS) {
- return getThemeColor(visuals.refNodeColor)
+ return needsHighlighting
+ ? getThemeColor(visuals.refNodeColor)
+ : highlightColors[visuals.refNodeColor][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
}
if (!needsHighlighting) {
- return getThemeColor(getNodeColorById(node.id as string))
+ return highlightColors[getNodeColorById(node.id as string)][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
}
if (!visuals.nodeHighlight) {
return getThemeColor(getNodeColorById(node.id as string))
@@ -731,6 +890,31 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
[visuals.labelBackgroundColor, emacsTheme],
)
+ const nodeSize = (node: NodeObject) => {
+ const links = linksByNodeId[node.id!] ?? []
+ const parentNeighbors = links.length ? links.filter((link) => link.type === 'parent').length : 0
+ const basicSize =
+ 3 + links.length * visuals.nodeSizeLinks - (!filter.parents ? parentNeighbors : 0)
+ if (visuals.highlightNodeSize === 1) {
+ return basicSize
+ }
+ const highlightSize =
+ highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
+ ? 1 + opacity * (visuals.highlightNodeSize - 1)
+ : 1
+ return basicSize * highlightSize
+ }
+
+ const [dragging, setDragging] = useState(false)
+ const { isOpen, onOpen, onClose } = useDisclosure()
+ const [rightClickedNode, setRightClickedNode] = useState<OrgRoamNode | null>(null)
+ const [contextPos, setContextPos] = useState([0, 0])
+ const openContextMenu = (node: OrgRoamNode, event: any) => {
+ setContextPos([event.pageX, event.pageY])
+ setRightClickedNode(node)
+ onOpen()
+ }
+
const graphCommonProps: ComponentPropsWithoutRef<typeof TForceGraph2D> = {
graphData: scopedGraphData,
width: windowWidth,
@@ -742,21 +926,15 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
},
nodeRelSize: visuals.nodeRel,
nodeVal: (node) => {
- const links = linksByNodeId[node.id!] ?? []
- const parentNeighbors = links.length
- ? links.filter((link) => link.type === 'parent').length
- : 0
- const basicSize = 3 + links.length - (!filter.parents ? parentNeighbors : 0)
- const highlightSize =
- highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
- ? 1 + opacity * (visuals.highlightNodeSize - 1)
- : 1
- return basicSize * highlightSize
+ return nodeSize(node)
},
nodeCanvasObject: (node, ctx, globalScale) => {
if (!node) {
return
}
+ if (dragging) {
+ return
+ }
if (!visuals.labels) {
return
@@ -774,7 +952,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const nodeTitle = (node as OrgRoamNode).title!
const label = nodeTitle.substring(0, Math.min(nodeTitle.length, 40))
// const label = 'label'
- const fontSize = 12 / globalScale
+ const fontSize = 14 / (0.75 * Math.min(Math.max(0.5, globalScale), 3))
const textWidth = ctx.measureText(label).width
const bckgDimensions = [textWidth * 1.1, fontSize].map((n) => n + fontSize * 0.5) as [
number,
@@ -793,16 +971,16 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}
return highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
? Math.max(fadeFactor, opacity)
- : 1 * fadeFactor * (-1 * (0.5 * opacity - 1))
+ : 1 * fadeFactor * (-1 * (visuals.highlightFade * opacity - 1))
}
-
+ const nodeS = 8 * Math.cbrt(nodeSize(node) * visuals.nodeRel)
if (visuals.labelBackgroundColor && visuals.labelBackgroundOpacity) {
const backgroundOpacity = getLabelOpacity() * visuals.labelBackgroundOpacity
const labelBackground = hexToRGBA(labelBackgroundColor, backgroundOpacity)
ctx.fillStyle = labelBackground
ctx.fillRect(
node.x! - bckgDimensions[0] / 2,
- node.y! - bckgDimensions[1] / 2,
+ node.y! - bckgDimensions[1] / 2 + nodeS,
...bckgDimensions,
)
}
@@ -814,7 +992,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const labelText = hexToRGBA(labelTextColor, textOpacity)
ctx.fillStyle = labelText
ctx.font = `${fontSize}px Sans-Serif`
- ctx.fillText(label, node.x!, node.y!)
+ ctx.fillText(label, node.x!, node.y! + nodeS)
},
nodeCanvasObjectMode: () => 'after',
@@ -837,19 +1015,26 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
? highlightColors[visuals.refLinkColor][
visuals.refLinkHighlightColor || visuals.linkHighlight
](opacity)
- : getThemeColor(visuals.refLinkColor)
+ : highlightColors[visuals.refLinkColor][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
}
if (visuals.citeLinkColor && roamLink.type === 'cite') {
return needsHighlighting && (visuals.citeLinkHighlightColor || visuals.linkHighlight)
? highlightColors[visuals.citeLinkColor][
visuals.citeLinkHighlightColor || visuals.linkHighlight
](opacity)
- : getThemeColor(visuals.citeLinkColor)
+ : highlightColors[visuals.citeLinkColor][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
}
return getLinkColor(sourceId as string, targetId as string, needsHighlighting)
},
linkWidth: (link) => {
+ if (visuals.highlightLinkSize === 1) {
+ return visuals.linkWidth
+ }
const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode.current)
const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current)
@@ -863,7 +1048,9 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
d3AlphaMin: physics.alphaMin,
d3VelocityDecay: physics.velocityDecay,
- onNodeClick: (node: NodeObject, event: any) => {
+ onNodeClick: (nodeArg: NodeObject, event: any) => {
+ const node = nodeArg as OrgRoamNode
+ onClose()
const isDoubleClick = event.timeStamp - lastNodeClickRef.current < 400
lastNodeClickRef.current = event.timeStamp
if (isDoubleClick) {
@@ -872,6 +1059,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
return handleClick('click', node)
},
onBackgroundClick: () => {
+ onClose()
setHoverNode(null)
if (scope.nodeIds.length === 0) {
return
@@ -892,13 +1080,39 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}
setHoverNode(node)
},
- onNodeRightClick: (node) => {
- handleClick('right', node)
+ onNodeRightClick: (nodeArg, event) => {
+ const node = nodeArg as OrgRoamNode
+ openContextMenu(node, event)
+
+ //handleClick('right', node)
+ },
+ onNodeDrag: (node) => {
+ onClose()
+ setHoverNode(node)
+ setDragging(true)
+ },
+ onNodeDragEnd: (node) => {
+ setHoverNode(null)
+ setDragging(false)
},
}
return (
- <div>
+ <Box overflow="hidden">
+ {isOpen && (
+ <ContextMenu
+ scope={scope}
+ node={rightClickedNode!}
+ nodeType={rightClickedNode?.id}
+ background={false}
+ coordinates={contextPos}
+ handleLocal={handleLocal}
+ menuClose={onClose}
+ openNodeInEmacs={openNodeInEmacs}
+ deleteNodeInEmacs={deleteNodeInEmacs}
+ createNodeInEmacs={createNodeInEmacs}
+ />
+ )}
{threeDim ? (
<ForceGraph3D
ref={graphRef}
@@ -940,13 +1154,13 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}}
/>
)}
- </div>
+ </Box>
)
})
function isLinkRelatedToNode(link: LinkObject, node: NodeObject | null) {
return (
- (link.source as NodeObject).id! === node?.id! || (link.target as NodeObject).id! === node?.id!
+ (link.source as NodeObject)?.id! === node?.id! || (link.target as NodeObject)?.id! === node?.id!
)
}