summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--org-roam-ui.el35
-rw-r--r--pages/index.tsx132
2 files changed, 118 insertions, 49 deletions
diff --git a/org-roam-ui.el b/org-roam-ui.el
index fe17676..7a84d2f 100644
--- a/org-roam-ui.el
+++ b/org-roam-ui.el
@@ -36,6 +36,7 @@
(require 'json)
(require 'simple-httpd)
(require 'org-roam)
+(require 'websocket)
(defgroup org-roam-ui nil
"UI in Org-roam."
@@ -84,6 +85,8 @@ E.g. '((bg . '#1E2029')
:group 'org-roam-ui
:type 'list)
+(defvar org-roam-ui--ws-current-node nil)
+
;;;###autoload
(define-minor-mode
org-roam-ui-mode
@@ -98,11 +101,41 @@ This serves the web-build and API over HTTP."
(setq httpd-port org-roam-ui-port
httpd-root org-roam-ui/app-build-dir)
(httpd-start)
+ (setq org-roam-ui-ws
+ (websocket-server
+ 35903
+ :host 'local
+ :on-open (lambda (ws) (progn (setq oru-ws ws) (org-roam-ui--send-graphdata) (message "Connection established with org-roam-ui")))
+ :on-close (lambda (_websocket) (setq oru-ws nil) (message "Connection with org-roam-ui closed succesfully."))))
+ (add-hook 'post-command-hook #'org-roam-ui--update-current-node)
(add-hook 'post-command-hook #'org-roam-ui-update))
(t
+ (progn
(remove-hook 'post-command-hook #'org-roam-ui-update)
- (httpd-stop))))
+ (remove-hook 'post-command-hook #'org-roam-ui--update-current-node)
+ (websocket-server-close org-roam-ui-ws)
+ (delete-process org-roam-ui-ws)
+ (httpd-stop)))))
+
+(defun org-roam-ui--send-graphdata ()
+ (let* ((nodes-columns [id file title level])
+ (links-columns [source dest type])
+ (nodes-db-rows (org-roam-db-query `[:select ,nodes-columns :from nodes]))
+ (links-db-rows (org-roam-db-query `[:select ,links-columns :from links :where (or (= type "id") (= type "cite"))]))
+ (response `((nodes . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist (append nodes-columns nil)) nodes-db-rows))
+ (links . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist '(source target type)) links-db-rows)))))
+ (websocket-send-text oru-ws (json-encode `((type . "graphdata") (data . ,response))))))
+
+(defun org-roam-ui--update-current-node ()
+ (let* ((node (org-roam-id-at-point)))
+ (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)))))))))
+
+(defun org-roam-ui-show-node ()
+ (interactive)
+ (websocket-send-text oru-ws (json-encode `((type . "command") (data . ((commandName . "follow") (id . ,(org-roam-id-at-point))))))))
(defservlet* graph application/json ()
(let* ((nodes-columns [id file title level])
diff --git a/pages/index.tsx b/pages/index.tsx
index 7ac12cd..d197ae5 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -61,71 +61,107 @@ export function GraphPage() {
const fetchGraphData = () => {
return fetch('http://localhost:35901/graph')
.then((res) => res.json())
- .then((orgRoamGraphData: OrgRoamGraphReponse) => {
- const nodesByFile = orgRoamGraphData.nodes.reduce<NodesByFile>((acc, node) => {
- return {
- ...acc,
- [node.file]: [...(acc[node.file] ?? []), node],
- }
- }, {})
+ .then((orgRoamGraphData) => {
+ parseGraphData(orgRoamGraphData)
+ })
+ }
- const fileLinks: OrgRoamLink[] = Object.keys(nodesByFile).flatMap((file) => {
- const nodesInFile = nodesByFile[file] ?? []
- // "file node" as opposed to "heading node"
- const fileNode = nodesInFile.find((node) => node.level === 0)
- const headingNodes = nodesInFile.filter((node) => node.level !== 0)
+ const parseGraphData = (orgRoamGraphData: OrgRoamGraphReponse) => {
+ const nodesByFile = orgRoamGraphData.nodes.reduce<NodesByFile>((acc, node) => {
+ return {
+ ...acc,
+ [node.file]: [...(acc[node.file] ?? []), node],
+ }
+ }, {})
- if (!fileNode) {
- return []
- }
+ const fileLinks: OrgRoamLink[] = Object.keys(nodesByFile).flatMap((file) => {
+ const nodesInFile = nodesByFile[file] ?? []
+ // "file node" as opposed to "heading node"
+ const fileNode = nodesInFile.find((node) => node.level === 0)
+ const headingNodes = nodesInFile.filter((node) => node.level !== 0)
- return headingNodes.map((headingNode) => ({
- source: headingNode.id,
- target: fileNode.id,
- type: 'parent',
- }))
- })
+ if (!fileNode) {
+ return []
+ }
- nodeByIdRef.current = Object.fromEntries(
- orgRoamGraphData.nodes.map((node) => [node.id, node]),
- )
+ return headingNodes.map((headingNode) => ({
+ source: headingNode.id,
+ target: fileNode.id,
+ type: 'parent',
+ }))
+ })
- const links = [...orgRoamGraphData.links, ...fileLinks]
- linksByNodeIdRef.current = links.reduce<LinksByNodeId>((acc, link) => {
- return {
- ...acc,
- [link.source]: [...(acc[link.source] ?? []), link],
- [link.target]: [...(acc[link.target] ?? []), link],
- }
- }, {})
+ nodeByIdRef.current = Object.fromEntries(orgRoamGraphData.nodes.map((node) => [node.id, node]))
- const orgRoamGraphDataWithFileLinks = {
- ...orgRoamGraphData,
- links,
- }
+ const links = [...orgRoamGraphData.links, ...fileLinks]
+ linksByNodeIdRef.current = links.reduce<LinksByNodeId>((acc, link) => {
+ return {
+ ...acc,
+ [link.source]: [...(acc[link.source] ?? []), link],
+ [link.target]: [...(acc[link.target] ?? []), link],
+ }
+ }, {})
- // 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)
- })
+ const orgRoamGraphDataWithFileLinks = {
+ ...orgRoamGraphData,
+ 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)
}
useEffect(() => {
- const trackEmacs = new EventSource('http://127.0.0.1:35901/current-node-id')
- trackEmacs.addEventListener('message', (e) => {
- const emacsNodeId = e.data
- setEmacsNodeId(emacsNodeId)
+ //const trackEmacs = new EventSource('http://127.0.0.1:35901/current-node-id')
+ //trackEmacs.addEventListener('message', (e) => {
+ // const emacsNodeId = e.data
+ //setEmacsNodeId(emacsNodeId)
+ //})
+ const socket = new WebSocket('ws://localhost:35903')
+ socket.addEventListener('open', (e) => {
+ console.log('Connection with Emacs established')
+ })
+ socket.addEventListener('message', (event) => {
+ const data = JSON.parse(event.data)
+ console.log(typeof data.type)
+ switch (data.type) {
+ case 'graphdata':
+ console.log('hey')
+ parseGraphData(data.data)
+ break
+ case 'command':
+ console.log('command')
+ switch (data.data.commandName) {
+ case 'follow':
+ setEmacsNodeId(data.data.id)
+ break
+ case 'zoom': {
+ const links = linksByNodeIdRef.current[data.data.id!] ?? []
+ const nodes = Object.fromEntries(
+ [
+ data.commandData.id! as string,
+ ...links.flatMap((link) => [link.source, link.target]),
+ ].map((nodeId) => [nodeId, {}]),
+ )
+ /* zoomToFit(500, 200, (node: OrgRoamNode)=>nodes[node.id!]) */
+ console.log(nodes)
+ }
+ default:
+ console.log('oopsie whoopsie')
+ }
+ }
})
- fetchGraphData()
+ // fetchGraphData()
}, [])
useEffect(() => {
if (!emacsNodeId) {
return
}
- fetchGraphData()
+ //fetchGraphData()
}, [emacsNodeId])
const [threeDim, setThreeDim] = useState(false)