From fa84c052b221d157fa869cd4912acd538b5005a0 Mon Sep 17 00:00:00 2001
From: "Thomas F. K. Jorna" <hello@tefkah.com>
Date: Tue, 27 Sep 2022 14:32:54 +0200
Subject: comment out useless usestate

---
 pages/index.tsx | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

(limited to 'pages/index.tsx')

diff --git a/pages/index.tsx b/pages/index.tsx
index fdbabc3..6511773 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -79,7 +79,7 @@ export interface EmacsVariables {
   dailyDir?: string
   katexMacros?: { [key: string]: string }
   attachDir?: string
-  useInheritance?: boolean,
+  useInheritance?: boolean
   subDirs: string[]
 }
 export type Tags = string[]
@@ -461,6 +461,8 @@ export function GraphPage() {
               setEmacsNodeId(message.data.id)
               break
             }
+            case 'add-node-local': {
+            }
             default:
               return console.error('unknown message type', message.type)
           }
@@ -537,18 +539,17 @@ export function GraphPage() {
     return
   }
 
-  const [mainItem, setMainItem] = useState({
-    type: 'Graph',
-    title: 'Graph',
-    icon: <BiNetworkChart />,
-  })
+  // const [mainItem, setMainItem] = useState({
+  //   type: 'Graph',
+  //   title: 'Graph',
+  //   icon: <BiNetworkChart />,
+  // })
 
   const [mainWindowWidth, setMainWindowWidth] = usePersistantState<number>(
     'mainWindowWidth',
     windowWidth,
   )
 
-  console.log(emacsVariables)
   return (
     <VariablesContext.Provider value={{ ...emacsVariables }}>
       <Box
-- 
cgit v1.2.3


From a575d14b6621d5464d33d52ca642f2db70c3e370 Mon Sep 17 00:00:00 2001
From: "Thomas F. K. Jorna" <hello@tefkah.com>
Date: Tue, 27 Sep 2022 15:09:17 +0200
Subject: fix: move a ton of functions to separate files

---
 components/Graph/drawLabels.ts |  17 ++-
 pages/index.tsx                | 242 +++++++++--------------------------------
 util/findNthNeighbour.ts       |  42 +++++++
 util/getLinkColor.ts           |  82 ++++++++++++++
 util/getLinkNodeColor.ts       |  23 ++++
 util/getNodeColor.ts           |  75 +++++++++++++
 util/getNodeColorById.ts       |  25 +++++
 util/getThemeColor.ts          |   3 +
 util/hexToRGBA.ts              |  11 ++
 util/isLinkRelatedToNode.ts    |   7 ++
 util/nodeSize.ts               |  34 ++++++
 util/normalizeLinkEnds.ts      |  12 ++
 util/numberWithinRange.ts      |   3 +
 13 files changed, 383 insertions(+), 193 deletions(-)
 create mode 100644 util/findNthNeighbour.ts
 create mode 100644 util/getLinkColor.ts
 create mode 100644 util/getLinkNodeColor.ts
 create mode 100644 util/getNodeColor.ts
 create mode 100644 util/getNodeColorById.ts
 create mode 100644 util/getThemeColor.ts
 create mode 100644 util/hexToRGBA.ts
 create mode 100644 util/isLinkRelatedToNode.ts
 create mode 100644 util/nodeSize.ts
 create mode 100644 util/normalizeLinkEnds.ts
 create mode 100644 util/numberWithinRange.ts

(limited to 'pages/index.tsx')

diff --git a/components/Graph/drawLabels.ts b/components/Graph/drawLabels.ts
index fa19270..46790e6 100644
--- a/components/Graph/drawLabels.ts
+++ b/components/Graph/drawLabels.ts
@@ -1,8 +1,10 @@
 import { OrgRoamNode } from '../../api'
 import { NodeObject } from 'force-graph'
 import { initialVisuals } from '../config'
-import { hexToRGBA, LinksByNodeId } from '../../pages'
+import { LinksByNodeId } from '../../pages'
 import wrap from 'word-wrap'
+import { nodeSize } from '../../util/nodeSize'
+import { hexToRGBA } from '../../util/hexToRGBA'
 
 export interface drawLabelsProps {
   labelBackgroundColor: string
@@ -14,7 +16,6 @@ export interface drawLabelsProps {
   previouslyHighlightedNodes: { [id: string]: {} }
   visuals: typeof initialVisuals
   opacity: number
-  nodeSize: (node: NodeObject) => number
   filteredLinksByNodeId: LinksByNodeId
   nodeRel: number
   hoverNode: NodeObject | null
@@ -44,7 +45,6 @@ export function drawLabels(props: drawLabelsProps) {
     previouslyHighlightedNodes,
     visuals,
     opacity,
-    nodeSize,
     filteredLinksByNodeId,
     nodeRel,
     hoverNode,
@@ -78,7 +78,16 @@ export function drawLabels(props: drawLabelsProps) {
   const label = nodeTitle.substring(0, visuals.labelLength)
 
   const nodeS = Math.cbrt(
-    (visuals.nodeRel * nodeSize(node)) / Math.pow(globalScale, visuals.nodeZoomSize),
+    (visuals.nodeRel *
+      nodeSize({
+        node,
+        highlightedNodes,
+        linksByNodeId: filteredLinksByNodeId,
+        opacity,
+        previouslyHighlightedNodes,
+        visuals,
+      })) /
+      Math.pow(globalScale, visuals.nodeZoomSize),
   )
   const fontSize = visuals.labelFontSize / Math.cbrt(Math.pow(globalScale, visuals.nodeZoomSize))
   //   ? Math.max((visuals.labelFontSize * nodeS) / 2, (visuals.labelFontSize * nodeS) / 3)
diff --git a/pages/index.tsx b/pages/index.tsx
index 6511773..ddd3bb7 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -17,7 +17,6 @@ import { GraphData, LinkObject, NodeObject } from 'force-graph'
 import Head from 'next/head'
 import React, {
   ComponentPropsWithoutRef,
-  forwardRef,
   useContext,
   useEffect,
   useMemo,
@@ -30,12 +29,11 @@ import type {
   ForceGraph2D as TForceGraph2D,
   ForceGraph3D as TForceGraph3D,
 } from 'react-force-graph'
-import { BiChart, BiNetworkChart } from 'react-icons/bi'
+import { BiNetworkChart } from 'react-icons/bi'
 import { BsReverseLayoutSidebarInsetReverse } from 'react-icons/bs'
 import ReconnectingWebSocket from 'reconnecting-websocket'
 import SpriteText from 'three-spritetext'
 import useUndo from 'use-undo'
-import wrap from 'word-wrap'
 import { OrgRoamGraphReponse, OrgRoamLink, OrgRoamNode } from '../api'
 import {
   algos,
@@ -57,6 +55,13 @@ import { ThemeContext, ThemeContextProps } from '../util/themecontext'
 import { openNodeInEmacs } from '../util/webSocketFunctions'
 import { drawLabels } from '../components/Graph/drawLabels'
 import { VariablesContext } from '../util/variablesContext'
+import { findNthNeighbors } from '../util/findNthNeighbour'
+import { getThemeColor } from '../util/getThemeColor'
+import { normalizeLinkEnds } from '../util/normalizeLinkEnds'
+import { nodeSize } from '../util/nodeSize'
+import { getNodeColor } from '../util/getNodeColor'
+import { isLinkRelatedToNode } from '../util/isLinkRelatedToNode'
+import { getLinkColor } from '../util/getLinkColor'
 
 const d3promise = import('d3-force-3d')
 
@@ -809,36 +814,6 @@ export const Graph = function (props: GraphProps) {
     }
   }
 
-  const findNthNeighbors = (ids: string[], excludedIds: string[], n: number) => {
-    let queue = [ids[0]]
-    let todo: string[] = []
-    const completed = [ids[0]]
-    Array.from({ length: n }, () => {
-      queue.forEach((node) => {
-        const links = filteredLinksByNodeIdRef.current[node as string] ?? []
-        links.forEach((link) => {
-          const [sourceId, targetId] = normalizeLinkEnds(link)
-          if (excludedIds.some((id) => [sourceId, targetId].includes(id))) {
-            return
-          }
-          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(() => {
@@ -984,7 +959,12 @@ export const Graph = function (props: GraphProps) {
         ? scopedGraphData.nodes.filter((n) => !scope.excludedNodeIds.includes(n.id as string))
         : []
     const oldScopedNodeIds = oldScopedNodes.map((node) => node.id as string)
-    const neighbs = findNthNeighbors(scope.nodeIds, scope.excludedNodeIds, local.neighbors)
+    const neighbs = findNthNeighbors({
+      ids: scope.nodeIds,
+      excludedIds: scope.excludedNodeIds,
+      n: local.neighbors,
+      linksByNodeId: filteredLinksByNodeIdRef.current,
+    })
     const newScopedNodes = filteredGraphData.nodes
       .filter((node) => {
         if (oldScopedNodes.length) {
@@ -1079,6 +1059,7 @@ export const Graph = function (props: GraphProps) {
   // shitty handler to check for doubleClicks
   const lastNodeClickRef = useRef(0)
 
+  // this is for animations, it's a bit hacky and can definitely be optimized
   const [opacity, setOpacity] = useState(1)
   const [fadeIn, cancel] = useAnimation((x) => setOpacity(x), {
     duration: visuals.animationSpeed,
@@ -1107,10 +1088,7 @@ export const Graph = function (props: GraphProps) {
         ...links.flatMap((link) => [link.source, link.target]),
       ].map((nodeId) => [nodeId, {}]),
     )
-  }, [
-    JSON.stringify(centralHighlightedNode.current),
-    JSON.stringify(filteredLinksByNodeIdRef.current),
-  ])
+  }, [centralHighlightedNode.current, filteredLinksByNodeIdRef.current])
 
   useEffect(() => {
     if (sidebarHighlightedNode?.id) {
@@ -1121,6 +1099,7 @@ export const Graph = function (props: GraphProps) {
   }, [sidebarHighlightedNode])
 
   const lastHoverNode = useRef<OrgRoamNode | null>(null)
+
   useEffect(() => {
     centralHighlightedNode.current = hoverNode
     if (hoverNode) {
@@ -1163,102 +1142,6 @@ export const Graph = function (props: GraphProps) {
     )
   }, [JSON.stringify(hoverNode), lastHoverNode.current, filteredLinksByNodeIdRef.current])
 
-  const getNodeColorById = (id: string) => {
-    const linklen = filteredLinksByNodeIdRef.current[id!]?.length ?? 0
-    if (coloring.method === 'degree') {
-      return visuals.nodeColorScheme[
-        numberWithinRange(linklen, 0, visuals.nodeColorScheme.length - 1)
-      ]
-    }
-    return visuals.nodeColorScheme[
-      linklen && clusterRef.current[id] % visuals.nodeColorScheme.length
-    ]
-  }
-
-  const getLinkNodeColor = (sourceId: string, targetId: string) => {
-    return filteredLinksByNodeIdRef.current[sourceId]!.length >
-      filteredLinksByNodeIdRef.current[targetId]!.length
-      ? getNodeColorById(sourceId)
-      : getNodeColorById(targetId)
-  }
-
-  const getLinkColor = (
-    sourceId: string,
-    targetId: string,
-    needsHighlighting: boolean,
-    theme: any,
-  ) => {
-    if (!visuals.linkHighlight && !visuals.linkColorScheme && !needsHighlighting) {
-      const nodeColor = getLinkNodeColor(sourceId, targetId)
-      return getThemeColor(nodeColor, theme)
-    }
-
-    if (!needsHighlighting && !visuals.linkColorScheme) {
-      const nodeColor = getLinkNodeColor(sourceId, targetId)
-      return highlightColors[nodeColor][visuals.backgroundColor](visuals.highlightFade * opacity)
-    }
-
-    if (!needsHighlighting) {
-      return highlightColors[visuals.linkColorScheme][visuals.backgroundColor](
-        visuals.highlightFade * opacity,
-      )
-    }
-
-    if (!visuals.linkHighlight && !visuals.linkColorScheme) {
-      const nodeColor = getLinkNodeColor(sourceId, targetId)
-      return getThemeColor(nodeColor, theme)
-    }
-
-    if (!visuals.linkHighlight) {
-      return getThemeColor(visuals.linkColorScheme, theme)
-    }
-
-    if (!visuals.linkColorScheme) {
-      return highlightColors[getLinkNodeColor(sourceId, targetId)][visuals.linkHighlight](opacity)
-    }
-
-    return highlightColors[visuals.linkColorScheme][visuals.linkHighlight](opacity)
-  }
-
-  const getNodeColor = (node: OrgRoamNode, theme: any) => {
-    const needsHighlighting = highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
-    //const needsHighlighting = hoverNode?.id === node.id! || lastHoverNode?.current?.id === 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
-    if (visuals.emacsNodeColor && node.id === emacsNodeId) {
-      return getThemeColor(visuals.emacsNodeColor, theme)
-    }
-    if (tagColors && node?.tags.some((tag) => tagColors[tag])) {
-      const tagColor = tagColors[node?.tags.filter((tag) => tagColors[tag])[0]]
-      return needsHighlighting
-        ? highlightColors[tagColor][tagColor](visuals.highlightFade * opacity)
-        : highlightColors[tagColor][visuals.backgroundColor](visuals.highlightFade * opacity)
-    }
-    if (visuals.citeNodeColor && node?.properties?.ROAM_REFS && node?.properties?.FILELESS) {
-      return needsHighlighting
-        ? getThemeColor(visuals.citeNodeColor, theme)
-        : highlightColors[visuals.citeNodeColor][visuals.backgroundColor](
-            visuals.highlightFade * opacity,
-          )
-    }
-    if (visuals.refNodeColor && node.properties.ROAM_REFS) {
-      return needsHighlighting
-        ? getThemeColor(visuals.refNodeColor, theme)
-        : highlightColors[visuals.refNodeColor][visuals.backgroundColor](
-            visuals.highlightFade * opacity,
-          )
-    }
-    if (!needsHighlighting) {
-      return highlightColors[getNodeColorById(node.id as string)][visuals.backgroundColor](
-        visuals.highlightFade * opacity,
-      )
-    }
-    if (!visuals.nodeHighlight) {
-      return getThemeColor(getNodeColorById(node.id as string), theme)
-    }
-    return highlightColors[getNodeColorById(node.id as string)][visuals.nodeHighlight](opacity)
-  }
-
   const labelTextColor = useMemo(
     () => getThemeColor(visuals.labelTextColor, theme),
     [visuals.labelTextColor, emacsTheme],
@@ -1269,21 +1152,6 @@ export const Graph = function (props: GraphProps) {
     [visuals.labelBackgroundColor, emacsTheme],
   )
 
-  const nodeSize = (node: NodeObject) => {
-    const links = filteredLinksByNodeIdRef.current[node.id!] ?? []
-    const parentNeighbors = links.length ? links.filter((link) => link.type === 'parent').length : 0
-    const basicSize =
-      3 + links.length * visuals.nodeSizeLinks - (!filter.parent ? 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 scaleRef = useRef(1)
@@ -1295,11 +1163,33 @@ export const Graph = function (props: GraphProps) {
     warmupTicks: scope.nodeIds.length === 1 ? 100 : scope.nodeIds.length > 1 ? 20 : 0,
     onZoom: ({ k, x, y }) => (scaleRef.current = k),
     nodeColor: (node) => {
-      return getNodeColor(node as OrgRoamNode, theme)
+      return getNodeColor({
+        node: node as OrgRoamNode,
+        theme,
+        visuals,
+        cluster: clusterRef.current,
+        coloring,
+        emacsNodeId,
+        highlightColors,
+        highlightedNodes,
+        previouslyHighlightedNodes,
+        linksByNodeId: filteredLinksByNodeIdRef.current,
+        opacity,
+        tagColors,
+      })
     },
     nodeRelSize: visuals.nodeRel,
     nodeVal: (node) => {
-      return nodeSize(node) / Math.pow(scaleRef.current, visuals.nodeZoomSize)
+      return (
+        nodeSize({
+          node,
+          highlightedNodes,
+          linksByNodeId: filteredLinksByNodeIdRef.current,
+          opacity,
+          previouslyHighlightedNodes,
+          visuals,
+        }) / Math.pow(scaleRef.current, visuals.nodeZoomSize)
+      )
     },
     nodeCanvasObject: (node, ctx, globalScale) => {
       drawLabels({
@@ -1314,7 +1204,6 @@ export const Graph = function (props: GraphProps) {
           previouslyHighlightedNodes,
           visuals,
           opacity,
-          nodeSize,
           labelTextColor,
           labelBackgroundColor,
           hoverNode,
@@ -1356,7 +1245,18 @@ export const Graph = function (props: GraphProps) {
             )
       }
 
-      return getLinkColor(sourceId as string, targetId as string, needsHighlighting, theme)
+      return getLinkColor({
+        sourceId: sourceId as string,
+        targetId: targetId as string,
+        needsHighlighting,
+        theme,
+        cluster: clusterRef.current,
+        coloring,
+        highlightColors,
+        linksByNodeId: filteredLinksByNodeIdRef.current,
+        opacity,
+        visuals,
+      })
     },
     linkWidth: (link) => {
       if (visuals.highlightLinkSize === 1) {
@@ -1481,39 +1381,3 @@ export const Graph = function (props: GraphProps) {
     </Box>
   )
 }
-
-function isLinkRelatedToNode(link: LinkObject, node: NodeObject | null) {
-  return (
-    (link.source as NodeObject)?.id! === node?.id! || (link.target as NodeObject)?.id! === node?.id!
-  )
-}
-
-function numberWithinRange(num: number, min: number, max: number) {
-  return Math.min(Math.max(num, min), max)
-}
-
-export function normalizeLinkEnds(link: OrgRoamLink | LinkObject): [string, string] {
-  // 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 =
-    typeof link.source === 'object' ? (link.source.id! as string) : (link.source as string)
-  const targetId =
-    typeof link.target === 'object' ? (link.target.id! as string) : (link.target as string)
-  return [sourceId, targetId]
-}
-
-export function getThemeColor(name: string, theme: any) {
-  return name.split('.').reduce((o, i) => o[i], theme.colors)
-}
-
-export function hexToRGBA(hex: string, opacity: number) {
-  return (
-    'rgba(' +
-    (hex = hex.replace('#', ''))
-      .match(new RegExp('(.{' + hex.length / 3 + '})', 'g'))!
-      .map((l) => parseInt(hex.length % 2 ? l + l : l, 16))
-      .concat(isFinite(opacity) ? opacity : 1)
-      .join(',') +
-    ')'
-  )
-}
diff --git a/util/findNthNeighbour.ts b/util/findNthNeighbour.ts
new file mode 100644
index 0000000..d48adbc
--- /dev/null
+++ b/util/findNthNeighbour.ts
@@ -0,0 +1,42 @@
+import { LinksByNodeId } from '../pages'
+import { normalizeLinkEnds } from './normalizeLinkEnds'
+
+export const findNthNeighbors = ({
+  ids,
+  excludedIds,
+  n,
+  linksByNodeId,
+}: {
+  ids: string[]
+  excludedIds: string[]
+  n: number
+  linksByNodeId: LinksByNodeId
+}) => {
+  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 (excludedIds.some((id) => [sourceId, targetId].includes(id))) {
+          return
+        }
+        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
+}
diff --git a/util/getLinkColor.ts b/util/getLinkColor.ts
new file mode 100644
index 0000000..33f7094
--- /dev/null
+++ b/util/getLinkColor.ts
@@ -0,0 +1,82 @@
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { getLinkNodeColor } from './getLinkNodeColor'
+import { getThemeColor } from './getThemeColor'
+
+export const getLinkColor = ({
+  sourceId,
+  targetId,
+  needsHighlighting,
+  theme,
+  visuals,
+  highlightColors,
+  opacity,
+  linksByNodeId,
+  coloring,
+  cluster,
+}: {
+  sourceId: string
+  targetId: string
+  needsHighlighting: boolean
+  theme: any
+  visuals: typeof initialVisuals
+  highlightColors: Record<string, any>
+  opacity: number
+  linksByNodeId: LinksByNodeId
+  coloring: typeof initialColoring
+  cluster: any
+}) => {
+  if (!visuals.linkHighlight && !visuals.linkColorScheme && !needsHighlighting) {
+    const nodeColor = getLinkNodeColor({
+      sourceId,
+      targetId,
+      linksByNodeId,
+      visuals,
+      coloring,
+      cluster,
+    })
+    return getThemeColor(nodeColor, theme)
+  }
+
+  if (!needsHighlighting && !visuals.linkColorScheme) {
+    const nodeColor = getLinkNodeColor({
+      sourceId,
+      targetId,
+      linksByNodeId,
+      visuals,
+      coloring,
+      cluster,
+    })
+    return highlightColors[nodeColor][visuals.backgroundColor](visuals.highlightFade * opacity)
+  }
+
+  if (!needsHighlighting) {
+    return highlightColors[visuals.linkColorScheme][visuals.backgroundColor](
+      visuals.highlightFade * opacity,
+    )
+  }
+
+  if (!visuals.linkHighlight && !visuals.linkColorScheme) {
+    const nodeColor = getLinkNodeColor({
+      sourceId,
+      targetId,
+      linksByNodeId,
+      visuals,
+      coloring,
+      cluster,
+    })
+    return getThemeColor(nodeColor, theme)
+  }
+
+  if (!visuals.linkHighlight) {
+    return getThemeColor(visuals.linkColorScheme, theme)
+  }
+
+  if (!visuals.linkColorScheme) {
+    return highlightColors[
+      getLinkNodeColor({ sourceId, targetId, linksByNodeId, visuals, coloring, cluster })
+    ][visuals.linkHighlight](opacity)
+  }
+
+  return highlightColors[visuals.linkColorScheme][visuals.linkHighlight](opacity)
+}
diff --git a/util/getLinkNodeColor.ts b/util/getLinkNodeColor.ts
new file mode 100644
index 0000000..86cfa4b
--- /dev/null
+++ b/util/getLinkNodeColor.ts
@@ -0,0 +1,23 @@
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { getNodeColorById } from './getNodeColorById'
+
+export const getLinkNodeColor = ({
+  sourceId,
+  targetId,
+  linksByNodeId,
+  visuals,
+  coloring,
+  cluster,
+}: {
+  sourceId: string
+  targetId: string
+  linksByNodeId: LinksByNodeId
+  visuals: typeof initialVisuals
+  coloring: typeof initialColoring
+  cluster: any
+}) => {
+  return linksByNodeId[sourceId]!.length > linksByNodeId[targetId]!.length
+    ? getNodeColorById({ id: sourceId, linksByNodeId, visuals, cluster, coloring })
+    : getNodeColorById({ id: targetId, visuals, linksByNodeId, cluster, coloring })
+}
diff --git a/util/getNodeColor.ts b/util/getNodeColor.ts
new file mode 100644
index 0000000..f25a15b
--- /dev/null
+++ b/util/getNodeColor.ts
@@ -0,0 +1,75 @@
+import { OrgRoamNode } from '../api'
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { getNodeColorById } from './getNodeColorById'
+import { getThemeColor } from './getThemeColor'
+
+export const getNodeColor = ({
+  node,
+  theme,
+  highlightedNodes,
+  previouslyHighlightedNodes,
+  visuals,
+  tagColors,
+  highlightColors,
+  opacity,
+  emacsNodeId,
+  linksByNodeId,
+  cluster,
+  coloring,
+}: {
+  node: OrgRoamNode
+  theme: any
+  visuals: typeof initialVisuals
+  highlightedNodes: Record<string, any>
+  previouslyHighlightedNodes: Record<string, any>
+  tagColors: Record<string, any>
+  highlightColors: Record<string, any>
+  opacity: number
+  emacsNodeId: string | null
+  linksByNodeId: LinksByNodeId
+  cluster: any
+  coloring: typeof initialColoring
+}) => {
+  const needsHighlighting = highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
+  //const needsHighlighting = hoverNode?.id === node.id! || lastHoverNode?.current?.id === 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
+  if (visuals.emacsNodeColor && node.id === emacsNodeId) {
+    return getThemeColor(visuals.emacsNodeColor, theme)
+  }
+  if (tagColors && node?.tags.some((tag) => tagColors[tag])) {
+    const tagColor = tagColors[node?.tags.filter((tag) => tagColors[tag])[0]]
+    return needsHighlighting
+      ? highlightColors[tagColor][tagColor](visuals.highlightFade * opacity)
+      : highlightColors[tagColor][visuals.backgroundColor](visuals.highlightFade * opacity)
+  }
+  if (visuals.citeNodeColor && node?.properties?.ROAM_REFS && node?.properties?.FILELESS) {
+    return needsHighlighting
+      ? getThemeColor(visuals.citeNodeColor, theme)
+      : highlightColors[visuals.citeNodeColor][visuals.backgroundColor](
+          visuals.highlightFade * opacity,
+        )
+  }
+  if (visuals.refNodeColor && node.properties.ROAM_REFS) {
+    return needsHighlighting
+      ? getThemeColor(visuals.refNodeColor, theme)
+      : highlightColors[visuals.refNodeColor][visuals.backgroundColor](
+          visuals.highlightFade * opacity,
+        )
+  }
+  if (!needsHighlighting) {
+    return highlightColors[
+      getNodeColorById({ id: node.id as string, cluster, coloring, linksByNodeId, visuals })
+    ][visuals.backgroundColor](visuals.highlightFade * opacity)
+  }
+  if (!visuals.nodeHighlight) {
+    return getThemeColor(
+      getNodeColorById({ id: node.id as string, cluster, coloring, linksByNodeId, visuals }),
+      theme,
+    )
+  }
+  return highlightColors[
+    getNodeColorById({ id: node.id as string, cluster, coloring, linksByNodeId, visuals })
+  ][visuals.nodeHighlight](opacity)
+}
diff --git a/util/getNodeColorById.ts b/util/getNodeColorById.ts
new file mode 100644
index 0000000..d2c198b
--- /dev/null
+++ b/util/getNodeColorById.ts
@@ -0,0 +1,25 @@
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { numberWithinRange } from './numberWithinRange'
+
+export const getNodeColorById = ({
+  id,
+  linksByNodeId,
+  visuals,
+  coloring,
+  cluster,
+}: {
+  id: string
+  linksByNodeId: LinksByNodeId
+  visuals: typeof initialVisuals
+  cluster: any
+  coloring: typeof initialColoring
+}) => {
+  const linklen = linksByNodeId[id!]?.length ?? 0
+  if (coloring.method === 'degree') {
+    return visuals.nodeColorScheme[
+      numberWithinRange(linklen, 0, visuals.nodeColorScheme.length - 1)
+    ]
+  }
+  return visuals.nodeColorScheme[linklen && cluster[id] % visuals.nodeColorScheme.length]
+}
diff --git a/util/getThemeColor.ts b/util/getThemeColor.ts
new file mode 100644
index 0000000..1175a52
--- /dev/null
+++ b/util/getThemeColor.ts
@@ -0,0 +1,3 @@
+export const getThemeColor = (name: string, theme: any) => {
+  return name.split('.').reduce((o, i) => o[i], theme.colors)
+}
diff --git a/util/hexToRGBA.ts b/util/hexToRGBA.ts
new file mode 100644
index 0000000..bacb601
--- /dev/null
+++ b/util/hexToRGBA.ts
@@ -0,0 +1,11 @@
+export function hexToRGBA(hex: string, opacity: number) {
+  return (
+    'rgba(' +
+    (hex = hex.replace('#', ''))
+      .match(new RegExp('(.{' + hex.length / 3 + '})', 'g'))!
+      .map((l) => parseInt(hex.length % 2 ? l + l : l, 16))
+      .concat(isFinite(opacity) ? opacity : 1)
+      .join(',') +
+    ')'
+  )
+}
diff --git a/util/isLinkRelatedToNode.ts b/util/isLinkRelatedToNode.ts
new file mode 100644
index 0000000..eeacab7
--- /dev/null
+++ b/util/isLinkRelatedToNode.ts
@@ -0,0 +1,7 @@
+import { NodeObject, LinkObject } from 'force-graph'
+
+export const isLinkRelatedToNode = (link: LinkObject, node: NodeObject | null) => {
+  return (
+    (link.source as NodeObject)?.id! === node?.id! || (link.target as NodeObject)?.id! === node?.id!
+  )
+}
diff --git a/util/nodeSize.ts b/util/nodeSize.ts
new file mode 100644
index 0000000..3601c1e
--- /dev/null
+++ b/util/nodeSize.ts
@@ -0,0 +1,34 @@
+import { filter } from '@chakra-ui/react'
+import { initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { NodeObject } from 'force-graph'
+
+export const nodeSize = ({
+  linksByNodeId,
+  visuals,
+  highlightedNodes,
+  previouslyHighlightedNodes,
+  opacity,
+  node,
+}: {
+  node: NodeObject
+  visuals: typeof initialVisuals
+
+  highlightedNodes: Record<string, any>
+  previouslyHighlightedNodes: Record<string, any>
+  opacity: number
+  linksByNodeId: LinksByNodeId
+}) => {
+  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.parent ? 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
+}
diff --git a/util/normalizeLinkEnds.ts b/util/normalizeLinkEnds.ts
new file mode 100644
index 0000000..43eee9c
--- /dev/null
+++ b/util/normalizeLinkEnds.ts
@@ -0,0 +1,12 @@
+import { OrgRoamLink } from '../api'
+import { LinkObject } from 'force-graph'
+
+export function normalizeLinkEnds(link: OrgRoamLink | LinkObject): [string, string] {
+  // 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 =
+    typeof link.source === 'object' ? (link.source.id! as string) : (link.source as string)
+  const targetId =
+    typeof link.target === 'object' ? (link.target.id! as string) : (link.target as string)
+  return [sourceId, targetId]
+}
diff --git a/util/numberWithinRange.ts b/util/numberWithinRange.ts
new file mode 100644
index 0000000..ae435ce
--- /dev/null
+++ b/util/numberWithinRange.ts
@@ -0,0 +1,3 @@
+export const numberWithinRange = (num: number, min: number, max: number) => {
+  return Math.min(Math.max(num, min), max)
+}
-- 
cgit v1.2.3


From c4a9897fa15e6c576d26849f54daba4a4bc665a6 Mon Sep 17 00:00:00 2001
From: "Thomas F. K. Jorna" <hello@tefkah.com>
Date: Tue, 27 Sep 2022 16:13:44 +0200
Subject: feat: add/remove note to local graph from emacs

---
 org-roam-ui.el  | 24 ++++++++++++++++++++++++
 pages/index.tsx | 21 +++++++++++++++------
 2 files changed, 39 insertions(+), 6 deletions(-)

(limited to 'pages/index.tsx')

diff --git a/org-roam-ui.el b/org-roam-ui.el
index ce2eeec..e13349b 100644
--- a/org-roam-ui.el
+++ b/org-roam-ui.el
@@ -690,6 +690,30 @@ Optionally with ID (string), SPEED (number, ms) and PADDING (number, px)."
                                                    (padding . ,padding))))))
     (message "No node found.")))
 
+
+(defun org-roam-ui-change-local-graph (&optional id manipulation)
+  "Add or remove current node to the local graph. If not in local mode, open local-graph for this node."  
+  (interactive)
+  (if-let ((node (or id (org-roam-id-at-point))))
+      (websocket-send-text org-roam-ui-ws-socket
+                           (json-encode `((type . "command")
+                                          (data . ((commandName . "change-local-graph")
+                                                   (id . ,node)
+                                                   (manipulation . ,(or manipulation "add")))))))
+    (message "No node found.")))
+
+;;;###autoload
+(defun org-roam-ui-add-to-local-graph (&optional id)
+  "Add current node to the local graph. If not in local mode, open local-graph for this node."
+  (interactive)
+  (org-roam-ui-change-local-graph id "add"))
+
+;;;###autoload
+(defun org-roam-ui-remove-from-local-graph (&optional id)
+  "Remove current node from the local graph. If not in local mode, open local-graph for this node."
+  (interactive)
+  (org-roam-ui-change-local-graph id "remove"))
+
 ;;;###autoload
 (defun org-roam-ui-sync-theme ()
   "Sync your current Emacs theme with org-roam-ui."
diff --git a/pages/index.tsx b/pages/index.tsx
index ddd3bb7..75b33d3 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -466,7 +466,10 @@ export function GraphPage() {
               setEmacsNodeId(message.data.id)
               break
             }
-            case 'add-node-local': {
+            case 'change-local-graph': {
+              console.log(message)
+              handleLocal(nodeByIdRef.current[message.data.id as string], message.data.manipulation)
+              break
             }
             default:
               return console.error('unknown message type', message.type)
@@ -525,7 +528,7 @@ export function GraphPage() {
   const handleLocal = (node: OrgRoamNode, command: string) => {
     if (command === 'remove') {
       setScope((currentScope: Scope) => ({
-        ...currentScope,
+        nodeIds: currentScope.nodeIds.filter((id: string) => id !== node.id),
         excludedNodeIds: [...currentScope.excludedNodeIds, node.id as string],
       }))
       return
@@ -538,7 +541,7 @@ export function GraphPage() {
       return
     }
     setScope((currentScope: Scope) => ({
-      ...currentScope,
+      excludedNodeIds: currentScope.excludedNodeIds.filter((id: string) => id !== node.id),
       nodeIds: [...currentScope.nodeIds, node.id as string],
     }))
     return
@@ -974,7 +977,11 @@ export const Graph = function (props: GraphProps) {
           const links = filteredLinksByNodeIdRef.current[node.id as string] ?? []
           return links.some((link) => {
             const [source, target] = normalizeLinkEnds(link)
-            return scope.nodeIds.includes(source) || scope.nodeIds.includes(target)
+            return (
+              !scope.excludedNodeIds.includes(source) &&
+              !scope.excludedNodeIds.includes(target) &&
+              (scope.nodeIds.includes(source) || scope.nodeIds.includes(target))
+            )
           })
         }
         return neighbs.includes(node.id as string)
@@ -1018,8 +1025,10 @@ export const Graph = function (props: GraphProps) {
   }, [
     local.neighbors,
     filter,
-    JSON.stringify(scope),
-    JSON.stringify(graphData),
+    scope,
+    scope.excludedNodeIds,
+    scope.nodeIds,
+    graphData,
     filteredGraphData.links,
     filteredGraphData.nodes,
   ])
-- 
cgit v1.2.3


From 1936250b99b8747d841edd83002ed20ec12aa793 Mon Sep 17 00:00:00 2001
From: "Thomas F. K. Jorna" <hello@tefkah.com>
Date: Tue, 27 Sep 2022 16:18:11 +0200
Subject: fix: type-error

---
 pages/index.tsx | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

(limited to 'pages/index.tsx')

diff --git a/pages/index.tsx b/pages/index.tsx
index 75b33d3..8770283 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -467,8 +467,10 @@ export function GraphPage() {
               break
             }
             case 'change-local-graph': {
+              const node = nodeByIdRef.current[message.data.id as string]
+              if (!node) break
               console.log(message)
-              handleLocal(nodeByIdRef.current[message.data.id as string], message.data.manipulation)
+              handleLocal(node, message.data.manipulation)
               break
             }
             default:
-- 
cgit v1.2.3

