diff options
author | Thomas F. K. Jorna <[email protected]> | 2021-10-01 19:11:08 +0200 |
---|---|---|
committer | Thomas F. K. Jorna <[email protected]> | 2021-10-01 19:11:08 +0200 |
commit | 56eeb1fc6b03c4e8ed9405e301c7edc7be5fd5d9 (patch) | |
tree | 0c7e889f2d749abad3d65a16d4ee0449002c4d89 | |
parent | 56e81e56ad181129c9f36b75e5ca076162b7cac7 (diff) |
feat: basic preview feature
-rw-r--r-- | components/Sidebar/index.tsx | 273 | ||||
-rw-r--r-- | components/contextmenu.tsx | 22 | ||||
-rw-r--r-- | pages/index.tsx | 137 | ||||
-rw-r--r-- | pages/uniorg.tsx | 3 | ||||
-rw-r--r-- | util/uniorg.tsx | 8 | ||||
-rw-r--r-- | util/webSocketFunctions.ts | 25 |
6 files changed, 384 insertions, 84 deletions
diff --git a/components/Sidebar/index.tsx b/components/Sidebar/index.tsx new file mode 100644 index 0000000..64436f2 --- /dev/null +++ b/components/Sidebar/index.tsx @@ -0,0 +1,273 @@ +import React, { useContext, useEffect, useState } from 'react' + +import { UniOrg } from "../../util/uniorg" +import { getOrgText } from "../../util/webSocketFunctions" + +import { + Button, + Slide, + VStack, + Flex, + Heading, + Box, + CloseButton, + Text, + Drawer, + DrawerOverlay, + DrawerHeader, + DrawerBody, + DrawerCloseButton, + DrawerContent, + DrawerFooter, + IconButton, +} from '@chakra-ui/react' +import { Scrollbars } from 'react-custom-scrollbars-2' +import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons' + +import { GraphData, NodeObject, LinkObject } from 'force-graph' +import { OrgRoamNode } from '../../api' +import {ThemeContext} from '../../util/themecontext' + +export interface SidebarProps { + isOpen: boolean + onClose: any + openNode: string + nodeById: any + previewNode: NodeObject, + orgText: string +} + +const Sidebar = (props: SidebarProps) => { + + const { isOpen, onClose, openNode, nodeById, previewNode, orgText } = props + + const {highlightColor}= useContext(ThemeContext) + + useEffect(() => { + (async () => { + if (!openNode) { + return + } + if (openNode === 'nil') { + return + } + })() + + const previewRoamNode = previewNode as OrgRoamNode + }, [previewNode]) + + //maybe want to close it when clicking outside, but not sure + //const outsideClickRef = useRef(); + return ( + <Slide direction="right" in={isOpen} style={{ zIndex: 200, width: 500 }} unmountOnExit> + <Flex flexDirection="row" height="100%"> + <IconButton + icon={<ChevronRightIcon height={30} />} + colorScheme="white" + aria-label="Close file-viewer" + height={100} + variant="ghost" + marginRight={-2} + bg="alt.100" + onClick={onClose} + marginTop={20} + /> + <Box + color="gray.800" + bg="alt.100" + boxShadow="xl" + w={500} + height="98%" + position="relative" + zIndex="overlay" + marginTop={10} + paddingBottom={15} + borderRadius="xl" + right={-2} + > + <Flex + justifyContent="space-between" + padding={4} + paddingTop={10} + paddingLeft={10} + width="80%" + alignItems="center" + color="black" + > + <Heading size="md">{previewNode.title}</Heading> + </Flex> + <Scrollbars + //autoHeight + //autoHeightMax={600} + autoHide + renderThumbVertical={({ style, ...props }) => ( + <Box + {...props} + style={{ + ...style, + borderRadius: 10, + }} + bg={highlightColor} + /> + )} + > + <VStack alignItems="left" bg="alt.100" paddingLeft={10} paddingRight={10}> + <Box + className="org" + sx={{ + h1: { display: 'none' }, + h2: { + fontSize: '20', + fontWeight: 'bold !important', + marginBottom: '1em', + color: 'black', + }, + h3: { + fontSize: '18', + fontWeight: '600 !important', + marginBottom: '.5em', + }, + h4: { + fontSize: '16', + fontWeight: '500 !important', + marginBottom: '.25em', + fontStyle: 'italic', + }, + a: { + color: highlightColor, + pointerEvents: 'none', + }, + ol: { + paddingLeft: '5', + }, + ul: { + paddingLeft: '5', + }, + p: { + paddingBottom: '.5em', + }, + ".katex-html": {visibility: 'hidden', + width: '0px', position: 'absolute'}, + '#content': { textAlign: 'justify' }, + '.title': { + textAlign: 'center', + marginBottom: '.2em', + }, + '.subtitle': { + textAlign: 'center', + fontSize: 'medium', + fontWeight: 'bold', + marginTop: 0, + }, + '.todo': { fontFamily: 'monospace', color: 'red' }, + '.equationContainer': { + display: 'table', + textAlign: 'center', + width: '100%', + }, + '.equation': { + verticalAlign: 'middle', + }, + '.equation-label': { + display: 'tableCell', + textAlign: 'right', + verticalAlign: 'middle', + }, + '.inlinetask': { + padding: '10px', + border: '2px solid gray', + margin: '10px', + background: '#ffffcc', + }, + '#org-div-home-and-up': { + textAlign: 'right', + fontSize: '70 % ', + whiteSpace: 'nowrap', + }, + textarea: { overflowX: 'auto' }, + '.linenr': { fontSize: 'smaller' }, + '.code-highlighted': { backgroundColor: '#ffff00' }, + '.org-info-js_info-navigation': { borderStyle: 'none' }, + '#org-info-js_console-label': { + fontSize: '10px', + fontWeight: 'bold', + whiteSpace: 'nowrap', + }, + '.org-info-js_search-highlight': { + backgroundColor: '#ffff00', + color: '#000000', + fontWeight: 'bold', + }, + '.org-svg': { width: '90%' }, + '.done': { fontFamily: 'monospace', color: 'green' }, + '.priority': { fontFamily: 'monospace', color: 'orange' }, + '.tag': { + backgroundColor: '#eee', + fontFamily: 'monospace', + padding: '2px', + fontSize: '80%', + fontWeight: 'normal', + }, + '.timestamp': { color: '#bebebe' }, + '.timestamp-kwd': { color: '#5f9ea0' }, + '.org-right': { marginLeft: 'auto', marginRight: '0px', textAlign: 'right' }, + '.org-left': { marginLeft: '0px', marginRight: 'auto', textAlign: 'left' }, + '.org-center': { marginLeft: 'auto', marginRight: 'auto', textAlign: 'center' }, + '.underline': { textDecoration: 'underline' }, + '#postamble p': { fontSize: '90%', margin: '.2em' }, + '#preamble p': { fontSize: '90%', margin: '.2em' }, + 'p.verse': { marginLeft: '3%' }, + pre: { + border: '1px solid #e6e6e6', + borderRadius: '3px', + backgroundColor: '#f2f2f2', + padding: '8pt', + fontFamily: 'monospace', + overflow: 'auto', + margin: '1.2em', + }, + 'pre.src': { + position: 'relative', + overflow: 'auto', + }, + 'pre.src:before': { + display: 'none', + position: 'absolute', + top: '-8px', + right: '12px', + padding: '3px', + color: '#555', + backgroundColor: '#f2f2f299', + }, + 'caption.t-above': { captionSide: 'top' }, + 'caption.t-bottom': { captionSide: 'bottom' }, + 'th.org-right': { textAlign: 'center' }, + 'th.org-left': { textAlign: 'center' }, + 'th.org-center': { textAlign: 'center' }, + 'td.org-right': { textAlign: 'right' }, + 'td.org-left': { textAlign: 'left' }, + 'td.org-center': { textAlign: 'center' }, + '.footpara': { display: 'inline' }, + '.footdef': { marginBottom: '1em' }, + '.figure': { padding: '1em' }, + '.figure p': { textAlign: 'center' }, + }} + > + <UniOrg orgText={orgText} /> + </Box> + </VStack> + </Scrollbars> + </Box> + </Flex> + </Slide> + ) +} + +export default Sidebar + + + + +/* <Box marginLeft="auto" zIndex={5000} bg="alt.100" maxHeight="100%" width= "30%" padding={8} borderRadius={2}> + <Heading size="sm">{previewNode.title}</Heading> +</Box> */ diff --git a/components/contextmenu.tsx b/components/contextmenu.tsx index 39b1895..ff3dd81 100644 --- a/components/contextmenu.tsx +++ b/components/contextmenu.tsx @@ -43,6 +43,7 @@ import { } from '@chakra-ui/icons' import { OrgRoamGraphReponse, OrgRoamLink, OrgRoamNode } from '../api' +import { getOrgText, deleteNodeInEmacs, openNodeInEmacs, createNodeInEmacs } from "../util/webSocketFunctions" export default interface ContextMenuProps { background: Boolean @@ -50,12 +51,10 @@ export default interface ContextMenuProps { nodeType?: string coordinates: number[] handleLocal: (node: OrgRoamNode, add: string) => void - openNodeInEmacs: (node: OrgRoamNode) => void menuClose: () => void scope: { nodeIds: string[] } - deleteNodeInEmacs: (node: OrgRoamNode) => void - createNodeInEmacs: (node: OrgRoamNode) => void - getOrgText: any + webSocket: any + setPreviewNode: any, } export const ContextMenu = (props: ContextMenuProps) => { @@ -67,10 +66,8 @@ export const ContextMenu = (props: ContextMenuProps) => { handleLocal, menuClose, scope, - openNodeInEmacs, - deleteNodeInEmacs, - createNodeInEmacs, - getOrgText, + webSocket, + setPreviewNode, } = props const { isOpen, onOpen, onClose } = useDisclosure() const copyRef = useRef<any>() @@ -104,11 +101,11 @@ export const ContextMenu = (props: ContextMenuProps) => { </> )} {!node?.properties.FILELESS ? ( - <MenuItem icon={<EditIcon />} onClick={() => openNodeInEmacs(node as OrgRoamNode)}> + <MenuItem icon={<EditIcon />} onClick={() => openNodeInEmacs(node as OrgRoamNode, webSocket)}> Open in Emacs </MenuItem> ) : ( - <MenuItem icon={<AddIcon />} onClick={() => createNodeInEmacs(node)}> + <MenuItem icon={<AddIcon />} onClick={() => createNodeInEmacs(node, webSocket)}> Create node </MenuItem> )} @@ -159,7 +156,8 @@ export const ContextMenu = (props: ContextMenuProps) => { Permenantly delete note </MenuItem> )} - <MenuItem onClick={() => getOrgText(node)}>Preview</MenuItem> + <MenuItem onClick={() => {getOrgText(node!, webSocket) + setPreviewNode(node)}}>Preview</MenuItem> </MenuList> </Menu> </Box> @@ -198,7 +196,7 @@ export const ContextMenu = (props: ContextMenuProps) => { ml={3} onClick={() => { console.log('aaaaa') - deleteNodeInEmacs(node!) + deleteNodeInEmacs(node!, webSocket) onClose() menuClose() }} diff --git a/pages/index.tsx b/pages/index.tsx index 5b3c35d..ad01741 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -17,11 +17,12 @@ import type { } from 'react-force-graph' 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, useDisclosure, useTheme } from '@chakra-ui/react' +import { Box, Flex, IconButton, useDisclosure, useTheme } from '@chakra-ui/react' import { initialPhysics, @@ -41,6 +42,8 @@ import SpriteText from 'three-spritetext' import wrap from 'word-wrap' import ReconnectingWebSocket from 'reconnecting-websocket' +import { getOrgText, deleteNodeInEmacs, openNodeInEmacs, createNodeInEmacs } from "../util/webSocketFunctions" +import { ChevronLeftIcon } from '@chakra-ui/icons' // react-force-graph fails on import when server-rendered // https://github.com/vasturiano/react-force-graph/issues/155 const ForceGraph2D = ( @@ -84,11 +87,9 @@ export function GraphPage() { const [emacsNodeId, setEmacsNodeId] = useState<string | null>(null) const [behavior, setBehavior] = usePersistantState('behavior', initialBehavior) const [mouse, setMouse] = usePersistantState('mouse', initialMouse) - const [orgText, setOrgText] = useState('') - - useEffect(() => { - console.log(orgText) - }, [orgText]) + const [orgText, setOrgText] = useState("") + const [previewNode, setPreviewNode] = useState<NodeObject>({}) + const { isOpen, onOpen, onClose } = useDisclosure() const nodeByIdRef = useRef<NodeById>({}) const linksByNodeIdRef = useRef<LinksByNodeId>({}) @@ -364,7 +365,7 @@ export function GraphPage() { case 'graphdata': return updateGraphData(message.data) case 'orgText': - return setOrgText(message.data) + return setOrgText(message.data) case 'theme': return setEmacsTheme(message.data) case 'command': @@ -414,25 +415,50 @@ export function GraphPage() { return ( <Box display="flex" alignItems="flex-start" flexDirection="row" height="100%" overflow="hidden"> - <Tweaks - {...{ - physics, - setPhysics, - threeDim, - setThreeDim, - filter, - setFilter, - visuals, - setVisuals, - mouse, - setMouse, - behavior, - setBehavior, - tagColors, - setTagColors, + <Box display="flex" justifyContent="space-between" flexDirection="row" height="100%" width="100%"> + <Sidebar + {...{ + isOpen, + onClose, + previewNode, + orgText, }} - tags={tagsRef.current} - /> + /> + <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={<ChevronLeftIcon />} + height={100} + aria-label="Open org-viewer" + position="relative" + zIndex="overlay" + colorScheme="purple" + onClick={onOpen} + variant="ghost" + marginTop={10} + /> + )} + </Flex> + </Box> <Box position="absolute" alignItems="top" overflow="hidden"> <Graph ref={graphRef} @@ -451,6 +477,7 @@ export function GraphPage() { scope, setScope, tagColors, + setPreviewNode, }} /> </Box> @@ -473,9 +500,10 @@ export interface GraphProps { setScope: any webSocket: any tagColors: { [tag: string]: string } + setPreviewNode: any } -export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { +export const Graph = forwardRef(function(props: GraphProps, graphRef: any) { const { physics, graphData, @@ -491,6 +519,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { setScope, webSocket, tagColors, + setPreviewNode, } = props // react-force-graph does not track window size @@ -522,28 +551,6 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { return } - const sendMessageToEmacs = (command: string, data: {}) => { - webSocket.send(JSON.stringify({ command: command, data: data })) - } - - const getOrgText = (node: OrgRoamNode) => { - sendMessageToEmacs('getText', { id: node.id }) - } - - const openNodeInEmacs = (node: OrgRoamNode) => { - sendMessageToEmacs('open', { id: node.id }) - } - - const deleteNodeInEmacs = (node: OrgRoamNode) => { - if (node.level !== 0) { - return - } - sendMessageToEmacs('delete', { id: node.id, file: node.file }) - } - - const createNodeInEmacs = (node: OrgRoamNode) => { - sendMessageToEmacs('create', { id: node.id, title: node.title, ref: node.properties.ROAM_REFS }) - } const contextMenu = useDisclosure() @@ -560,7 +567,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { break } case mouse.follow: { - openNodeInEmacs(node) + openNodeInEmacs(node, webSocket) break } case mouse.context: { @@ -772,7 +779,7 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { ]) useEffect(() => { - ;(async () => { + ; (async () => { const fg = graphRef.current const d3 = await d3promise if (physics.gravityOn && !(scope.nodeIds.length && !physics.gravityLocal)) { @@ -934,15 +941,15 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { return needsHighlighting ? getThemeColor(visuals.citeNodeColor) : highlightColors[visuals.citeNodeColor][visuals.backgroundColor]( - visuals.highlightFade * opacity, - ) + visuals.highlightFade * opacity, + ) } if (visuals.refNodeColor && node.properties.ROAM_REFS) { return needsHighlighting ? getThemeColor(visuals.refNodeColor) : highlightColors[visuals.refNodeColor][visuals.backgroundColor]( - visuals.highlightFade * opacity, - ) + visuals.highlightFade * opacity, + ) } if (!needsHighlighting) { return highlightColors[getNodeColorById(node.id as string)][visuals.backgroundColor]( @@ -1090,20 +1097,20 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { if (visuals.refLinkColor && roamLink.type === 'ref') { return needsHighlighting && (visuals.refLinkHighlightColor || visuals.linkHighlight) ? highlightColors[visuals.refLinkColor][ - visuals.refLinkHighlightColor || visuals.linkHighlight - ](opacity) + visuals.refLinkHighlightColor || visuals.linkHighlight + ](opacity) : highlightColors[visuals.refLinkColor][visuals.backgroundColor]( - visuals.highlightFade * opacity, - ) + visuals.highlightFade * opacity, + ) } if (visuals.citeLinkColor && roamLink.type?.includes('cite')) { return needsHighlighting && (visuals.citeLinkHighlightColor || visuals.linkHighlight) ? highlightColors[visuals.citeLinkColor][ - visuals.citeLinkHighlightColor || visuals.linkHighlight - ](opacity) + visuals.citeLinkHighlightColor || visuals.linkHighlight + ](opacity) : highlightColors[visuals.citeLinkColor][visuals.backgroundColor]( - visuals.highlightFade * opacity, - ) + visuals.highlightFade * opacity, + ) } return getLinkColor(sourceId as string, targetId as string, needsHighlighting) @@ -1192,10 +1199,8 @@ export const Graph = forwardRef(function (props: GraphProps, graphRef: any) { coordinates={contextPos} handleLocal={handleLocal} menuClose={contextMenu.onClose.bind(contextMenu)} - openNodeInEmacs={openNodeInEmacs} - deleteNodeInEmacs={deleteNodeInEmacs} - createNodeInEmacs={createNodeInEmacs} - getOrgText={getOrgText} + webSocket={webSocket} + setPreviewNode={setPreviewNode} /> )} {threeDim ? ( diff --git a/pages/uniorg.tsx b/pages/uniorg.tsx index 65786c9..0c77cdb 100644 --- a/pages/uniorg.tsx +++ b/pages/uniorg.tsx @@ -11,7 +11,7 @@ export interface uniorgProps { orgText: string } -const UniOrg = (props: uniorgProps) => { +export const UniOrg = (props: uniorgProps) => { const { orgText } = props const processor = unified() .use(uniorgParse) @@ -22,4 +22,3 @@ const UniOrg = (props: uniorgProps) => { return <div>processor.processSync(orgText)</div> } -export default UniOrg diff --git a/util/uniorg.tsx b/util/uniorg.tsx index e4d8dfd..ba86a89 100644 --- a/util/uniorg.tsx +++ b/util/uniorg.tsx @@ -7,11 +7,11 @@ import katex from 'rehype-katex' import rehype2react from 'rehype-react' import React from 'react' -export interface uniorgProps { +export interface UniOrgProps { orgText: string } -const uniorg = (props: uniorgProps) => { +export const UniOrg = (props: UniOrgProps) => { const { orgText } = props const processor = unified() .use(uniorgParse) @@ -19,7 +19,7 @@ const uniorg = (props: uniorgProps) => { .use(katex) .use(rehype2react, { createElement: React.createElement }) - return processor.processSync(orgText) + console.log(processor.processSync(orgText)) + return <div> {processor.processSync(orgText).result}</div> } -export default uniorg diff --git a/util/webSocketFunctions.ts b/util/webSocketFunctions.ts new file mode 100644 index 0000000..8e2bd7e --- /dev/null +++ b/util/webSocketFunctions.ts @@ -0,0 +1,25 @@ +import { OrgRoamNode } from '../api' +import ReconnectingWebSocket from 'reconnecting-websocket' + +export function sendMessageToEmacs(command: string, data: {}, webSocket: ReconnectingWebSocket) { + webSocket.send(JSON.stringify({ command: command, data: data })) +} + +export function getOrgText(node: OrgRoamNode, webSocket: ReconnectingWebSocket) { + sendMessageToEmacs('getText', { id: node.id }, webSocket) +} + +export function openNodeInEmacs(node: OrgRoamNode, webSocket: ReconnectingWebSocket) { + sendMessageToEmacs('open', { id: node.id }, webSocket) +} + +export function deleteNodeInEmacs(node: OrgRoamNode, webSocket: ReconnectingWebSocket) { + if (node.level !== 0) { + return + } + sendMessageToEmacs('delete', { id: node.id, file: node.file }, webSocket) +} + +export function createNodeInEmacs(node: OrgRoamNode, webSocket: ReconnectingWebSocket) { + sendMessageToEmacs('create', { id: node.id, title: node.title, ref: node.properties.ROAM_REFS }, webSocket) +} |