summaryrefslogtreecommitdiff
path: root/pages
diff options
context:
space:
mode:
Diffstat (limited to 'pages')
-rw-r--r--pages/index.tsx205
1 files changed, 142 insertions, 63 deletions
diff --git a/pages/index.tsx b/pages/index.tsx
index b22e678..42170a1 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -73,6 +73,10 @@ export default function Home() {
}
export function GraphPage() {
+ const [threeDim, setThreeDim] = usePersistantState('3d', false)
+ const [tagColors, setTagColors] = usePersistantState<TagColors>('tagCols', {})
+ const [scope, setScope] = useState<Scope>({ nodeIds: [] })
+
const [physics, setPhysics] = usePersistantState('physics', initialPhysics)
const [filter, setFilter] = usePersistantState('filter', initialFilter)
const [visuals, setVisuals] = usePersistantState('visuals', initialVisuals)
@@ -220,9 +224,6 @@ export function GraphPage() {
const { setEmacsTheme } = useContext(ThemeContext)
- const [threeDim, setThreeDim] = usePersistantState('3d', false)
- const [tagColors, setTagColors] = usePersistantState<TagColors>('tagCols', {})
- const [scope, setScope] = useState<Scope>({ nodeIds: [] })
const scopeRef = useRef<Scope>({ nodeIds: [] })
const behaviorRef = useRef(initialBehavior)
behaviorRef.current = behavior
@@ -248,26 +249,28 @@ export function GraphPage() {
),
)
if (command === 'zoom') {
- console.log(sr)
if (sr.nodeIds.length) {
- console.log('emptying')
- console.log('scope ' + sr.nodeIds)
setScope({ nodeIds: [] })
}
- setTimeout(() => fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!]), 50)
+ setTimeout(
+ () => fg.zoomToFit(speed, padding, (node: NodeObject) => nodes[node.id as string]),
+ 50,
+ )
return
}
if (!sr.nodeIds.length) {
setScope({ nodeIds: [emacsNode] })
setTimeout(() => {
- fg.centerAt(0, 0, speed)
+ fg.centerAt(0, 0, 10)
+ fg.zoomToFit(1, padding)
}, 50)
return
}
if (bh.localSame !== 'add') {
setScope({ nodeIds: [emacsNode] })
setTimeout(() => {
- fg.centerAt(0, 0, speed)
+ fg.centerAt(0, 0, 10)
+ fg.zoomToFit(1, padding)
}, 50)
return
}
@@ -281,7 +284,8 @@ export function GraphPage() {
) {
setScope({ nodeIds: [emacsNode] })
setTimeout(() => {
- fg.centerAt(0, 0, speed)
+ fg.centerAt(0, 0, 10)
+ fg.zoomToFit(1, padding)
}, 50)
return
}
@@ -289,10 +293,14 @@ export function GraphPage() {
...currentScope,
nodeIds: [...currentScope.nodeIds, emacsNode as string],
}))
- setTimeout(() => fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!]), 50)
+ setTimeout(() => {
+ fg.centerAt(0, 0, 10)
+ fg.zoomToFit(1, padding)
+ }, 50)
}
useEffect(() => {
+ // initialize websocket
WebSocketRef.current = new ReconnectingWebSocket('ws://localhost:35903')
WebSocketRef.current.addEventListener('open', () => {
console.log('Connection with Emacs established')
@@ -332,6 +340,20 @@ export function GraphPage() {
})
}, [])
+ useEffect(() => {
+ const fg = graphRef.current
+ if (!fg || scope.nodeIds.length > 1) {
+ return
+ }
+ if (!scope.nodeIds.length && physics.gravityOn) {
+ fg.zoomToFit()
+ return
+ }
+ setTimeout(() => {
+ fg.zoomToFit(5, 200)
+ }, 50)
+ }, [scope.nodeIds])
+
if (!graphData) {
return null
}
@@ -424,18 +446,21 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const [hoverNode, setHoverNode] = useState<NodeObject | null>(null)
+ const [rightClickedNode, setRightClickedNode] = useState<OrgRoamNode | null>(null)
+ const [contextPos, setContextPos] = useState([0, 0])
+
const theme = useTheme()
const { emacsTheme } = useContext<ThemeContextProps>(ThemeContext)
const handleLocal = (node: OrgRoamNode, add: string) => {
- if (scope.nodeIds.includes(node.id as string)) {
- return
- }
if (add === 'replace') {
setScope({ nodeIds: [node.id] })
return
}
+ if (scope.nodeIds.includes(node.id as string)) {
+ return
+ }
setScope((currentScope: Scope) => ({
...currentScope,
nodeIds: [...currentScope.nodeIds, node.id as string],
@@ -461,7 +486,15 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
sendMessageToEmacs('create', { id: node.id, title: node.title, ref: node.properties.ROAM_REFS })
}
- const handleClick = (click: string, node: OrgRoamNode) => {
+ const contextMenu = useDisclosure()
+
+ const openContextMenu = (node: OrgRoamNode, event: any) => {
+ setContextPos([event.pageX, event.pageY])
+ setRightClickedNode(node)
+ contextMenu.onOpen()
+ }
+
+ const handleClick = (click: string, node: OrgRoamNode, event: any) => {
switch (click) {
case mouse.local: {
handleLocal(node, behavior.localSame)
@@ -471,11 +504,41 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
openNodeInEmacs(node)
break
}
+ case mouse.context: {
+ openContextMenu(node, event)
+ }
default:
break
}
}
+ const findNthNeighbors = (ids: string[], n: number) => {
+ let queue = [ids[0]]
+ let todo: string[] = []
+ const completed = [ids[0]]
+ Array.from({ length: n }, () => {
+ queue.forEach((node) => {
+ const links = linksByNodeId[node as string] ?? []
+ links.forEach((link) => {
+ const [sourceId, targetId] = normalizeLinkEnds(link)
+ if (!completed.includes(sourceId)) {
+ todo.push(sourceId)
+ return
+ }
+ if (!completed.includes(targetId)) {
+ todo.push(targetId)
+ return
+ }
+ return
+ })
+ })
+ queue = todo
+ todo.forEach((neighbor) => neighbor && completed.push(neighbor))
+ todo = []
+ })
+ return completed
+ }
+
const centralHighlightedNode = useRef<NodeObject | null>(null)
useEffect(() => {
@@ -560,55 +623,79 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const filteredLinks = graphData.links.filter((link) => {
const [sourceId, targetId] = normalizeLinkEnds(link)
if (
- filter.bad ||
- filter.tagsBlacklist.length ||
- filter.tagsWhitelist.length ||
- filter.filelessCites
+ !filteredNodeIds.includes(sourceId as string) ||
+ !filteredNodeIds.includes(targetId as string)
) {
- return (
- filteredNodeIds.includes(sourceId as string) &&
- filteredNodeIds.includes(targetId as string)
- )
+ return false
}
const linkRoam = link as OrgRoamLink
return filter.parents || linkRoam.type !== 'parent'
})
- return { filteredNodes, filteredLinks }
+ return { nodes: filteredNodes, links: filteredLinks }
}, [filter, graphData])
- const scopedGraphData = useMemo(() => {
- const scopedNodes = filteredGraphData.filteredNodes.filter((node) => {
- const links = linksByNodeId[node.id as string] ?? []
- return (
- scope.nodeIds.includes(node.id as string) ||
- links.some((link) => {
- return scope.nodeIds.includes(link.source) || scope.nodeIds.includes(link.target)
- })
- )
- })
+ const [scopedGraphData, setScopedGraphData] = useState<GraphData>({ nodes: [], links: [] })
+ useEffect(() => {
+ if (!scope.nodeIds.length) {
+ return
+ }
+ const oldScopedNodes = scope.nodeIds.length > 1 ? scopedGraphData.nodes : []
+ const oldScopedNodeIds = oldScopedNodes.map((node) => node.id as string)
+ const neighbs = findNthNeighbors(scope.nodeIds, 1)
+ const newScopedNodes = filteredGraphData.nodes
+ .filter((node) => {
+ if (oldScopedNodes.length) {
+ if (oldScopedNodeIds.includes(node.id as string)) {
+ return false
+ }
+ const links = linksByNodeId[node.id as string] ?? []
+ return links.some((link) => {
+ return scope.nodeIds.includes(link.source) || scope.nodeIds.includes(link.target)
+ })
+ }
+ return neighbs.includes(node.id as string)
+ // this creates new nodes, to separate them from the nodes in the global graph
+ // and positions them in the center, so that the camera is not so jumpy
+ })
+ .map((node) => {
+ return { ...node, x: 0, y: 0, vy: 0, vx: 0 }
+ })
+ const scopedNodes = [...oldScopedNodes, ...newScopedNodes]
const scopedNodeIds = scopedNodes.map((node) => node.id as string)
- const scopedLinks = filteredGraphData.filteredLinks.filter((link) => {
- const [sourceId, targetId] = normalizeLinkEnds(link)
- return (
- scopedNodeIds.includes(sourceId as string) && scopedNodeIds.includes(targetId as string)
- )
- })
-
- return scope.nodeIds.length === 0
- ? { nodes: filteredGraphData.filteredNodes, links: filteredGraphData.filteredLinks }
- : {
- nodes: scopedNodes,
- links: scopedLinks,
+ const oldScopedLinks = scope.nodeIds.length > 1 ? scopedGraphData.links : []
+ const newScopedLinks = filteredGraphData.links
+ .filter((link) => {
+ // we need to cover both because force-graph modifies the original data
+ // but if we supply the original data on each render, the graph will re-render sporadically
+ const [sourceId, targetId] = normalizeLinkEnds(link)
+ if (
+ oldScopedLinks.length &&
+ oldScopedNodeIds.includes(targetId) &&
+ oldScopedNodeIds.includes(sourceId)
+ ) {
+ return false
}
+ return (
+ scopedNodeIds.includes(sourceId as string) && scopedNodeIds.includes(targetId as string)
+ )
+ })
+ .map((link) => {
+ const [sourceId, targetId] = normalizeLinkEnds(link)
+ return { source: sourceId, target: targetId }
+ })
+
+ const scopedLinks = [...oldScopedLinks, ...newScopedLinks]
+
+ setScopedGraphData({ nodes: scopedNodes, links: scopedLinks })
}, [filter, scope, graphData])
useEffect(() => {
;(async () => {
const fg = graphRef.current
const d3 = await d3promise
- if (physics.gravityOn) {
+ if (physics.gravityOn && !(scope.nodeIds.length && !physics.gravityLocal)) {
fg.d3Force('x', d3.forceX().strength(physics.gravity))
fg.d3Force('y', d3.forceY().strength(physics.gravity))
threeDim && fg.d3Force('z', d3.forceZ().strength(physics.gravity))
@@ -628,18 +715,18 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
physics.collision ? d3.forceCollide().radius(physics.collisionStrength) : null,
)
})()
- }, [physics])
+ }, [physics, threeDim, scope])
// 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
useEffect(() => {
graphRef.current?.d3ReheatSimulation()
- }, [physics])
+ }, [physics, scope.nodeIds.length])
// shitty handler to check for doubleClicks
const lastNodeClickRef = useRef(0)
- const [opacity, setOpacity] = useState<number>(1)
+ const [opacity, setOpacity] = useState(1)
const [fadeIn, cancel] = useAnimation((x) => setOpacity(x), {
duration: visuals.animationSpeed,
algorithm: algos[visuals.algorithmName],
@@ -811,20 +898,13 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
}
const [dragging, setDragging] = useState(false)
- const contextMenu = 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)
- contextMenu.onOpen()
- }
const graphCommonProps: ComponentPropsWithoutRef<typeof TForceGraph2D> = {
- graphData: scopedGraphData,
+ graphData: scope.nodeIds.length ? scopedGraphData : filteredGraphData,
width: windowWidth,
height: windowHeight,
backgroundColor: theme.colors.gray[visuals.backgroundColor],
+ warmupTicks: scope.nodeIds.length === 1 ? 100 : scope.nodeIds.length > 1 ? 20 : 0,
nodeLabel: (node) => (node as OrgRoamNode).title,
nodeColor: (node) => {
return getNodeColor(node as OrgRoamNode)
@@ -961,9 +1041,9 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const isDoubleClick = event.timeStamp - lastNodeClickRef.current < 400
lastNodeClickRef.current = event.timeStamp
if (isDoubleClick) {
- return handleClick('double', node)
+ return handleClick('double', node, event)
}
- return handleClick('click', node)
+ return handleClick('click', node, event)
},
onBackgroundClick: () => {
contextMenu.onClose()
@@ -989,9 +1069,8 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
},
onNodeRightClick: (nodeArg, event) => {
const node = nodeArg as OrgRoamNode
- openContextMenu(node, event)
- //handleClick('right', node)
+ handleClick('right', node, event)
},
onNodeDrag: (node) => {
contextMenu.onClose()