summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/config.ts5
-rw-r--r--org-roam-ui.el65
-rw-r--r--package.json1
-rw-r--r--pages/index.tsx119
-rw-r--r--yarn.lock5
5 files changed, 150 insertions, 45 deletions
diff --git a/components/config.ts b/components/config.ts
index 2c7a1b6..9c49068 100644
--- a/components/config.ts
+++ b/components/config.ts
@@ -90,3 +90,8 @@ export const initialVisuals = {
linkHighlight: '',
backgroundColor: 'white',
}
+
+export const initialBehavior = {
+ follow: 'Zoom',
+ followLocalOrZoom: true,
+}
diff --git a/org-roam-ui.el b/org-roam-ui.el
index 8a3a5b3..877a4d6 100644
--- a/org-roam-ui.el
+++ b/org-roam-ui.el
@@ -86,6 +86,8 @@ E.g. '((bg . '#1E2029')
:type 'list)
(defvar org-roam-ui--ws-current-node nil)
+(defvar oru-ws nil
+ "The websocket for org-roam-ui.")
;;;###autoload
(define-minor-mode
@@ -105,22 +107,24 @@ This serves the web-build and API over HTTP."
(websocket-server
35903
:host 'local
- :on-open (lambda (ws) (progn (setq oru-ws ws) (org-roam-ui--send-graphdata) (org-roam-ui-sync-theme--advice) (message "Connection established with org-roam-ui")))
- :on-close (lambda (_websocket) (setq oru-ws nil) (message "Connection with org-roam-ui closed succesfully."))))
+ :on-open (lambda (ws) (progn (setq oru-ws
+ ws) (org-roam-ui--send-graphdata) (message "Connection established with org-roam-ui")
+ (add-hook 'post-command-hook #'org-roam-ui--update-current-node)))
+ :on-close (lambda (_websocket) (setq oru-ws
+ nil) (message "Connection with org-roam-ui closed succesfully."))))
(if (boundp 'counsel-load-theme)
(advice-add 'counsel-load-theme :after #'org-roam-ui-sync-theme--advice)
- (advice-add 'load-theme :after #'org-roam-ui-sync-theme-manually))
- (add-hook 'post-command-hook #'org-roam-ui--update-current-node)
- (add-hook 'post-command-hook #'org-roam-ui-update))
+ (advice-add 'load-theme :around #'org-roam-ui-sync-theme--advice))
+ (add-hook 'post-command-hook #'org-roam-ui--update-current-node))
+ ;(add-hook 'post-command-hook #'org-roam-ui-update))
(t
(progn
+ (websocket-server-close org-roam-ui-ws)
(remove-hook 'post-command-hook #'org-roam-ui-update)
(remove-hook 'post-command-hook #'org-roam-ui--update-current-node)
(if (boundp 'counsel-load-theme)
(advice-remove 'counsel-load-theme #'org-roam-ui-sync-theme--advice)
(advice-remove 'load-theme #'org-roam-ui-sync-theme--advice))
- (websocket-server-close org-roam-ui-ws)
- (delete-process org-roam-ui-ws)
(httpd-stop)))))
@@ -135,11 +139,13 @@ This serves the web-build and API over HTTP."
(websocket-send-text oru-ws (json-encode `((type . "graphdata") (data . ,response))))))
(defun org-roam-ui--update-current-node ()
+ (when (websocket-openp oru-ws)
(let* ((node (org-roam-id-at-point)))
(unless (string-match-p (regexp-quote "Minibuf") (buffer-name (current-buffer)))
(unless (string= org-roam-ui--ws-current-node node)
(setq org-roam-ui--ws-current-node node)
- (websocket-send-text oru-ws (json-encode `((type . "command") (data . ((commandName . "follow") (id . ,node))))))))))
+ (websocket-send-text oru-ws (json-encode `((type . "command") (data
+. ((commandName . "follow") (id . ,node)))))))))))
(defun org-roam-ui-show-node ()
"Open the current org-roam node in org-roam-ui."
@@ -148,6 +154,7 @@ This serves the web-build and API over HTTP."
(defun org-roam-ui-sync-theme--advice ()
"Function which is called after load-theme to sync your current theme with org-roam-ui."
+ (message "Syncing theme")
(websocket-send-text oru-ws (json-encode `((type . "theme") (data . ,(org-roam-ui--update-theme))))))
(defun org-roam-ui-sync-theme-manually ()
@@ -245,19 +252,35 @@ This function is added to `post-command-hook'."
))
-;; (defservlet* theme text/stream ()
-;; (progn)
-;; (if org-roam-ui-sync-theme
-;; (if (boundp 'doom-themes--colors)
-;; (let*
-;; ((colors (butlast doom-themes--colors (- (length doom-themes--colors) 25))) ui-theme (list nil))
-;; (progn
-;; (dolist (color colors) (push (cons (car color) (car (cdr color))) ui-theme))
-;; ui-theme))
-;; (insert (format "data: %s\n\n" (json-encode (org-roam-ui-get-theme)))))
-;; (when org-roam-ui-custom-theme
-;; (insert (format "data %s\n\n" (json-encode org-roam-ui-custom-theme)))))
-;; (httpd-send-header t "text/event-stream" 200 :Access-Control-Allow-Origin "*"))
+;;;; commands
+(defun orui-zoom-to-node (&optional id speed padding)
+ "Move the view of the graph to the node at points, or optionally a node of your choosing.
+Optionally takes three arguments:
+The id of the node you want to travel to.
+The time in ms it takes to make the transition.
+The padding around the nodes in the viewport."
+ (interactive)
+ (if-let ((node (or id (org-roam-id-at-point))))
+ (websocket-send-text oru-ws (json-encode `((type . "command") (data .
+ ((commandName . "zoom") (id . ,node) (speed . ,speed) (padding . ,padding)))))))
+ (message "No node found."))
+
+(defun orui-toggle-following ()
+ "Set whether ORUI should follow your every move in emacs. Default yes."
+ (interactive)
+ (if (member #'org-roam-ui--update-current-node post-command-hook)
+ (progn
+ (remove-hook 'post-command-hook #'org-roam-ui--update-current-node)
+ (message "Org-Roam-UI will now leave you alone.")
+ (add-hook 'post-command-hook #'org-roam-ui--update-current-node)
+ (message "Org-Roam-UI will now follow you around."))
+ ))
+
+(defun orui-toggle-local-zoom ()
+ "Toggles whether org-roam-ui should go to the local view of a given node or zoom to it.
+Defaults to local."
+ (interactive)
+ (org-roam-ui--send-command "toggle" `(id . yes)))
(provide 'org-roam-ui)
;;; org-roam-ui.el ends here
diff --git a/package.json b/package.json
index 55b4c5e..7862a9b 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"react-dom": "17.0.2",
"react-force-graph": "^1.41.7",
"react-spring": "^9.2.4",
+ "reconnecting-websocket": "^4.4.0",
"three-spritetext": "^1.6.2",
"use-constant": "^1.1.0"
},
diff --git a/pages/index.tsx b/pages/index.tsx
index e0e8b6e..3540a4c 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -22,10 +22,19 @@ import { useAnimation } from '@lilib/hooks'
import { Box, useTheme } from '@chakra-ui/react'
-import { initialPhysics, initialFilter, initialVisuals } from '../components/config'
+import {
+ initialPhysics,
+ initialFilter,
+ initialVisuals,
+ initialBehavior,
+} from '../components/config'
import { Tweaks } from '../components/tweaks'
import { ThemeContext, ThemeContextProps } from './themecontext'
+import SpriteText from 'three-spritetext'
+
+import ReconnectingWebSocket from 'reconnecting-websocket'
+
// react-force-graph fails on import when server-rendered
// https://github.com/vasturiano/react-force-graph/issues/155
const ForceGraph2D = (
@@ -62,6 +71,7 @@ export function GraphPage() {
const [visuals, setVisuals] = usePersistantState('visuals', initialVisuals)
const [graphData, setGraphData] = useState<GraphData | null>(null)
const [emacsNodeId, setEmacsNodeId] = useState<string | null>(null)
+ const [behavior, setBehavior] = usePersistantState('behavior', initialBehavior)
const nodeByIdRef = useRef<NodeById>({})
const linksByNodeIdRef = useRef<LinksByNodeId>({})
@@ -113,10 +123,23 @@ export function GraphPage() {
const orgRoamGraphDataClone = JSON.parse(JSON.stringify(orgRoamGraphDataWithFileLinks))
setGraphData(orgRoamGraphDataClone)
}
+
const { setEmacsTheme } = useContext(ThemeContext)
+
+ const [threeDim, setThreeDim] = useState(false)
+
+ const graph2dRef = useRef<any>(null)
+ const graph3dRef = useRef<any>(null)
+
useEffect(() => {
+<<<<<<< HEAD
+ const fg = threeDim ? graph3dRef.current : graph2dRef.current
+ const socket = new ReconnectingWebSocket('ws://localhost:35903')
+ socket.addEventListener('open', (event) => {
+=======
const socket = new WebSocket('ws://localhost:35903')
socket.addEventListener('open', () => {
+>>>>>>> 4e3d884c402b7dc7d12f0cae88a9a312b10f166f
console.log('Connection with Emacs established')
})
socket.addEventListener('message', (event) => {
@@ -131,14 +154,18 @@ export function GraphPage() {
case 'follow':
return setEmacsNodeId(message.data.id)
case 'zoom': {
+ console.log(message)
const links = linksByNodeIdRef.current[message.data.id!] ?? []
const nodes = Object.fromEntries(
[
- message.commandData.id! as string,
+ message.data.id! as string,
...links.flatMap((link) => [link.source, link.target]),
].map((nodeId) => [nodeId, {}]),
)
- /* zoomToFit(500, 200, (node: OrgRoamNode)=>nodes[node.id!]) */
+ fg.zoomToFit(2000, 200, (node: OrgRoamNode) => nodes[node.id!])
+ }
+ case 'toggle': {
+ /* setBehavior({ ...behavior, followLocalorZoom: !behavior.followLocalOrZoom }) */
}
default:
return console.error('unknown message type', message.type)
@@ -147,7 +174,16 @@ export function GraphPage() {
})
}, [])
+<<<<<<< HEAD
+ useEffect(() => {
+ if (!emacsNodeId) {
+ return
+ }
+ //fetchGraphData()
+ }, [emacsNodeId])
+=======
const [threeDim, setThreeDim] = useState(false)
+>>>>>>> 4e3d884c402b7dc7d12f0cae88a9a312b10f166f
if (!graphData) {
return null
@@ -178,6 +214,9 @@ export function GraphPage() {
emacsNodeId,
filter,
visuals,
+ behavior,
+ graph2dRef,
+ graph3dRef,
}}
/>
</Box>
@@ -194,14 +233,25 @@ export interface GraphProps {
filter: typeof initialFilter
emacsNodeId: string | null
visuals: typeof initialVisuals
+ behavior: typeof initialBehavior
+ graph2dRef: any
+ graph3dRef: any
}
export const Graph = function (props: GraphProps) {
- const { physics, graphData, threeDim, linksByNodeId, filter, emacsNodeId, nodeById, visuals } =
- props
-
- const graph2dRef = useRef<any>(null)
- const graph3dRef = useRef<any>(null)
+ const {
+ physics,
+ graphData,
+ threeDim,
+ linksByNodeId,
+ filter,
+ emacsNodeId,
+ nodeById,
+ visuals,
+ behavior,
+ graph2dRef,
+ graph3dRef,
+ } = props
// react-force-graph does not track window size
// https://github.com/vasturiano/react-force-graph/issues/233
@@ -215,33 +265,43 @@ export const Graph = function (props: GraphProps) {
if (!emacsNodeId) {
return
}
- switch (physics.follow) {
- case 'Local':
- setScope({ nodeIds: [emacsNodeId] })
- break
- case 'Zoom':
- default:
+ const fg = threeDim ? graph3dRef.current : graph2dRef.current
+ if (behavior.followLocalOrZoom) {
+ setScope({ nodeIds: [emacsNodeId] })
+ } else {
+ fg?.zoomToFit(1000, 200, (node: NodeObject) => getNeighborNodes(emacsNodeId)[node.id!])
+ setHoverNode(nodeById[emacsNodeId] as NodeObject)
}
}, [emacsNodeId])
- const centralHighlightedNode = hoverNode
+ const getNeighborNodes = (id: string) => {
+ const links = linksByNodeId[id]! ?? []
+ return Object.fromEntries(
+ [id as string, ...links.flatMap((link) => [link.source, link.target])].map((nodeId) => [
+ nodeId,
+ {},
+ ]),
+ )
+ }
+ const centralHighlightedNode = useRef<NodeObject | null>(null)
+ centralHighlightedNode.current = hoverNode
const highlightedNodes = useMemo(() => {
- if (!centralHighlightedNode) {
+ if (!centralHighlightedNode.current) {
return {}
}
- const links = linksByNodeId[centralHighlightedNode.id!]
+ const links = linksByNodeId[centralHighlightedNode.current.id!]
if (!links) {
return {}
}
return Object.fromEntries(
[
- centralHighlightedNode.id! as string,
+ centralHighlightedNode.current.id! as string,
...links.flatMap((link) => [link.source, link.target]),
].map((nodeId) => [nodeId, {}]),
)
- }, [centralHighlightedNode, linksByNodeId])
+ }, [centralHighlightedNode.current, linksByNodeId])
const filteredNodes = useMemo(() => {
return graphData.nodes.filter((node) => {
@@ -590,13 +650,13 @@ export const Graph = function (props: GraphProps) {
linkColor: (link) => {
const sourceId = typeof link.source === 'object' ? link.source.id! : (link.source as string)
const targetId = typeof link.target === 'object' ? link.target.id! : (link.target as string)
- const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode)
+ const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode.current)
const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current)
const needsHighlighting = linkIsHighlighted || linkWasHighlighted
return getLinkColor(sourceId as string, targetId as string, needsHighlighting)
},
linkWidth: (link) => {
- const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode)
+ const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode.current)
const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current)
return linkIsHighlighted || linkWasHighlighted
@@ -647,6 +707,18 @@ export const Graph = function (props: GraphProps) {
nodeOpacity={physics.nodeOpacity}
nodeResolution={physics.nodeResolution}
linkOpacity={physics.linkOpacity}
+ nodeThreeObject={(node: OrgRoamNode) => {
+ if (!physics.labels) {
+ return
+ }
+ if (physics.labels === 1 && !highlightedNodes[node.id!]) {
+ return
+ }
+ const sprite = new SpriteText(node.title.substring(0, 30))
+ sprite.color = '#ffffff'
+ sprite.textHeight = 8
+ return sprite
+ }}
/>
) : (
<ForceGraph2D ref={graph2dRef} {...graphCommonProps} />
@@ -655,10 +727,9 @@ export const Graph = function (props: GraphProps) {
)
}
-function isLinkRelatedToNode(link: LinkObject, centralHighlightedNode: NodeObject | null) {
+function isLinkRelatedToNode(link: LinkObject, node: NodeObject | null) {
return (
- (link.source as NodeObject).id! === centralHighlightedNode?.id! ||
- (link.target as NodeObject).id! === centralHighlightedNode?.id!
+ (link.source as NodeObject).id! === node?.id! || (link.target as NodeObject).id! === node?.id!
)
}
diff --git a/yarn.lock b/yarn.lock
index 1e586f6..85dfee6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4299,6 +4299,11 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
+reconnecting-websocket@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
+ integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
+
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz"