diff options
author | Thomas F. K. Jorna <[email protected]> | 2021-10-11 01:13:10 +0200 |
---|---|---|
committer | Thomas F. K. Jorna <[email protected]> | 2021-10-11 01:13:10 +0200 |
commit | 546a88ec37073840e98ed689f7139d04985e861c (patch) | |
tree | 73467c1720328fd11a77e864ebe6e5fd7c9b3251 | |
parent | 31d7477b376501bd80fe635b91412bc7f6ae7ea7 (diff) |
feat(preview): ui redo
-rw-r--r-- | components/Sidebar/Backlinks.tsx | 3 | ||||
-rw-r--r-- | components/Sidebar/Link.tsx | 20 | ||||
-rw-r--r-- | components/Sidebar/Note.tsx | 4 | ||||
-rw-r--r-- | components/Sidebar/Toolbar.tsx | 102 | ||||
-rw-r--r-- | components/Sidebar/index.tsx | 109 | ||||
-rw-r--r-- | components/Tweaks/BehaviorPanel.tsx | 1 | ||||
-rw-r--r-- | components/Tweaks/SliderWithInfo.tsx | 2 | ||||
-rw-r--r-- | components/Tweaks/index.tsx | 27 | ||||
-rw-r--r-- | components/config.ts | 5 | ||||
-rw-r--r-- | components/contextmenu.tsx | 146 | ||||
-rw-r--r-- | package-lock.json | 31 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | pages/_app.tsx | 5 | ||||
-rw-r--r-- | pages/index.tsx | 357 | ||||
-rw-r--r-- | util/processOrg.tsx | 7 | ||||
-rw-r--r-- | util/uniorg.tsx | 11 |
16 files changed, 532 insertions, 299 deletions
diff --git a/components/Sidebar/Backlinks.tsx b/components/Sidebar/Backlinks.tsx index 8c1e9bc..68ab551 100644 --- a/components/Sidebar/Backlinks.tsx +++ b/components/Sidebar/Backlinks.tsx @@ -14,6 +14,7 @@ export interface BacklinksProps { linksByNodeId: LinksByNodeId nodeByCite: NodeByCite setSidebarHighlightedNode: OrgRoamNode + openContextMenu: any } import { PreviewLink } from './Link' @@ -27,6 +28,7 @@ export const Backlinks = (props: BacklinksProps) => { nodeById, linksByNodeId, nodeByCite, + openContextMenu, } = props const links = linksByNodeId[previewNode?.id] ?? [] @@ -59,6 +61,7 @@ export const Backlinks = (props: BacklinksProps) => { href={`id:${link as string}`} nodeById={nodeById} setPreviewNode={setPreviewNode} + openContextMenu={openContextMenu} > {nodeById[link as string]?.title} </PreviewLink> diff --git a/components/Sidebar/Link.tsx b/components/Sidebar/Link.tsx index 16fc2ac..49fe9cf 100644 --- a/components/Sidebar/Link.tsx +++ b/components/Sidebar/Link.tsx @@ -35,6 +35,7 @@ export interface LinkProps { setSidebarHighlightedNode: any nodeByCite: NodeByCite nodeById: NodeById + openContextMenu: any } export interface NormalLinkProps { @@ -44,6 +45,7 @@ export interface NormalLinkProps { href: any children: any setSidebarHighlightedNode: any + openContextMenu: any } import { hexToRGBA, getThemeColor } from '../../pages/index' @@ -51,7 +53,8 @@ import noteStyle from './noteStyle' import { OrgImage } from './OrgImage' export const NormalLink = (props: NormalLinkProps) => { - const { setSidebarHighlightedNode, setPreviewNode, nodeById, href, children } = props + const { setSidebarHighlightedNode, setPreviewNode, nodeById, openContextMenu, href, children } = + props const { highlightColor } = useContext(ThemeContext) const theme = useTheme() @@ -67,12 +70,16 @@ export const NormalLink = (props: NormalLinkProps) => { fontWeight={500} color={highlightColor} textDecoration="underline" + onContextMenu={(e) => { + e.preventDefault() + openContextMenu(nodeById[uri], e) + }} onClick={() => setPreviewNode(nodeById[uri])} // TODO don't hardcode the opacitycolor _hover={{ textDecoration: 'none', cursor: 'pointer', bgColor: coolHighlightColor + '22' }} _focus={{ outlineColor: highlightColor }} > - {nodeById[uri]?.title} + {children} </Text> ) } @@ -86,6 +93,7 @@ export const PreviewLink = (props: LinkProps) => { previewNode, setPreviewNode, nodeByCite, + openContextMenu, } = props // TODO figure out how to properly type this // see https://github.com/rehypejs/rehype-react/issues/25 @@ -129,8 +137,9 @@ export const PreviewLink = (props: LinkProps) => { href={href} nodeById={nodeById} setPreviewNode={setPreviewNode} + openContextMenu={openContextMenu} > - {nodeById[id as string]?.title} + {children} </PreviewLink> ), img: ({ src }) => { @@ -186,6 +195,7 @@ export const PreviewLink = (props: LinkProps) => { href, children, nodeByCite, + openContextMenu, }} /> </Box> @@ -213,7 +223,9 @@ export const PreviewLink = (props: LinkProps) => { maxHeight={300} overflow="scroll" > - <Box sx={noteStyle}>{orgText}</Box> + <Box color="black" sx={noteStyle}> + {orgText} + </Box> </PopoverBody> </PopoverContent> </Portal> diff --git a/components/Sidebar/Note.tsx b/components/Sidebar/Note.tsx index ef2e2b2..e425559 100644 --- a/components/Sidebar/Note.tsx +++ b/components/Sidebar/Note.tsx @@ -16,6 +16,7 @@ export interface NoteProps { justification: number justificationList: string[] linksByNodeId: LinksByNodeId + openContextMenu: any } export const Note = (props: NoteProps) => { @@ -28,6 +29,7 @@ export const Note = (props: NoteProps) => { nodeByCite, setSidebarHighlightedNode, linksByNodeId, + openContextMenu, } = props return ( <Box @@ -50,6 +52,7 @@ export const Note = (props: NoteProps) => { nodeById, nodeByCite, setSidebarHighlightedNode, + openContextMenu, }} /> <Backlinks @@ -60,6 +63,7 @@ export const Note = (props: NoteProps) => { linksByNodeId, nodeByCite, setSidebarHighlightedNode, + openContextMenu, }} /> </Flex> diff --git a/components/Sidebar/Toolbar.tsx b/components/Sidebar/Toolbar.tsx index f6f1e39..8741da5 100644 --- a/components/Sidebar/Toolbar.tsx +++ b/components/Sidebar/Toolbar.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Text, Flex, IconButton } from '@chakra-ui/react' +import { Text, Flex, IconButton, ButtonGroup, Tooltip } from '@chakra-ui/react' import { BiAlignJustify, BiAlignLeft, @@ -38,53 +38,65 @@ export const Toolbar = (props: ToolbarProps) => { nextPreviewNode, } = props return ( - <Flex py={3} alignItems="center" justifyContent="space-between" pr={4}> + <Flex pb={3} alignItems="center" justifyContent="space-between" pl={1} pr={1}> <Flex> - <IconButton - variant="ghost" - icon={<ChevronLeftIcon />} - aria-label="Previous node" - disabled={!canUndo} - onClick={() => previousPreviewNode()} - /> - <IconButton - variant="ghost" - icon={<ChevronRightIcon />} - aria-label="Next node" - disabled={!canRedo} - onClick={() => nextPreviewNode()} - /> + <ButtonGroup isAttached> + <Tooltip label="Go backward"> + <IconButton + variant="subtle" + icon={<ChevronLeftIcon />} + aria-label="Previous node" + disabled={!canUndo} + onClick={() => previousPreviewNode()} + /> + </Tooltip> + <Tooltip label="Go forward"> + <IconButton + variant="subtle" + icon={<ChevronRightIcon />} + aria-label="Next node" + disabled={!canRedo} + onClick={() => nextPreviewNode()} + /> + </Tooltip> + </ButtonGroup> </Flex> <Flex> - <IconButton - variant="ghost" - aria-label="Justify content" - icon={ - [ - <BiAlignJustify key="justify" />, - <BiAlignLeft key="left" />, - <BiAlignRight key="right" />, - <BiAlignMiddle key="center" />, - ][justification] - } - onClick={() => setJustification((curr: number) => (curr + 1) % 4)} - /> - <IconButton - variant="ghost" - aria-label="Indent Text" - icon={<BiRightIndent />} - onClick={() => { - setIndent((curr: number) => (curr ? 0 : 1)) - }} - /> - <IconButton - variant="ghost" - aria-label="Change font" - icon={<BiFont />} - onClick={() => { - setFont((curr: string) => (curr === 'sans serif' ? 'serif' : 'sans serif')) - }} - /> + <Tooltip label="Justify content"> + <IconButton + variant="subtle" + aria-label="Justify content" + icon={ + [ + <BiAlignJustify key="justify" />, + <BiAlignLeft key="left" />, + <BiAlignRight key="right" />, + <BiAlignMiddle key="center" />, + ][justification] + } + onClick={() => setJustification((curr: number) => (curr + 1) % 4)} + /> + </Tooltip> + {/* <Tooltip label="Indent trees"> + <IconButton + variant="subtle" + aria-label="Indent Text" + icon={<BiRightIndent />} + onClick={() => { + setIndent((curr: number) => (curr ? 0 : 1)) + }} + /> + </Tooltip> + <Tooltip label="Switch betwwen sans and serif"> + <IconButton + variant="subtle" + aria-label="Change font" + icon={<BiFont />} + onClick={() => { + setFont((curr: string) => (curr === 'sans serif' ? 'serif' : 'sans serif')) + }} + /> + </Tooltip> */} </Flex> </Flex> ) diff --git a/components/Sidebar/index.tsx b/components/Sidebar/index.tsx index 2e32f4f..cbc0cc9 100644 --- a/components/Sidebar/index.tsx +++ b/components/Sidebar/index.tsx @@ -3,15 +3,19 @@ import React, { useContext, useEffect, useRef, useState } from 'react' import { Toolbar } from './Toolbar' import { Note } from './Note' -import { Button, Slide, VStack, Flex, Heading, Box, IconButton } from '@chakra-ui/react' +import { Button, Slide, VStack, Flex, Heading, Box, IconButton, Tooltip } from '@chakra-ui/react' +import { Collapse } from './Collapse' import { Scrollbars } from 'react-custom-scrollbars-2' -import { ChevronLeftIcon, ChevronRightIcon, HamburgerIcon } from '@chakra-ui/icons' -import { BiFile } from 'react-icons/bi' +import { ChevronLeftIcon, ChevronRightIcon, CloseIcon, HamburgerIcon } from '@chakra-ui/icons' +import { BiDotsVerticalRounded, BiFile, BiNetworkChart } from 'react-icons/bi' +import { BsReverseLayoutSidebarInsetReverse } from 'react-icons/bs' import { GraphData, NodeObject, LinkObject } from 'force-graph' import { OrgRoamNode } from '../../api' import { ThemeContext } from '../../util/themecontext' -import { LinksByNodeId, NodeByCite, NodeById } from '../../pages/index' +import { LinksByNodeId, NodeByCite, NodeById, Scope } from '../../pages/index' +import { Resizable } from 're-resizable' +import { usePersistantState } from '../../util/persistant-state' export interface SidebarProps { isOpen: boolean @@ -28,6 +32,10 @@ export interface SidebarProps { resetPreviewNode: any previousPreviewNode: any nextPreviewNode: any + openContextMenu: any + scope: Scope + setScope: any + windowWidth: number } const Sidebar = (props: SidebarProps) => { @@ -46,10 +54,15 @@ const Sidebar = (props: SidebarProps) => { resetPreviewNode, previousPreviewNode, nextPreviewNode, + openContextMenu, + scope, + setScope, + windowWidth, } = props const { highlightColor } = useContext(ThemeContext) const [previewRoamNode, setPreviewRoamNode] = useState<OrgRoamNode>() + const [sidebarWidth, setSidebarWidth] = usePersistantState<number>('sidebarWidth', 400) useEffect(() => { if (!previewNode?.id) { @@ -67,25 +80,58 @@ const Sidebar = (props: SidebarProps) => { //maybe want to close it when clicking outside, but not sure //const outsideClickRef = useRef(); return ( - <Slide - direction="right" + <Collapse + animateOpacity={false} + dimension="width" in={isOpen} - style={{ width: 'clamp(400px, 30%, 500px)' }} + //style={{ position: 'relative' }} unmountOnExit + startingSize={0} + style={{ height: '100vh' }} > - <Flex flexDirection="row" height="100%"> - <Box pl={2} color="black" bg="alt.100" w="100%" paddingBottom={15}> + <Resizable + size={{ height: '100%', width: sidebarWidth }} + onResizeStop={(e, direction, ref, d) => { + setSidebarWidth((curr: number) => curr + d.width) + }} + enable={{ + top: false, + right: false, + bottom: false, + left: true, + topRight: false, + bottomRight: false, + bottomLeft: false, + topLeft: false, + }} + minWidth="220px" + maxWidth={windowWidth - 200} + > + <Box pl={2} color="black" h="100%" bg="alt.100" width="100%"> <Flex - justifyContent="space-between" - paddingTop={4} + whiteSpace="nowrap" + overflow="hidden" + textOverflow="ellipsis" pl={4} - pb={5} - pr={3} - alignItems="top" + alignItems="center" color="black" + width="100%" > - <Flex alignItems="center" whiteSpace="nowrap" textOverflow="ellipsis" overflow="hidden"> - <BiFile /> + <BiFile + onContextMenu={(e) => { + e.preventDefault() + openContextMenu(previewNode, e) + }} + /> + <Flex + whiteSpace="nowrap" + textOverflow="ellipsis" + overflow="hidden" + onContextMenu={(e) => { + e.preventDefault() + openContextMenu(previewNode, e) + }} + > <Heading pl={2} whiteSpace="nowrap" @@ -99,13 +145,23 @@ const Sidebar = (props: SidebarProps) => { {previewRoamNode?.title} </Heading> </Flex> - <IconButton - // eslint-disable-next-line react/jsx-no-undef - icon={<HamburgerIcon />} - aria-label="Close file-viewer" - variant="link" - onClick={onClose} - /> + <Flex flexDir="row" ml="auto"> + <IconButton + // eslint-disable-next-line react/jsx-no-undef + m={1} + icon={<BiDotsVerticalRounded />} + aria-label="Options" + variant="subtle" + onClick={(e) => { + openContextMenu(previewNode, e, { + left: undefined, + top: 12, + right: -windowWidth + 20, + bottom: undefined, + }) + }} + /> + </Flex> </Flex> <Toolbar {...{ @@ -132,7 +188,7 @@ const Sidebar = (props: SidebarProps) => { borderRadius: 10, backgroundColor: highlightColor, }} - color="black" + color="alt.100" {...props} /> )} @@ -148,13 +204,14 @@ const Sidebar = (props: SidebarProps) => { justification, justificationList, linksByNodeId, + openContextMenu, }} /> </VStack> </Scrollbars> </Box> - </Flex> - </Slide> + </Resizable> + </Collapse> ) } diff --git a/components/Tweaks/BehaviorPanel.tsx b/components/Tweaks/BehaviorPanel.tsx index 651396d..5d61730 100644 --- a/components/Tweaks/BehaviorPanel.tsx +++ b/components/Tweaks/BehaviorPanel.tsx @@ -11,6 +11,7 @@ import { VStack, Text, Box, + Switch, } from '@chakra-ui/react' import React from 'react' import { initialBehavior, initialMouse } from '../config' diff --git a/components/Tweaks/SliderWithInfo.tsx b/components/Tweaks/SliderWithInfo.tsx index f70faae..9d6903a 100644 --- a/components/Tweaks/SliderWithInfo.tsx +++ b/components/Tweaks/SliderWithInfo.tsx @@ -30,7 +30,7 @@ export const SliderWithInfo = ({ const { onChange, label, infoText } = rest const { highlightColor } = useContext(ThemeContext) return ( - <Box key={label}> + <Box key={label} pt={1} pb={2}> <Box display="flex" alignItems="flex-end"> <Text>{label}</Text> {infoText && <InfoTooltip infoText={infoText} />} diff --git a/components/Tweaks/index.tsx b/components/Tweaks/index.tsx index c60e670..33f11ee 100644 --- a/components/Tweaks/index.tsx +++ b/components/Tweaks/index.tsx @@ -68,6 +68,7 @@ export const Tweaks = (props: TweakProps) => { tagColors, setTagColors, } = props + const [showTweaks, setShowTweaks] = usePersistantState('showTweaks', false) const { highlightColor, setHighlightColor } = useContext(ThemeContext) @@ -75,12 +76,12 @@ export const Tweaks = (props: TweakProps) => { <Box position="absolute" zIndex="overlay" - marginTop={10} - marginLeft={10} + marginTop={0} + marginLeft={0} display={showTweaks ? 'none' : 'block'} > <IconButton - variant="ghost" + variant="subtle" aria-label="Settings" icon={<SettingsIcon />} onClick={() => setShowTweaks(true)} @@ -88,17 +89,17 @@ export const Tweaks = (props: TweakProps) => { </Box> ) : ( <Box + position="absolute" bg="alt.100" w="xs" - marginTop={10} - marginLeft={10} - borderRadius="xl" + marginTop={2} + marginLeft={2} + borderRadius="lg" paddingBottom={5} - zIndex={300} - position="relative" + zIndex="overlay" boxShadow="xl" - maxH={0.92 * globalThis.innerHeight} - marginBottom={10} + maxH={'95vh'} + fontSize="sm" > <Box display="flex" @@ -108,7 +109,7 @@ export const Tweaks = (props: TweakProps) => { paddingTop={1} > <Tooltip label={'Switch to ' + threeDim ? '2D' : '3D' + ' view'}> - <Button onClick={() => setThreeDim(!threeDim)} variant="ghost" zIndex="overlay"> + <Button onClick={() => setThreeDim(!threeDim)} variant="subtle" zIndex="overlay"> {threeDim ? '3D' : '2D'} </Button> </Tooltip> @@ -124,7 +125,7 @@ export const Tweaks = (props: TweakProps) => { setPhysics(initialPhysics) setBehavior(initialBehavior) }} - variant="none" + variant="subtle" size="sm" /> </Tooltip> @@ -132,7 +133,7 @@ export const Tweaks = (props: TweakProps) => { size="sm" icon={<CloseIcon />} aria-label="Close Tweak Panel" - variant="ghost" + variant="subtle" onClick={() => setShowTweaks(false)} /> </Box> diff --git a/components/config.ts b/components/config.ts index e1923f2..fd77ba3 100644 --- a/components/config.ts +++ b/components/config.ts @@ -58,13 +58,13 @@ export const initialVisuals = { nodeOpacity: 1, nodeResolution: 12, labels: 2, - labelScale: 1.5, + labelScale: 1, labelFontSize: 13, labelLength: 40, labelWordWrap: 25, labelLineSpace: 1, highlight: true, - highlightNodeSize: 2, + highlightNodeSize: 1.2, highlightLinkSize: 2, highlightFade: 0.8, highlightAnim: true, @@ -123,6 +123,7 @@ export const initialMouse = { follow: 'never', context: 'right', preview: 'click', + backgroundExitsLocal: false, } export const colorList = [ diff --git a/components/contextmenu.tsx b/components/contextmenu.tsx index 73758a5..bff4861 100644 --- a/components/contextmenu.tsx +++ b/components/contextmenu.tsx @@ -48,20 +48,21 @@ import { BiNetworkChart } from 'react-icons/bi' export default interface ContextMenuProps { background: Boolean - node?: OrgRoamNode + target: OrgRoamNode | null nodeType?: string - coordinates: number[] + coordinates: { [direction: string]: number } handleLocal: (node: OrgRoamNode, add: string) => void menuClose: () => void scope: { nodeIds: string[] } webSocket: any setPreviewNode: any + contextMenuRef: any } export const ContextMenu = (props: ContextMenuProps) => { const { background, - node, + target, nodeType, coordinates, handleLocal, @@ -69,59 +70,65 @@ export const ContextMenu = (props: ContextMenuProps) => { scope, webSocket, setPreviewNode, + contextMenuRef, } = props const { isOpen, onOpen, onClose } = useDisclosure() const copyRef = useRef<any>() return ( <> - <Box - position="absolute" - zIndex="overlay" - left={coordinates[0] + 10} - top={coordinates[1] - 10} - padding={5} - > - <Menu closeOnBlur={false} defaultIsOpen onClose={() => menuClose()}> - <MenuList zIndex="overlay" bgColor="alt.100" borderColor="gray.500" maxWidth="xs"> - {node && ( - <> - <Heading size="sm" isTruncated px={3} py={1}> - {node.title} - </Heading> - <MenuDivider borderColor="gray.500" /> - </> - )} - {scope.nodeIds.length !== 0 && ( - <> - <MenuItem onClick={() => handleLocal(node!, 'add')} icon={<PlusSquareIcon />}> - Expand local graph at node - </MenuItem> - <MenuItem onClick={() => handleLocal(node!, 'replace')} icon={<ViewIcon />}> - Open local graph for this node - </MenuItem> - </> - )} - {!node?.properties.FILELESS ? ( - <MenuItem - icon={<EditIcon />} - onClick={() => openNodeInEmacs(node as OrgRoamNode, webSocket)} - > - Open in Emacs + <Menu defaultIsOpen closeOnBlur={false} onClose={() => menuClose()}> + <MenuList + zIndex="overlay" + bgColor="white" + color="black" + borderColor="gray.500" + maxWidth="xs" + position="absolute" + left={coordinates.left} + top={coordinates.top} + right={coordinates.right} + bottom={coordinates.bottom} + fontSize="xs" + > + {target && ( + <> + <Heading size="xs" isTruncated px={3} py={1}> + {target.title} + </Heading> + <MenuDivider borderColor="gray.500" /> + </> + )} + {scope.nodeIds.length !== 0 && ( + <> + <MenuItem onClick={() => handleLocal(target!, 'add')} icon={<PlusSquareIcon />}> + Expand local graph at node </MenuItem> - ) : ( - <MenuItem icon={<AddIcon />} onClick={() => createNodeInEmacs(node, webSocket)}> - Create node + <MenuItem onClick={() => handleLocal(target!, 'replace')} icon={<BiNetworkChart />}> + Open local graph for this node </MenuItem> - )} - {node?.properties.ROAM_REFS && ( - <MenuItem icon={<ExternalLinkIcon />}>Open in Zotero</MenuItem> - )} - {scope.nodeIds.length === 0 && ( - <MenuItem icon={<BiNetworkChart />} onClick={() => handleLocal(node!, 'replace')}> - Open local graph - </MenuItem> - )} - {/* Doesn't work at the moment + </> + )} + {!target?.properties?.FILELESS ? ( + <MenuItem + icon={<EditIcon />} + onClick={() => openNodeInEmacs(target as OrgRoamNode, webSocket)} + > + Open in Emacs + </MenuItem> + ) : ( + <MenuItem icon={<AddIcon />} onClick={() => createNodeInEmacs(target, webSocket)}> + Create node + </MenuItem> + )} + {target?.properties?.ROAM_REFS && ( + <MenuItem icon={<ExternalLinkIcon />}>Open in Zotero</MenuItem> + )} + {scope.nodeIds.length === 0 && ( + <MenuItem icon={<BiNetworkChart />} onClick={() => handleLocal(target!, 'replace')}> + Open local graph + </MenuItem> + )} + {/* Doesn't work at the moment <MenuItem closeOnSelect={false} closeOnBlur={false}> <Box _hover={{ bg: 'gray.200' }} width="100%"> <Popover @@ -151,27 +158,26 @@ export const ContextMenu = (props: ContextMenuProps) => { </Box> </MenuItem> */} + <MenuItem + icon={<ViewIcon />} + onClick={() => { + setPreviewNode(target) + }} + > + Preview + </MenuItem> + {target?.level === 0 && ( <MenuItem - icon={<ViewIcon />} - onClick={() => { - setPreviewNode(node) - }} + closeOnSelect={false} + icon={<DeleteIcon color="red.500" />} + color="red.500" + onClick={onOpen} > - Preview + Permenantly delete note </MenuItem> - {node?.level === 0 && ( - <MenuItem - closeOnSelect={false} - icon={<DeleteIcon color="red.500" />} - color="red.500" - onClick={onOpen} - > - Permenantly delete note - </MenuItem> - )} - </MenuList> - </Menu> - </Box> + )} + </MenuList> + </Menu> <Modal isCentered isOpen={isOpen} onClose={onClose}> <ModalOverlay /> <ModalContent zIndex="popover"> @@ -180,8 +186,8 @@ export const ContextMenu = (props: ContextMenuProps) => { <ModalBody> <VStack spacing={4} display="flex" alignItems="flex-start"> <Text>This will permanently delete your note:</Text> - <Text fontWeight="bold">{node?.title}</Text> - {node?.level !== 0 && ( + <Text fontWeight="bold">{target?.title}</Text> + {target?.level !== 0 && ( <Text> This will only delete the from this heading until but not including the next node. Your parent file and all other nodes will not be deleted. @@ -207,7 +213,7 @@ export const ContextMenu = (props: ContextMenuProps) => { ml={3} onClick={() => { console.log('aaaaa') - deleteNodeInEmacs(node!, webSocket) + deleteNodeInEmacs(target!, webSocket) onClose() menuClose() }} diff --git a/package-lock.json b/package-lock.json index 1a6f57f..310c69b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "orgast-util-to-string": "^0.3.1", "orgast-util-visit-ids": "^0.3.1", "path": "^0.12.7", + "re-resizable": "^6.9.1", "react": "17.0.2", "react-custom-scrollbars-2": "^4.4.0", "react-dom": "17.0.2", @@ -4813,6 +4814,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==" + }, "node_modules/fast-shallow-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", @@ -7539,6 +7545,18 @@ "node": ">=0.10.0" } }, + "node_modules/re-resizable": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.1.tgz", + "integrity": "sha512-KRYAgr9/j1PJ3K+t+MBhlQ+qkkoLDJ1rs0z1heIWvYbCW/9Vq4djDU+QumJ3hQbwwtzXF6OInla6rOx6hhgRhQ==", + "dependencies": { + "fast-memoize": "^2.5.1" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0", + "react-dom": "^16.13.1 || ^17.0.0" + } + }, "node_modules/react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", @@ -13414,6 +13432,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==" + }, "fast-shallow-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", @@ -15572,6 +15595,14 @@ } } }, + "re-resizable": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.1.tgz", + "integrity": "sha512-KRYAgr9/j1PJ3K+t+MBhlQ+qkkoLDJ1rs0z1heIWvYbCW/9Vq4djDU+QumJ3hQbwwtzXF6OInla6rOx6hhgRhQ==", + "requires": { + "fast-memoize": "^2.5.1" + } + }, "react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", diff --git a/package.json b/package.json index a0b4777..80f47d6 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "orgast-util-to-string": "^0.3.1", "orgast-util-visit-ids": "^0.3.1", "path": "^0.12.7", + "re-resizable": "^6.9.1", "react": "17.0.2", "react-custom-scrollbars-2": "^4.4.0", "react-dom": "17.0.2", diff --git a/pages/_app.tsx b/pages/_app.tsx index f623786..17554b1 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -173,6 +173,11 @@ function SubApp(props: any) { _hover: { bg: `inherit`, border: '1px solid', borderColor: highlightColor }, _active: { color: `inherit`, bg: highlightColor }, }, + subtle: { + color: 'gray.800', + _hover: { bg: `inherit`, color: highlightColor }, + _active: { color: `inherit`, bg: borderColor }, + }, }, }, Accordion: { diff --git a/pages/index.tsx b/pages/index.tsx index a1df7e7..c80fc50 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,52 +1,59 @@ +import { HamburgerIcon } from '@chakra-ui/icons' +import { + Box, + Flex, + Heading, + IconButton, + Slide, + Tooltip, + useDisclosure, + useOutsideClick, + useTheme, +} from '@chakra-ui/react' +import { useAnimation } from '@lilib/hooks' +import { useWindowSize, useWindowWidth } from '@react-hook/window-size' +import * as d3int from 'd3-interpolate' +import { GraphData, LinkObject, NodeObject } from 'force-graph' +import Head from 'next/head' import React, { ComponentPropsWithoutRef, + forwardRef, + useContext, useEffect, + useMemo, useRef, useState, - useMemo, - useContext, - forwardRef, } from 'react' - -import Head from 'next/head' -import { usePersistantState } from '../util/persistant-state' -const d3promise = import('d3-force-3d') -import * as d3int from 'd3-interpolate' - import type { ForceGraph2D as TForceGraph2D, ForceGraph3D as TForceGraph3D, } from 'react-force-graph' +import { BiChart, BiNetworkChart } from 'react-icons/bi' +import { BsReverseLayoutSidebarInsetReverse } from 'react-icons/bs' +import ReconnectingWebSocket from 'reconnecting-websocket' +import SpriteText from 'three-spritetext' +import useUndo from 'use-undo' +import wrap from 'word-wrap' import { OrgRoamGraphReponse, OrgRoamLink, OrgRoamNode } from '../api' -import { GraphData, NodeObject, LinkObject } from 'force-graph' -import Sidebar from '../components/Sidebar' - -import { useWindowSize } from '@react-hook/window-size' -import { useAnimation } from '@lilib/hooks' - -import { Box, Flex, IconButton, useDisclosure, useTheme, WithCSSVar } from '@chakra-ui/react' - import { - initialPhysics, - initialFilter, - initialVisuals, + algos, + colorList, initialBehavior, + initialFilter, initialMouse, - algos, + initialPhysics, + initialVisuals, TagColors, - colorList, } from '../components/config' -import { Tweaks } from '../components/Tweaks' import { ContextMenu } from '../components/contextmenu' - +import Sidebar from '../components/Sidebar' +import { Tweaks } from '../components/Tweaks' +import { usePersistantState } from '../util/persistant-state' import { ThemeContext, ThemeContextProps } from '../util/themecontext' -import SpriteText from 'three-spritetext' -import wrap from 'word-wrap' -import ReconnectingWebSocket from 'reconnecting-websocket' +import { openNodeInEmacs } from '../util/webSocketFunctions' + +const d3promise = import('d3-force-3d') -import { deleteNodeInEmacs, openNodeInEmacs, createNodeInEmacs } from '../util/webSocketFunctions' -import { ChevronLeftIcon, HamburgerIcon } from '@chakra-ui/icons' -import useUndo from 'use-undo' // react-force-graph fails on import when server-rendered // https://github.com/vasturiano/react-force-graph/issues/155 const ForceGraph2D = ( @@ -450,53 +457,91 @@ export function GraphPage() { }, 50) }, [scope.nodeIds]) + const [windowWidth, windowHeight] = useWindowSize() + + const contextMenuRef = useRef<any>() + const [contextMenuTarget, setContextMenuTarget] = useState<OrgRoamNode | null>(null) + const [contextPos, setContextPos] = useState({ + left: 0, + top: 0, + right: undefined, + bottom: undefined, + }) + + const contextMenu = useDisclosure() + useOutsideClick({ + ref: contextMenuRef, + handler: () => { + console.log('click') + contextMenu.onClose() + }, + }) + + const openContextMenu = (node: OrgRoamNode, event: any, coords?: number[]) => { + coords + ? setContextPos(coords) + : setContextPos({ left: event.pageX, top: event.pageY, right: undefined, bottom: undefined }) + setContextMenuTarget(node) + contextMenu.onOpen() + } + + const handleLocal = (node: OrgRoamNode, add: string) => { + if (add === 'replace') { + setScope({ nodeIds: [node.id] }) + return + } + if (scope.nodeIds.includes(node.id as string)) { + return + } + setScope((currentScope: Scope) => ({ + ...currentScope, + nodeIds: [...currentScope.nodeIds, node.id as string], + })) + return + } + + const [mainItem, setMainItem] = useState({ + type: 'Graph', + title: 'Graph', + icon: <BiNetworkChart />, + }) + + const [mainWindowWidth, setMainWindowWidth] = usePersistantState<number>( + 'mainWindowWidth', + windowWidth, + ) if (!graphData) { return null } return ( - <Box display="flex" alignItems="flex-start" flexDirection="row" height="100%" overflow="hidden"> - <Box - display="flex" - justifyContent="space-between" - flexDirection="row" - height="100%" - width="100%" - > - <Tweaks - {...{ - physics, - setPhysics, - threeDim, - setThreeDim, - filter, - setFilter, - visuals, - setVisuals, - mouse, - setMouse, - behavior, - setBehavior, - tagColors, - setTagColors, - }} - tags={tagsRef.current} - /> - <Flex height="100%" flexDirection="column" marginLeft="auto"> - {!isOpen && ( - <IconButton - icon={<HamburgerIcon />} - aria-label="Open org-viewer" - zIndex={2} - onClick={onOpen} - variant="ghost" - marginTop={10} - mr={8} - /> - )} - </Flex> - </Box> - <Flex position="absolute" alignItems="top" overflow="hidden"> + <Box + display="flex" + alignItems="flex-start" + flexDirection="row" + height="100vh" + overflow="hidden" + > + <Tweaks + {...{ + physics, + setPhysics, + threeDim, + setThreeDim, + filter, + setFilter, + visuals, + setVisuals, + mouse, + setMouse, + behavior, + setBehavior, + tagColors, + setTagColors, + }} + tags={tagsRef.current} + /> + <Box position="absolute"> <Graph ref={graphRef} nodeById={nodeByIdRef.current!} @@ -516,8 +561,58 @@ export function GraphPage() { tagColors, setPreviewNode, sidebarHighlightedNode, + windowWidth, + windowHeight, + openContextMenu, + contextMenu, + handleLocal, + mainWindowWidth, + setMainWindowWidth, }} /> + </Box> + <Box position="relative" zIndex={4} width="100%"> + <Flex className="headerBar" h={10} flexDir="column"> + <Flex alignItems="center" h={10} justifyContent="flex-end"> + {/* <Flex flexDir="row" alignItems="center"> + * <Box color="blue.500" bgColor="alt.100" h="100%" p={3} mr={4}> + * {mainItem.icon} + * </Box> + * <Heading size="sm">{mainItem.title}</Heading> + * </Flex> */} + <Flex height="100%" flexDirection="row"> + {scope.nodeIds.length > 0 && ( + <Tooltip label="Return to main graph"> + <IconButton + m={1} + icon={<BiNetworkChart />} + aria-label="Exit local mode" + onClick={() => + setScope((currentScope: Scope) => ({ + ...currentScope, + nodeIds: [], + })) + } + variant="subtle" + /> + </Tooltip> + )} + <Tooltip label={isOpen ? 'Close sidebar' : 'Open sidebar'}> + <IconButton + m={1} + // eslint-disable-next-line react/jsx-no-undef + icon={<BsReverseLayoutSidebarInsetReverse />} + aria-label="Close file-viewer" + variant="subtle" + onClick={isOpen ? onClose : onOpen} + /> + </Tooltip> + </Flex> + </Flex> + </Flex> + </Box> + + <Box position="relative" zIndex={4}> <Sidebar {...{ isOpen, @@ -529,13 +624,33 @@ export function GraphPage() { canRedo, previousPreviewNode, nextPreviewNode, + resetPreviewNode, setSidebarHighlightedNode, + openContextMenu, + scope, + setScope, + windowWidth, }} nodeById={nodeByIdRef.current!} linksByNodeId={linksByNodeIdRef.current!} nodeByCite={nodeByCiteRef.current!} /> - </Flex> + </Box> + {contextMenu.isOpen && ( + <div ref={contextMenuRef}> + <ContextMenu + //contextMenuRef={contextMenuRef} + scope={scope} + target={contextMenuTarget} + background={false} + coordinates={contextPos} + handleLocal={handleLocal} + menuClose={contextMenu.onClose.bind(contextMenu)} + webSocket={WebSocketRef.current} + setPreviewNode={setPreviewNode} + /> + </div> + )} </Box> ) } @@ -557,6 +672,14 @@ export interface GraphProps { tagColors: { [tag: string]: string } setPreviewNode: any sidebarHighlightedNode: OrgRoamNode | null + windowWidth: number + windowHeight: number + setContextMenuTarget: any + openContextMenu: any + contextMenu: any + handleLocal: any + mainWindowWidth: number + setMainWindowWidth: any } export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { @@ -577,47 +700,25 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { tagColors, setPreviewNode, sidebarHighlightedNode, + windowWidth, + windowHeight, + setContextMenuTarget, + openContextMenu, + contextMenu, + handleLocal, } = props - // react-force-graph does not track window size - // https://github.com/vasturiano/react-force-graph/issues/233 - // does not work below a certain width - const [windowWidth, windowHeight] = useWindowSize() - const [hoverNode, setHoverNode] = useState<NodeObject | null>(null) - const [rightClickedNode, setRightClickedNode] = useState<OrgRoamNode | null>(null) - const [contextPos, setContextPos] = useState([0, 0]) - const theme = useTheme() const { emacsTheme } = useContext<ThemeContextProps>(ThemeContext) - const handleLocal = (node: OrgRoamNode, add: string) => { - if (add === 'replace') { - setScope({ nodeIds: [node.id] }) - return - } - if (scope.nodeIds.includes(node.id as string)) { - return - } - setScope((currentScope: Scope) => ({ - ...currentScope, - nodeIds: [...currentScope.nodeIds, node.id as string], - })) - return - } - - const contextMenu = useDisclosure() - - const openContextMenu = (node: OrgRoamNode, event: any) => { - setContextPos([event.pageX, event.pageY]) - setRightClickedNode(node) - contextMenu.onOpen() - } - const handleClick = (click: string, node: OrgRoamNode, event: any) => { switch (click) { + case mouse.preview: { + setPreviewNode(node) + } case mouse.local: { handleLocal(node, behavior.localSame) break @@ -629,9 +730,6 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { case mouse.context: { openContextMenu(node, event) } - case mouse.preview: { - setPreviewNode(node) - } default: break } @@ -1062,7 +1160,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { backgroundColor: theme.colors.gray[visuals.backgroundColor], warmupTicks: scope.nodeIds.length === 1 ? 100 : scope.nodeIds.length > 1 ? 20 : 0, onZoom: ({ k, x, y }) => setZoom(k), - nodeLabel: (node) => (node as OrgRoamNode).title, + //nodeLabel: (node) => (node as OrgRoamNode).title, nodeColor: (node) => { return getNodeColor(node as OrgRoamNode, theme) }, @@ -1199,7 +1297,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { onNodeClick: (nodeArg: NodeObject, event: any) => { const node = nodeArg as OrgRoamNode - contextMenu.onClose() + //contextMenu.onClose() const doubleClickTimeBuffer = 200 const isDoubleClick = event.timeStamp - lastNodeClickRef.current < doubleClickTimeBuffer lastNodeClickRef.current = event.timeStamp @@ -1215,17 +1313,19 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { return handleClick('click', node, event) }, doubleClickTimeBuffer) }, - onBackgroundClick: () => { - contextMenu.onClose() - setHoverNode(null) - if (scope.nodeIds.length === 0) { - return - } - setScope((currentScope: Scope) => ({ - ...currentScope, - nodeIds: [], - })) - }, + /* onBackgroundClick: () => { + * contextMenu.onClose() + * setHoverNode(null) + * if (scope.nodeIds.length === 0) { + * return + * } + * if (mouse.backgroundExitsLocal) { + * setScope((currentScope: Scope) => ({ + * ...currentScope, + * nodeIds: [], + * })) + * } + * }, */ onNodeHover: (node) => { if (!visuals.highlight) { return @@ -1243,7 +1343,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { handleClick('right', node, event) }, onNodeDrag: (node) => { - contextMenu.onClose() + //contextMenu.onClose() setHoverNode(node) setDragging(true) }, @@ -1254,20 +1354,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { } return ( - <Box overflow="hidden"> - {contextMenu.isOpen && ( - <ContextMenu - scope={scope} - node={rightClickedNode!} - nodeType={rightClickedNode?.id} - background={false} - coordinates={contextPos} - handleLocal={handleLocal} - menuClose={contextMenu.onClose.bind(contextMenu)} - webSocket={webSocket} - setPreviewNode={setPreviewNode} - /> - )} + <Box overflow="hidden" onClick={contextMenu.onClose}> {threeDim ? ( <ForceGraph3D ref={graphRef} diff --git a/util/processOrg.tsx b/util/processOrg.tsx index 1bab2cd..05142dd 100644 --- a/util/processOrg.tsx +++ b/util/processOrg.tsx @@ -7,7 +7,7 @@ import extractKeywords from 'uniorg-extract-keywords' import attachments from 'uniorg-attach' // rehypeHighlight does not have any types // @ts-expect-error -import highlight from 'rehype-highlight' +// import highlight from 'rehype-highlight' import katex from 'rehype-katex' import 'katex/dist/katex.css' import rehype2react from 'rehype-react' @@ -24,6 +24,7 @@ export interface ProcessedOrgProps { previewText: any nodeByCite: NodeByCite setSidebarHighlightedNode: any + openContextMenu: any } export const ProcessedOrg = (props: ProcessedOrgProps) => { @@ -34,6 +35,7 @@ export const ProcessedOrg = (props: ProcessedOrgProps) => { previewText, nodeByCite, previewNode, + openContextMenu, } = props const processor = unified() @@ -42,7 +44,7 @@ export const ProcessedOrg = (props: ProcessedOrgProps) => { .use(attachments) .use(uniorgSlug) .use(uniorg2rehype) - .use(highlight) + // .use(highlight) .use(katex) .use(rehype2react, { createElement: React.createElement, @@ -56,6 +58,7 @@ export const ProcessedOrg = (props: ProcessedOrgProps) => { href={`${href as string}`} nodeById={nodeById} setPreviewNode={setPreviewNode} + openContextMenu={openContextMenu} > {children} </PreviewLink> diff --git a/util/uniorg.tsx b/util/uniorg.tsx index 217bad1..8802dd1 100644 --- a/util/uniorg.tsx +++ b/util/uniorg.tsx @@ -9,10 +9,18 @@ export interface UniOrgProps { setPreviewNode: any nodeByCite: NodeByCite setSidebarHighlightedNode: any + openContextMenu: any } export const UniOrg = (props: UniOrgProps) => { - const { setSidebarHighlightedNode, nodeById, nodeByCite, previewNode, setPreviewNode } = props + const { + openContextMenu, + setSidebarHighlightedNode, + nodeById, + nodeByCite, + previewNode, + setPreviewNode, + } = props const [previewText, setPreviewText] = useState('') @@ -44,6 +52,7 @@ export const UniOrg = (props: UniOrgProps) => { previewText, nodeByCite, setSidebarHighlightedNode, + openContextMenu, }} /> )} |