diff options
-rw-r--r-- | api.d.ts | 1 | ||||
-rw-r--r-- | components/config.ts | 1 | ||||
-rw-r--r-- | components/tweaks.tsx | 75 | ||||
-rw-r--r-- | org-roam-ui.el | 24 | ||||
-rw-r--r-- | pages/index.tsx | 4 |
5 files changed, 100 insertions, 5 deletions
@@ -1,6 +1,7 @@ export type OrgRoamGraphReponse = { nodes: OrgRoamNode[] links: OrgRoamLink[] + tags: string[] } export type OrgRoamNode = { diff --git a/components/config.ts b/components/config.ts index 1181f65..e9471f8 100644 --- a/components/config.ts +++ b/components/config.ts @@ -35,6 +35,7 @@ export const initialFilter = { orphans: false, parents: true, tags: [], + tagColors: [], nodes: [], links: [], date: [], diff --git a/components/tweaks.tsx b/components/tweaks.tsx index 3c9ee00..e46baf3 100644 --- a/components/tweaks.tsx +++ b/components/tweaks.tsx @@ -6,6 +6,8 @@ import { InfoOutlineIcon, RepeatIcon, ArrowRightIcon, + AddIcon, + DeleteIcon, } from '@chakra-ui/icons' import { Accordion, @@ -38,6 +40,7 @@ import { Grid, Portal, SlideFade, + Input, } from '@chakra-ui/react' import React, { useState, useContext } from 'react' import Scrollbars from 'react-custom-scrollbars-2' @@ -64,6 +67,7 @@ export interface TweakProps { setMouse: any behavior: typeof initialBehavior setBehavior: any + tags: string[] } export const Tweaks = (props: TweakProps) => { @@ -80,6 +84,7 @@ export const Tweaks = (props: TweakProps) => { setMouse, behavior, setBehavior, + tags, } = props const [showTweaks, setShowTweaks] = useState(true) const { highlightColor, setHighlightColor } = useContext(ThemeContext) @@ -107,6 +112,7 @@ export const Tweaks = (props: TweakProps) => { 'gray.900', 'black', ] + return ( <> <SlideFade in={!showTweaks}> @@ -223,6 +229,24 @@ export const Tweaks = (props: TweakProps) => { ></Switch> </Flex> </VStack> + <Accordion allowToggle allowMultiple paddingLeft={3}> + <AccordionItem> + <AccordionButton> + Tag filters + <AccordionIcon /> + </AccordionButton> + <AccordionPanel> + <TagPanel filter={filter} setFilter={setFilter} tags={tags} /> + </AccordionPanel> + </AccordionItem> + <AccordionItem> + <AccordionButton> + Tag colors + <AccordionIcon /> + </AccordionButton> + <AccordionPanel></AccordionPanel> + </AccordionItem> + </Accordion> </AccordionPanel> </AccordionItem> <AccordionItem> @@ -1242,3 +1266,54 @@ export const ColorMenu = (props: ColorMenuProps) => { </Flex> ) } + +export interface TagPanelProps { + tags: string[] + filter: typeof initialFilter + setFilter: any +} + +export const TagPanel = (props: TagPanelProps) => { + const { filter, setFilter, tags } = props + const [tagFilterText, setTagFilterText] = useState('') + + return ( + <VStack> + <Flex alignItems="center" justifyContent="space-between"> + <Input + placeHolder="New tag..." + value={tagFilterText} + onChange={(v) => setTagFilterText(v.target.value)} + /> + <IconButton + onClick={() => { + if (!tags.includes(tagFilterText)) { + return + } + setFilter({ ...filter, tags: [...filter.tags, tagFilterText] }) + setTagFilterText('') + }} + aria-label="add tag" + icon={<AddIcon />} + /> + </Flex> + {filter.tags.map((tag) => { + return ( + <Flex aignItems="center" justifyContent="space-between"> + {tag} + <IconButton + aria-label={'delete tag ' + tag} + icon={<DeleteIcon />} + onClick={() => + setFilter({ + ...filter, + tags: filter.tags.filter((t) => t !== tag), + }) + } + /> + </Flex> + ) + })} + </VStack> + ) +} diff --git a/org-roam-ui.el b/org-roam-ui.el index 098e99e..5ac7eac 100644 --- a/org-roam-ui.el +++ b/org-roam-ui.el @@ -159,9 +159,14 @@ This serves the web-build and API over HTTP." (defun org-roam-ui--send-graphdata () "Get roam data, make JSON, send through websocket to org-roam-ui." - (let* ((nodes-columns [id file title level properties]) + (let* ((nodes-columns [id file title level properties ,(funcall group-concat tag (emacsql-escape-raw \, ))]) + (nodes-names [id file title level properties tags]) (links-columns [links:source links:dest links:type refs:node-id]) - (nodes-db-rows (org-roam-db-query `[:select ,nodes-columns :from nodes])) + (nodes-db-rows (org-roam-db-query `[:select ,nodes-columns :as tags + :from nodes + :left-join tags + :on (= id node_id) + :group :by id])) ;; Left outer join on refs means any id link (or cite link without a ;; corresponding node) will have 'nil for the `refs:node-id' value. Any ;; cite link where a node has that `:ROAM_REFS:' will have a value. @@ -177,8 +182,9 @@ This serves the web-build and API over HTTP." (if node-id (list source node-id "cite") (list source dest type)))) links-db-rows)) - (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))))) + (response `((nodes . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist (append nodes-names nil)) nodes-db-rows)) + (links . ,(mapcar (apply-partially #'org-roam-ui-sql-to-alist '(source target type)) links-db-rows)) + (tags . ,(seq-mapcat #'seq-reverse (org-roam-db-query [:select :distinct tag :from tags])))))) (websocket-send-text oru-ws (json-encode `((type . "graphdata") (data . ,response)))))) (defun org-roam-ui--update-current-node () @@ -227,7 +233,15 @@ This serves the web-build and API over HTTP." ROWS is the sql result, while COLUMN-NAMES is the columns to use." (let (res) (while rows - (push (cons (pop column-names) (pop rows)) res)) + ;; emacsql does not want to give us the tags as a list, so we post process it + (if (not (string= (car column-names) "tags")) + (push (cons (pop column-names) (pop rows)) res) + (push (cons (pop column-names) + (seq-remove + (lambda (elt) (string= elt ",")) + rows)) + res) + (setq rows nil))) res)) diff --git a/pages/index.tsx b/pages/index.tsx index 4a178be..ee826ce 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -51,6 +51,7 @@ const ForceGraph3D = ( export type NodeById = { [nodeId: string]: OrgRoamNode | undefined } export type LinksByNodeId = { [nodeId: string]: OrgRoamLink[] | undefined } export type NodesByFile = { [file: string]: OrgRoamNode[] | undefined } +export type Tags = string[] export type Scope = { nodeIds: string[] } @@ -79,8 +80,10 @@ export function GraphPage() { const nodeByIdRef = useRef<NodeById>({}) const linksByNodeIdRef = useRef<LinksByNodeId>({}) + const tagsRef = useRef<Tags>([]) const updateGraphData = (orgRoamGraphData: OrgRoamGraphReponse) => { + tagsRef.current = orgRoamGraphData.tags const nodesByFile = orgRoamGraphData.nodes.reduce<NodesByFile>((acc, node) => { return { ...acc, @@ -270,6 +273,7 @@ export function GraphPage() { behavior, setBehavior, }} + tags={tagsRef.current} /> <Box position="absolute" alignItems="top"> <Graph |