From 34bffd1bb0dc119dc7214a990772210b2da12bbc Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Fri, 30 Jul 2021 22:01:17 +0200 Subject: lots of label customization + fixes --- components/config.ts | 24 +- components/tweaks.tsx | 732 ++++++++++++++++++++++++++++++++++---------------- org-roam-ui.el | 14 +- pages/_app.tsx | 6 + pages/index.tsx | 66 +++-- 5 files changed, 582 insertions(+), 260 deletions(-) diff --git a/components/config.ts b/components/config.ts index d538941..5c0a922 100644 --- a/components/config.ts +++ b/components/config.ts @@ -84,15 +84,33 @@ export const initialVisuals = { algorithms: algorithms, algorithmOptions: options, algorithmName: 'CubicOut', - linkColorScheme: '500', - nodeColorScheme: ['gray', 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'pink', 'purple'], + linkColorScheme: 'gray.500', + nodeColorScheme: [ + 'red.500', + 'orange.500', + 'yellow.500', + 'green.500', + 'cyan.500', + 'blue.500', + 'pink.500', + 'purple.500', + ], nodeHighlight: '', - linkHighlight: '', + linkHighlight: 'purple.500', backgroundColor: 'white', emacsNodeColor: '', + labelTextColor: 'black', + labelBackgroundColor: 'white', + labelBackgroundOpacity: 0.7, } export const initialBehavior = { follow: 'Zoom', followLocalOrZoom: true, } + +export const initialMouse = { + highlight: 'hover', + local: 'click', + follow: 'double', +} diff --git a/components/tweaks.tsx b/components/tweaks.tsx index e9ab582..fb20cb3 100644 --- a/components/tweaks.tsx +++ b/components/tweaks.tsx @@ -36,10 +36,11 @@ import { Heading, Collapse, Grid, + Portal, } from '@chakra-ui/react' import React, { useState, useContext } from 'react' import Scrollbars from 'react-custom-scrollbars-2' -import { initialPhysics, initialFilter, initialVisuals } from './config' +import { initialPhysics, initialFilter, initialVisuals, initialMouse } from './config' import { ThemeContext } from '../pages/themecontext' @@ -52,15 +53,53 @@ export interface TweakProps { setFilter: any visuals: typeof initialVisuals setVisuals: any + mouse: typeof initialMouse + setMouse: any } export const Tweaks = (props: TweakProps) => { - const { physics, setPhysics, threeDim, setThreeDim, filter, setFilter, visuals, setVisuals } = - props + const { + physics, + setPhysics, + threeDim, + setThreeDim, + filter, + setFilter, + visuals, + setVisuals, + mouse, + setMouse, + } = props const [showTweaks, setShowTweaks] = useState(true) const { highlightColor, setHighlightColor } = useContext(ThemeContext) - const colorList = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'pink', 'purple', 'gray'] - const grays = ['100', '200', '300', '400', '500', '600', '700', '800', '900'] + const colorList = [ + 'red.500', + 'orange.500', + 'yellow.500', + 'green.500', + 'cyan.500', + 'blue.500', + 'pink.500', + 'purple.500', + 'gray.400', + 'gray.500', + 'gray.600', + 'white', + 'black', + ] + const grays = [ + 'black', + 'gray.100', + 'gray.200', + 'gray.300', + 'gray.400', + 'gray.500', + 'gray.600', + 'gray.700', + 'gray.800', + 'gray.900', + 'white', + ] return ( <> { {visuals.nodeColorScheme.map((color) => ( ))} - - { - if (!colors.length) { - return - } - setVisuals({ ...visuals, nodeColorScheme: colors }) - console.log(visuals.nodeColorScheme) - }} - > - {colorList.map((color) => ( - c === color, - )} - value={color} - isDisabled={ - visuals.nodeColorScheme.length === 1 && - visuals.nodeColorScheme[0] === color + + {' '} + + { + if (!colors.length) { + return } - > - + {colorList.map((color) => ( + c === color, + )} + value={color} + isDisabled={ + visuals.nodeColorScheme.length === 1 && + visuals.nodeColorScheme[0] === color + } > - {color[0]!.toUpperCase() + color!.slice(1)} - - - ))} - - + justifyContent="space-between" + alignItems="center" + display="flex" + > + + {color[0]!.toUpperCase() + color!.slice(1)} + + + + + ))} + + + Links - + { {visuals.nodeColorScheme.map((color) => ( @@ -448,60 +491,61 @@ export const Tweaks = (props: TweakProps) => { )} - - - setVisuals({ ...visuals, linkColorScheme: '' }) - } - justifyContent="space-between" - alignItems="center" - display="flex" - > - Match nodes - - {visuals.nodeColorScheme.map((color) => ( - - ))} - - - {grays.map((color) => ( + + {' '} + - setVisuals({ - ...visuals, - linkColorScheme: color, - }) + setVisuals({ ...visuals, linkColorScheme: '' }) } justifyContent="space-between" alignItems="center" display="flex" > - {color[0]!.toUpperCase() + color!.slice(1)} - + flexDirection="column" + flexWrap="wrap" + > + {visuals.nodeColorScheme.map((color) => ( + + ))} + - ))} - + {grays.map((color) => ( + + setVisuals({ + ...visuals, + linkColorScheme: color, + }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + + + ))} + + Accent - + { > } - - {colorList.map((color) => ( - setHighlightColor(color)} - justifyContent="space-between" - alignItems="center" - display="flex" - > - {color[0]!.toUpperCase() + color!.slice(1)} - - - ))} - + + {' '} + + {colorList.map((color) => ( + setHighlightColor(color)} + justifyContent="space-between" + alignItems="center" + display="flex" + > + {color[0]!.toUpperCase() + color!.slice(1)} + + + ))} + + Link Highlight - + { > } - - setVisuals({ ...visuals, linkHighlight: '' })} - justifyContent="space-between" - alignItems="center" - display="flex" - > - Match current color - - {colorList.map((color) => ( + + {' '} + - setVisuals({ ...visuals, linkHighlight: color }) + setVisuals({ ...visuals, linkHighlight: '' }) } justifyContent="space-between" alignItems="center" display="flex" > - {color[0]!.toUpperCase() + color!.slice(1)} - + Match current color - ))} - + {colorList.map((color) => ( + + setVisuals({ ...visuals, linkHighlight: color }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + {color[0]!.toUpperCase() + color!.slice(1)} + + + ))} + + Node Highlight - + { > } - - setVisuals({ ...visuals, nodeHighlight: '' })} - justifyContent="space-between" - alignItems="center" - display="flex" - > - Match current color - - {colorList.map((color) => ( + + {' '} + - setVisuals({ ...visuals, nodeHighlight: color }) + setVisuals({ ...visuals, nodeHighlight: '' }) } justifyContent="space-between" alignItems="center" display="flex" > - {color[0]!.toUpperCase() + color!.slice(1)} - + Match current color - ))} - + {colorList.map((color) => ( + + setVisuals({ ...visuals, nodeHighlight: color }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + {color[0]!.toUpperCase() + color!.slice(1)} + + + ))} + + Background - + { > } - - {grays.map((color) => ( - - setVisuals({ ...visuals, backgroundColor: color }) - } - justifyContent="space-between" - alignItems="center" - display="flex" - > - {color[0]!.toUpperCase() + color!.slice(1)} - - - ))} - + + {' '} + + {grays.map((color) => ( + + setVisuals({ ...visuals, backgroundColor: color }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + {color[0]!.toUpperCase() + color!.slice(1)} + + + ))} + + Emacs Node - + { > } - - setVisuals({ ...visuals, emacsNodeColor: '' })} - justifyContent="space-between" - alignItems="center" - display="flex" - > - No change - - - {colorList.map((color) => ( + + {' '} + - setVisuals({ ...visuals, emacsNodeColor: color }) + setVisuals({ ...visuals, emacsNodeColor: '' }) } justifyContent="space-between" alignItems="center" display="flex" > - {color[0]!.toUpperCase() + color!.slice(1)} - + No change + - ))} - + {colorList.map((color) => ( + + setVisuals({ ...visuals, emacsNodeColor: color }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + {color[0]!.toUpperCase() + color!.slice(1)} + + + ))} + + @@ -781,23 +846,24 @@ export const Tweaks = (props: TweakProps) => { onChange={(value) => setPhysics({ ...physics, linkOpacity: value })} /> )} - - - Labels - - } - > - {!physics.labels - ? 'Never' - : physics.labels < 2 - ? 'On Highlight' - : 'Always'} - - + + Labels + + } + > + {!physics.labels + ? 'Never' + : physics.labels < 2 + ? 'On Highlight' + : 'Always'} + + + {' '} + setPhysics({ ...physics, labels: 0 })}> Never @@ -807,21 +873,152 @@ export const Tweaks = (props: TweakProps) => { setPhysics({ ...physics, labels: 2 })}> Always + setPhysics({ ...physics, labels: 3 })}> + Always (even in 3D) + - - - 1} animateOpacity> - - - setPhysics({ ...physics, labelScale: value / 5 }) - } - /> - - - + + + + 0} animateOpacity> + } + align="stretch" + paddingLeft={2} + color="gray.800" + > + + Text + + } + color="black" + colorScheme="" + > + { + + } + + + {' '} + + {grays.map((color) => ( + + setVisuals({ + ...visuals, + labelTextColor: color, + }) + } + > + + + ))} + + + + + + Background + + } + color="black" + colorScheme="" + > + { + + } + + + {' '} + + + setVisuals({ + ...visuals, + labelBackgroundColor: '', + }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + None + + {grays.map((color) => ( + + setVisuals({ + ...visuals, + labelBackgroundColor: color, + }) + } + justifyContent="space-between" + alignItems="center" + display="flex" + > + + + ))} + + + + + + + { + console.log(visuals.labelBackgroundOpacity) + setVisuals({ ...visuals, labelBackgroundOpacity: value }) + }} + min={0} + max={1} + step={0.01} + /> + + + 1} animateOpacity> + + + setPhysics({ ...physics, labelScale: value / 5 }) + } + /> + + + + { align="stretch" paddingLeft={7} color="gray.800" - > + > + + + Expand Node + + + + } + colorScheme="" + color="black" + > + {mouse.local[0]!.toUpperCase() + mouse.local!.slice(1)} + + + {' '} + + setMouse({ ...mouse, local: '' })}> + Never + + setMouse({ ...mouse, local: 'click' })}> + Click + + setMouse({ ...mouse, local: 'double' })}> + Double Click + + setMouse({ ...mouse, local: 'right' })}> + Right Click + + + + + + + Open in Emacs + + } + colorScheme="" + color="black" + > + {mouse.follow[0]!.toUpperCase() + mouse.follow!.slice(1)} + + + {' '} + + setMouse({ ...mouse, follow: '' })}> + Never + + setMouse({ ...mouse, follow: 'click' })}> + Click + + setMouse({ ...mouse, follow: 'double' })}> + Double Click + + setMouse({ ...mouse, follow: 'right' })}> + Right Click + + + + + + @@ -1028,11 +1289,14 @@ export const DropDownMenu = (props: DropDownMenuProps) => { }> {displayValue} - - {textArray.map((option, i) => { - ; {option} - })} - + + {' '} + + {textArray.map((option, i) => { + ; {option} + })} + + ) } diff --git a/org-roam-ui.el b/org-roam-ui.el index 94d8e61..5ecc704 100644 --- a/org-roam-ui.el +++ b/org-roam-ui.el @@ -106,21 +106,18 @@ This serves the web-build and API over HTTP." :on-open (lambda (ws) (progn (setq oru-ws ws) (org-roam-ui--send-graphdata) - (add-hook 'after-save-hook #'org-roam-ui--send-graphdata) - + (add-hook 'after-save-hook #'org-roam-ui--on-save) (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) (remove-hook 'post-command-hook #'org-roam-ui--update-current-node) - (add-hook 'after-save-hook #'org-roam-ui--send-graphdata) + (remove-hook 'after-save-hook #'org-roam-ui--on-save) (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 :around #'org-roam-ui-sync-theme--advice))) - + (advice-add 'load-theme :after #'org-roam-ui-sync-theme--advice))) (t (progn (websocket-server-close org-roam-ui-ws) @@ -129,6 +126,11 @@ This serves the web-build and API over HTTP." (advice-remove 'load-theme #'org-roam-ui-sync-theme--advice)) (httpd-stop))))) +(defun org-roam-ui--on-save () + "Send graphdata on saving an only org-roam buffer." + (when (org-roam-buffer-p) + (org-roam-ui--send-graphdata)) + ) (defun org-roam-ui--send-graphdata () "Get roam data, make JSON, send through websocket to org-roam-ui." diff --git a/pages/_app.tsx b/pages/_app.tsx index f8ff942..9f580f6 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -175,6 +175,12 @@ function SubApp(props: any) { }, }, }, + SliderThumb: { + bg: highlightColor + '.500', + }, + SliderFilledTrack: { + bg: 'gray.400', + }, }, } }, [highlightColor, JSON.stringify(emacsTheme)]) diff --git a/pages/index.tsx b/pages/index.tsx index afd0ed3..ae3c048 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -28,6 +28,7 @@ import { initialFilter, initialVisuals, initialBehavior, + initialMouse, } from '../components/config' import { Tweaks } from '../components/tweaks' @@ -73,6 +74,7 @@ export function GraphPage() { const [graphData, setGraphData] = useState(null) const [emacsNodeId, setEmacsNodeId] = useState(null) const [behavior, setBehavior] = usePersistantState('behavior', initialBehavior) + const [mouse, setMouse] = usePersistantState('mouse', initialMouse) const nodeByIdRef = useRef({}) const linksByNodeIdRef = useRef({}) @@ -185,6 +187,8 @@ export function GraphPage() { setFilter, visuals, setVisuals, + mouse, + setMouse, }} /> @@ -421,6 +425,8 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { const theme = useTheme() const themeContext = useContext(ThemeContext) + const getThemeColor = (name: string) => name.split('.').reduce((o, i) => o[i], theme.colors) + const highlightColors = useMemo(() => { const allColors = visuals.nodeColorScheme.concat( visuals.linkColorScheme || [], @@ -428,13 +434,12 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { visuals.nodeHighlight || [], ) - const getColor = (c: any) => (isNaN(c) ? theme.colors[c][500] : theme.colors.gray[c]) return Object.fromEntries( allColors.map((color) => { - const color1 = getColor(color) + const color1 = getThemeColor(color) const crisscross = allColors.map((color2) => [ color2, - d3int.interpolate(color1, getColor(color2)), + d3int.interpolate(color1, getThemeColor(color2)), ]) return [color, Object.fromEntries(crisscross)] }), @@ -479,25 +484,25 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { // or we don't have our own scheme and we're not being highlighted if (!visuals.linkHighlight && !visuals.linkColorScheme && !needsHighlighting) { const nodeColor = getLinkNodeColor(sourceId, targetId) - return theme.colors[nodeColor][500] + return getThemeColor(nodeColor) } if (!needsHighlighting && !visuals.linkColorScheme) { const nodeColor = getLinkNodeColor(sourceId, targetId) - return theme.colors[nodeColor][500] + return getThemeColor(nodeColor) } if (!needsHighlighting) { - return theme.colors.gray[visuals.linkColorScheme] + return getThemeColor(visuals.linkColorScheme) } if (!visuals.linkHighlight && !visuals.linkColorScheme) { const nodeColor = getLinkNodeColor(sourceId, targetId) - return theme.colors[nodeColor][500] + return getThemeColor(nodeColor) } if (!visuals.linkHighlight) { - return theme.colors.gray[visuals.linkColorScheme] + return getThemeColor(visuals.linkColorScheme) } if (!visuals.linkColorScheme) { @@ -512,16 +517,37 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { // 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 theme.colors[visuals.emacsNodeColor][500] + return getThemeColor(visuals.emacsNodeColor) } if (!needsHighlighting) { - return theme.colors[getNodeColorById(node.id)][500] + return getThemeColor(getNodeColorById(node.id)) } if (!visuals.nodeHighlight) { - return theme.colors[getNodeColorById(node.id)][500] + return getThemeColor(getNodeColorById(node.id)) } return highlightColors[getNodeColorById(node.id)][visuals.nodeHighlight](opacity) } + + const hexToRGBA = (hex: string, opacity: number) => + 'rgba(' + + (hex = hex.replace('#', '')) + .match(new RegExp('(.{' + hex.length / 3 + '})', 'g'))! + .map(function (l) { + return parseInt(hex.length % 2 ? l + l : l, 16) + }) + .concat(isFinite(opacity) ? opacity : 1) + .join(',') + + ')' + + const labelTextColor = useMemo( + () => getThemeColor(visuals.labelTextColor), + [visuals.labelTextColor], + ) + const labelBackgroundColor = useMemo( + () => getThemeColor(visuals.labelBackgroundColor), + [visuals.labelBackgroundColor], + ) + const graphCommonProps: ComponentPropsWithoutRef = { graphData: scopedGraphData, width: windowWidth, @@ -582,26 +608,28 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { if (globalScale <= physics.labelScale) { return opacity } - return highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!] ? Math.max(fadeFactor, opacity) : 1 * fadeFactor * (-1 * (0.5 * opacity - 1)) } - if (physics.labels === 2) { - const backgroundOpacity = 0.5 * getLabelOpacity() - ctx.fillStyle = `rgba(20, 20, 20, ${backgroundOpacity})` + if (visuals.labelBackgroundColor && visuals.labelBackgroundOpacity) { + const backgroundOpacity = getLabelOpacity() * visuals.labelBackgroundOpacity + const labelBackground = hexToRGBA(labelBackgroundColor, backgroundOpacity) + ctx.fillStyle = labelBackground ctx.fillRect( node.x! - bckgDimensions[0] / 2, node.y! - bckgDimensions[1] / 2, ...bckgDimensions, ) } + // draw label text const textOpacity = getLabelOpacity() ctx.textAlign = 'center' ctx.textBaseline = 'middle' - ctx.fillStyle = `rgb(255, 255, 255, ${textOpacity})` + const labelText = hexToRGBA(labelTextColor, textOpacity) + ctx.fillStyle = labelText ctx.font = `${fontSize}px Sans-Serif` ctx.fillText(label, node.x!, node.y!) }, @@ -633,6 +661,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { onNodeClick: (node: NodeObject, event: any) => { const isDoubleClick = event.timeStamp - lastNodeClickRef.current < 400 lastNodeClickRef.current = event.timeStamp + console.log(event) if (isDoubleClick) { window.open('org-protocol://roam-node?node=' + node.id, '_self') @@ -683,8 +712,11 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { return } const sprite = new SpriteText(node.title.substring(0, 30)) - sprite.color = '#ffffff' + sprite.color = getThemeColor(visuals.labelTextColor) + sprite.backgroundColor = getThemeColor(visuals.labelBackgroundColor) + sprite.padding = 2 sprite.textHeight = 8 + return sprite }} /> -- cgit v1.2.3