summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas F. K. Jorna <[email protected]>2021-07-31 15:02:46 +0200
committerThomas F. K. Jorna <[email protected]>2021-07-31 15:02:46 +0200
commit144c2f66c54196bcc06e9096818317fc222ea009 (patch)
tree64415d7557b5c16c7650d13ae60b002a420aa1c8
parent1df1eaf3672694a0ba267ad3006918eaea1e5951 (diff)
tried to add expanding follow, and failed
-rw-r--r--components/config.ts6
-rw-r--r--components/tweaks.tsx84
-rw-r--r--org-roam-ui.el102
-rw-r--r--pages/index.tsx119
4 files changed, 228 insertions, 83 deletions
diff --git a/components/config.ts b/components/config.ts
index c2f8ff5..cc2beb8 100644
--- a/components/config.ts
+++ b/components/config.ts
@@ -80,8 +80,10 @@ export const initialVisuals = {
}
export const initialBehavior = {
- follow: 'Zoom',
- followLocalOrZoom: true,
+ follow: 'zoom',
+ localSame: 'add',
+ zoomPadding: 200,
+ zoomSpeed: 2000,
}
export const initialMouse = {
diff --git a/components/tweaks.tsx b/components/tweaks.tsx
index 35ce3e9..3da32cc 100644
--- a/components/tweaks.tsx
+++ b/components/tweaks.tsx
@@ -41,7 +41,13 @@ import {
} from '@chakra-ui/react'
import React, { useState, useContext } from 'react'
import Scrollbars from 'react-custom-scrollbars-2'
-import { initialPhysics, initialFilter, initialVisuals, initialMouse } from './config'
+import {
+ initialPhysics,
+ initialFilter,
+ initialVisuals,
+ initialMouse,
+ initialBehavior,
+} from './config'
import { ThemeContext } from '../pages/themecontext'
@@ -56,6 +62,8 @@ export interface TweakProps {
setVisuals: any
mouse: typeof initialMouse
setMouse: any
+ behavior: typeof initialBehavior
+ setBehavior: any
}
export const Tweaks = (props: TweakProps) => {
@@ -70,6 +78,8 @@ export const Tweaks = (props: TweakProps) => {
setVisuals,
mouse,
setMouse,
+ behavior,
+ setBehavior,
} = props
const [showTweaks, setShowTweaks] = useState(true)
const { highlightColor, setHighlightColor } = useContext(ThemeContext)
@@ -1188,6 +1198,78 @@ export const Tweaks = (props: TweakProps) => {
</Portal>
</Menu>
</Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Follow Emacs by...</Text>
+ <Menu placement="right">
+ <MenuButton
+ as={Button}
+ rightIcon={<ChevronDownIcon />}
+ colorScheme=""
+ color="black"
+ >
+ <Text>{behavior.follow[0].toUpperCase() + behavior.follow.slice(1)}</Text>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList bgColor="gray.200" zIndex="popover">
+ <MenuItem onClick={() => setBehavior({ ...behavior, follow: 'local' })}>
+ Opening the local graph
+ </MenuItem>
+ <MenuItem onClick={() => setBehavior({ ...behavior, follow: 'zoom' })}>
+ Zooming to the current node
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Flex>
+ <Text>Follow local graph</Text>
+ <InfoTooltip infoText="When in local mode and opening a node that already exists in Emacs, should I add that local graph or open the new one?" />
+ </Flex>
+ <Menu placement="right">
+ <MenuButton
+ as={Button}
+ rightIcon={<ChevronDownIcon />}
+ colorScheme=""
+ color="black"
+ >
+ <Text>{behavior.localSame === 'add' ? 'Add' : 'New'}</Text>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList bgColor="gray.200" zIndex="popover">
+ <MenuItem
+ onClick={() => setBehavior({ ...behavior, localSame: 'new' })}
+ >
+ Open that nodes graph
+ </MenuItem>
+ <MenuItem
+ onClick={() => setBehavior({ ...behavior, localSame: 'add' })}
+ >
+ Add node to local graph
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <SliderWithInfo
+ label="Zoom speed"
+ value={behavior.zoomSpeed}
+ min={0}
+ max={4000}
+ step={100}
+ onChange={(value) => setBehavior({ ...behavior, zoomSpeed: value })}
+ />
+ <SliderWithInfo
+ label="Zoom padding"
+ value={behavior.zoomPadding}
+ min={0}
+ max={400}
+ step={1}
+ onChange={(value) => setBehavior({ ...behavior, zoomPadding: value })}
+ infoText="How much to zoom out to accomodate all nodes when changing the view."
+ />
</VStack>
</AccordionPanel>
</AccordionItem>
diff --git a/org-roam-ui.el b/org-roam-ui.el
index 4ab2513..2b86914 100644
--- a/org-roam-ui.el
+++ b/org-roam-ui.el
@@ -119,7 +119,7 @@ This serves the web-build and API over HTTP."
:on-close (lambda (_websocket)
(remove-hook 'post-command-hook #'org-roam-ui--update-current-node)
(remove-hook 'after-save-hook #'org-roam-ui--on-save)
- (message "Connection with org-roam-ui closed succesfully."))))
+ (message "Connection with org-roam-ui closed."))))
(if
(boundp 'counsel-load-theme)
(advice-add 'counsel-load-theme :after #'org-roam-ui-sync-theme--advice)
@@ -133,9 +133,10 @@ This serves the web-build and API over HTTP."
(httpd-stop)))))
(defun org-roam-ui--on-save ()
- "Send graphdata on saving an only org-roam buffer."
+ "Send graphdata on saving an org-roam buffer."
(when (org-roam-buffer-p)
- (org-roam-ui--send-graphdata))
+ (org-roam-ui--send-graphdata)
+ (org-roam-ui))
)
(defun org-roam-ui--send-graphdata ()
@@ -156,20 +157,12 @@ This serves the web-build and API over HTTP."
(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."
- (interactive)
- (websocket-send-text oru-ws (json-encode `((type . "command") (data . ((commandName . "follow") (id . ,(org-roam-id-at-point))))))))
-(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--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 ()
- "Sync your current Emacs theme with org-roam-ui."
- (interactive)
- (websocket-send-text oru-ws (json-encode `((type . "theme") (data . ,(org-roam-ui--update-theme))))))
(defun org-roam-ui--update-theme ()
(let ((ui-theme (list nil)))
@@ -185,15 +178,15 @@ This serves the web-build and API over HTTP."
org-roam-ui-custom-theme))
ui-theme))
-(defservlet* graph application/json ()
- (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 (json-encode `((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))))))
- (insert response)
- (httpd-send-header t "application/json" 200 :Access-Control-Allow-Origin "*")))
+;; (defservlet* graph application/json ()
+;; (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 (json-encode `((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))))))
+;; (insert response)
+;; (httpd-send-header t "application/json" 200 :Access-Control-Allow-Origin "*")))
(defun org-roam-ui-sql-to-alist (column-names rows)
"Convert sql result to alist for json encoding.
@@ -204,31 +197,31 @@ ROWS is the sql result, while COLUMN-NAMES is the columns to use."
res))
-(defservlet* id/:id text/html ()
- (let ((node (org-roam-populate (org-roam-node-create :id id)))
- html-string)
- (org-roam-with-temp-buffer (org-roam-node-file node)
- (progn
- (setq-local org-export-with-toc nil)
- (setq-local org-export-with-broken-links t)
- (setq-local org-export-with-sub-superscripts nil)
- (replace-string "[[id:" "[[./")
- (let* ((file-string (buffer-string))
- (matches (s-match-strings-all "\\[\\[\\(file:\\|\\.\\/\\)\\(.*\\.\\(png\\|jpg\\|jpeg\\|gif\\|svg\\)\\)\\]\\(\\[.*\\]\\)?\\]" file-string)))
- (dolist (match matches)
- (let ((path (elt match 2))
- (link (elt match 0)))
- (unless (file-name-absolute-p path)
- (setq path (concat (file-name-directory (org-roam-node-file-node)) path)))
- (setq path (f-full path))
- (if (file-exists-p path)
- (setq file-string
- (s-replace link (format "[[image:%s]]" path) file-string)))))
- (erase-buffer)
- (insert file-string))
- (setq html-string (org-export-as 'html))))
- (insert html-string)
- (httpd-send-header t "text/html" 200 :Access-Control-Allow-Origin "*")))
+;; (defservlet* id/:id text/html ()
+;; (let ((node (org-roam-populate (org-roam-node-create :id id)))
+;; html-string)
+;; (org-roam-with-temp-buffer (org-roam-node-file node)
+;; (progn
+;; (setq-local org-export-with-toc nil)
+;; (setq-local org-export-with-broken-links t)
+;; (setq-local org-export-with-sub-superscripts nil)
+;; (replace-string "[[id:" "[[./")
+;; (let* ((file-string (buffer-string))
+;; (matches (s-match-strings-all "\\[\\[\\(file:\\|\\.\\/\\)\\(.*\\.\\(png\\|jpg\\|jpeg\\|gif\\|svg\\)\\)\\]\\(\\[.*\\]\\)?\\]" file-string)))
+;; (dolist (match matches)
+;; (let ((path (elt match 2))
+;; (link (elt match 0)))
+;; (unless (file-name-absolute-p path)
+;; (setq path (concat (file-name-directory (org-roam-node-file-node)) path)))
+;; (setq path (f-full path))
+;; (if (file-exists-p path)
+;; (setq file-string
+;; (s-replace link (format "[[image:%s]]" path) file-string)))))
+;; (erase-buffer)
+;; (insert file-string))
+;; (setq html-string (org-export-as 'html))))
+;; (insert html-string)
+;; (httpd-send-header t "text/html" 200 :Access-Control-Allow-Origin "*")))
(defun org-roam-ui-get-theme ()
"Attempt to bring the current theme into a standardized format."
@@ -269,22 +262,31 @@ The padding around the nodes in the viewport."
((commandName . "local") (id . ,node)))))))
(message "No node found."))
+(defvar org-roam-ui--following nil)
(defun orui-toggle-follow ()
"Set whether ORUI should follow your every move in emacs. Default yes."
(interactive)
(if (member 'org-roam-ui--update-current-node (default-value 'post-command-hook))
(progn
(remove-hook 'post-command-hook #'org-roam-ui--update-current-node)
- (message "Org-Roam-UI will now leave you alone."))
+ (message "Org-Roam-UI will now leave you alone.")
+ (setq org-roam-ui--following nil))
(add-hook 'post-command-hook #'org-roam-ui--update-current-node)
+ (setq org-roam-ui--following nil)
(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)))
+(defun orui-sync-theme ()
+ "Sync your current Emacs theme with org-roam-ui."
+ (interactive)
+ (websocket-send-text oru-ws (json-encode `((type . "theme") (data . ,(org-roam-ui--update-theme))))))
+
(provide 'org-roam-ui)
;;; org-roam-ui.el ends here
diff --git a/pages/index.tsx b/pages/index.tsx
index 90d9686..6eb86b1 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -131,15 +131,72 @@ export function GraphPage() {
const { setEmacsTheme } = useContext(ThemeContext)
const [threeDim, setThreeDim] = useState(false)
+ const [scope, setScope] = useState<Scope>({ nodeIds: [] })
const graphRef = useRef<any>(null)
+ const followBehavior = (
+ command: string,
+ emacsNode: string,
+ speed: number = 2000,
+ padding: number = 200,
+ ) => {
+ const fg = graphRef.current
+ const links = linksByNodeIdRef.current[emacsNode] ?? []
+ const nodes = Object.fromEntries(
+ [emacsNode as string, ...links.flatMap((link) => [link.source, link.target])].map(
+ (nodeId) => [nodeId, {}],
+ ),
+ )
+ if (command === 'zoom') {
+ if (scope.nodeIds.length) {
+ setScope({ nodeIds: [] })
+ }
+ setTimeout(() => fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!]), 50)
+ return
+ }
+ if (!scope.nodeIds.length) {
+ setScope({ nodeIds: [emacsNode] })
+ setTimeout(() => {
+ fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!])
+ }, 50)
+ return
+ }
+ if (behavior.localSame !== 'add') {
+ setScope({ nodeIds: [emacsNode] })
+ setTimeout(() => {
+ fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!])
+ }, 50)
+ return
+ }
+
+ // if the node is in the scopednodes, add it to scope instead of replacing it
+ if (
+ !scope.nodeIds.includes(emacsNode) ||
+ !scope.nodeIds.some((scopeId: string) => {
+ return nodes[scopeId]
+ })
+ ) {
+ setScope({ nodeIds: [emacsNode] })
+ setTimeout(() => {
+ fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!])
+ }, 50)
+ return
+ }
+ setScope((currentScope: Scope) => ({
+ ...currentScope,
+ nodeIds: [...currentScope.nodeIds, emacsNode as string],
+ }))
+ setTimeout(() => fg.zoomToFit(speed, padding, (node: OrgRoamNode) => nodes[node.id!]), 50)
+ }
+
useEffect(() => {
const socket = new ReconnectingWebSocket('ws://localhost:35903')
socket.addEventListener('open', (event) => {
console.log('Connection with Emacs established')
})
socket.addEventListener('message', (event) => {
+ const fg = graphRef.current
const message = JSON.parse(event.data)
switch (message.type) {
case 'graphdata':
@@ -148,21 +205,28 @@ export function GraphPage() {
return setEmacsTheme(message.data)
case 'command':
switch (message.data.commandName) {
- case 'follow':
- return setEmacsNodeId(message.data.id)
+ case 'local':
+ const speed = behavior.zoomSpeed
+ const padding = behavior.zoomPadding
+ followBehavior('local', message.data.id, speed, padding)
+ setEmacsNodeId(message.data.id)
+ break
case 'zoom': {
- const links = linksByNodeIdRef.current[message.data.id!] ?? []
- const nodes = Object.fromEntries(
- [
- message.data.id! as string,
- ...links.flatMap((link) => [link.source, link.target]),
- ].map((nodeId) => [nodeId, {}]),
- )
- const fg = graphRef.current
- fg.zoomToFit(2000, 200, (node: OrgRoamNode) => nodes[node.id!])
+ const speed = message?.data?.speed || behavior.zoomSpeed
+ const padding = message?.data?.padding || behavior.zoomPadding
+ followBehavior('zoom', message.data.id, speed, padding)
+ setEmacsNodeId(message.data.id)
+ break
}
- case 'toggle': {
- /* setBehavior({ ...behavior, followLocalorZoom: !behavior.followLocalOrZoom }) */
+ case 'follow': {
+ followBehavior(
+ behavior.follow,
+ message.data.id,
+ behavior.zoomSpeed,
+ behavior.zoomPadding,
+ )
+ setEmacsNodeId(message.data.id)
+ break
}
default:
return console.error('unknown message type', message.type)
@@ -189,6 +253,8 @@ export function GraphPage() {
setVisuals,
mouse,
setMouse,
+ behavior,
+ setBehavior,
}}
/>
<Box position="absolute" alignItems="top">
@@ -205,6 +271,8 @@ export function GraphPage() {
visuals,
behavior,
mouse,
+ scope,
+ setScope,
}}
/>
</Box>
@@ -223,6 +291,8 @@ export interface GraphProps {
visuals: typeof initialVisuals
behavior: typeof initialBehavior
mouse: typeof initialMouse
+ scope: Scope
+ setScope: any
}
export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
@@ -237,6 +307,8 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
visuals,
behavior,
mouse,
+ scope,
+ setScope,
} = props
// react-force-graph does not track window size
@@ -245,7 +317,6 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
const [windowWidth, windowHeight] = useWindowSize()
const [hoverNode, setHoverNode] = useState<NodeObject | null>(null)
- const [scope, setScope] = useState<Scope>({ nodeIds: [] })
const handleClick = (click: string, node: NodeObject) => {
switch (click) {
@@ -254,7 +325,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
if (scope.nodeIds.includes(node.id as string)) {
break
}
- setScope((currentScope) => ({
+ setScope((currentScope: Scope) => ({
...currentScope,
nodeIds: [...currentScope.nodeIds, node.id as string],
}))
@@ -278,27 +349,15 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) {
)
}
+ const centralHighlightedNode = useRef<NodeObject | null>(null)
+
useEffect(() => {
if (!emacsNodeId) {
return
}
- const fg = graphRef.current
- if (behavior.followLocalOrZoom) {
- setScope({ nodeIds: [emacsNodeId] })
- setTimeout(() => {
- fg?.zoomToFit(
- 2000,
- numberWithinRange(20, 200, windowWidth / 8),
- (node: NodeObject) => getNeighborNodes(emacsNodeId)[node.id!],
- )
- }, 1)
- } else {
- fg?.zoomToFit(1000, 200, (node: NodeObject) => getNeighborNodes(emacsNodeId)[node.id!])
- setHoverNode(nodeById[emacsNodeId] as NodeObject)
- }
+ centralHighlightedNode.current = nodeById[emacsNodeId] as NodeObject
}, [emacsNodeId])
- const centralHighlightedNode = useRef<NodeObject | null>(null)
centralHighlightedNode.current = hoverNode
const highlightedNodes = useMemo(() => {
if (!centralHighlightedNode.current) {