summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas F. K. Jorna <[email protected]>2021-09-21 21:00:21 +0200
committerThomas F. K. Jorna <[email protected]>2021-09-21 21:00:21 +0200
commit075d3831ffae63f128bcaabf9fc5e70ade41ad33 (patch)
tree79495b8cd6f6fe2bcb67bbaf3cac6811fc70e1b1
parentcffebb920e7b29985fa90979dbdb56f795980f41 (diff)
chore: separated out all tweak panels
-rw-r--r--components/BehaviorPanel.tsx148
-rw-r--r--components/CitationsPanel.tsx102
-rw-r--r--components/ColorMenu.tsx71
-rw-r--r--components/ColorsPanel.tsx237
-rw-r--r--components/DropDownMenu.tsx28
-rw-r--r--components/EnableSection.tsx31
-rw-r--r--components/FilterPanel.tsx171
-rw-r--r--components/HighlightingPanel.tsx116
-rw-r--r--components/InfoTooltip.tsx17
-rw-r--r--components/LabelsPanel.tsx139
-rw-r--r--components/NodesNLinksPanel.tsx131
-rw-r--r--components/PhysicsPanel.tsx143
-rw-r--r--components/SliderWithInfo.tsx48
-rw-r--r--components/TagColorPanel.tsx138
-rw-r--r--components/TagPanel.tsx59
-rw-r--r--components/VisualsPanel.tsx117
-rw-r--r--components/tweaks.tsx212
17 files changed, 1705 insertions, 203 deletions
diff --git a/components/BehaviorPanel.tsx b/components/BehaviorPanel.tsx
new file mode 100644
index 0000000..09558af
--- /dev/null
+++ b/components/BehaviorPanel.tsx
@@ -0,0 +1,148 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Button,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ StackDivider,
+ VStack,
+ Text,
+} from '@chakra-ui/react'
+import React from 'react'
+import { initialBehavior, initialMouse } from './config'
+import { InfoTooltip } from './InfoTooltip'
+import { SliderWithInfo } from './SliderWithInfo'
+
+export interface BehaviorPanelProps {
+ behavior: typeof initialBehavior
+ setBehavior: any
+ mouse: typeof initialMouse
+ setMouse: any
+}
+export const BehaviorPanel = (props: BehaviorPanelProps) => {
+ const { behavior, setBehavior, mouse, setMouse } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ paddingLeft={7}
+ color="gray.800"
+ >
+ <Flex alignItems="center" justifyContent="space-between">
+ <Flex>
+ <Text>Expand Node</Text>
+ <InfoTooltip infoText="View only the node and its direct neighbors" />
+ </Flex>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} rightIcon={<ChevronDownIcon />} colorScheme="" color="black">
+ <Text>
+ {mouse.local ? mouse.local[0]!.toUpperCase() + mouse.local!.slice(1) : 'Never'}
+ </Text>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList zIndex="popover" bgColor="gray.200">
+ <MenuItem onClick={() => setMouse({ ...mouse, local: '' })}>Never</MenuItem>
+ <MenuItem onClick={() => setMouse({ ...mouse, local: 'click' })}>Click</MenuItem>
+ <MenuItem onClick={() => setMouse({ ...mouse, local: 'double' })}>
+ Double Click
+ </MenuItem>
+ <MenuItem onClick={() => setMouse({ ...mouse, local: 'right' })}>
+ Right Click
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Open in Emacs</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} rightIcon={<ChevronDownIcon />} colorScheme="" color="black">
+ <Text>
+ {mouse.follow ? mouse.follow[0]!.toUpperCase() + mouse.follow!.slice(1) : 'Never'}
+ </Text>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList bgColor="gray.200" zIndex="popover">
+ <MenuItem onClick={() => setMouse({ ...mouse, follow: '' })}>Never</MenuItem>
+ <MenuItem onClick={() => setMouse({ ...mouse, follow: 'click' })}>Click</MenuItem>
+ <MenuItem onClick={() => setMouse({ ...mouse, follow: 'double' })}>
+ Double Click
+ </MenuItem>
+ <MenuItem onClick={() => setMouse({ ...mouse, follow: 'right' })}>
+ Right Click
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Follow Emacs by...</Text>
+ <Menu isLazy 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: 'color' })}>
+ Just coloring the currently opened node
+ </MenuItem>
+ <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>Local graph</Text>
+ <InfoTooltip infoText="When in local mode and clicking a new node, should I add that node's local graph or open the new one?" />
+ </Flex>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} rightIcon={<ChevronDownIcon />} colorScheme="" color="black">
+ <Text>{behavior.localSame === 'add' ? 'Add' : 'Replace'}</Text>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList bgColor="gray.200" zIndex="popover">
+ <MenuItem onClick={() => setBehavior({ ...behavior, localSame: 'replace' })}>
+ 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>
+ )
+}
diff --git a/components/CitationsPanel.tsx b/components/CitationsPanel.tsx
new file mode 100644
index 0000000..c34b0df
--- /dev/null
+++ b/components/CitationsPanel.tsx
@@ -0,0 +1,102 @@
+import { Box, StackDivider, VStack } from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from './config'
+import { EnableSection } from './EnableSection'
+import { SliderWithInfo } from './SliderWithInfo'
+
+export interface CitationsPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+}
+export const CitationsPanel = (props: CitationsPanelProps) => {
+ const { visuals, setVisuals } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <EnableSection
+ label="Dash cite links"
+ infoText="Add dashes to citation links made with org-roam-bibtex"
+ value={visuals.citeDashes}
+ onChange={() => setVisuals({ ...visuals, citeDashes: !visuals.citeDashes })}
+ >
+ <SliderWithInfo
+ label="Dash length"
+ value={visuals.citeDashLength / 10}
+ onChange={(value) => setVisuals({ ...visuals, citeDashLength: value * 10 })}
+ />
+ <SliderWithInfo
+ label="Gap length"
+ value={visuals.citeGapLength / 5}
+ onChange={(value) => setVisuals({ ...visuals, citeGapLength: value * 5 })}
+ />
+ </EnableSection>
+ <ColorMenu
+ colorList={colorList}
+ label="Citation node color"
+ setVisuals={setVisuals}
+ value={'citeNodeColor'}
+ visValue={visuals.citeNodeColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Citation link color"
+ setVisuals={setVisuals}
+ value={'citeLinkColor'}
+ visValue={visuals.citeLinkColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Reference link highlight"
+ setVisuals={setVisuals}
+ value={'citeLinkHighlightColor'}
+ visValue={visuals.citeLinkHighlightColor}
+ />
+ <EnableSection
+ label="Dash ref links"
+ infoText="Add dashes to citation links, whose target has a note, made with org-roam-bibtex"
+ value={visuals.refDashes}
+ onChange={() => setVisuals({ ...visuals, refDashes: !visuals.refDashes })}
+ >
+ <SliderWithInfo
+ label="Dash length"
+ value={visuals.refDashLength / 10}
+ onChange={(value) => setVisuals({ ...visuals, refDashLength: value * 10 })}
+ />
+ <SliderWithInfo
+ label="Gap length"
+ value={visuals.refGapLength / 5}
+ onChange={(value) => setVisuals({ ...visuals, refGapLength: value * 5 })}
+ />
+ </EnableSection>
+ <ColorMenu
+ colorList={colorList}
+ label="Reference node color"
+ setVisuals={setVisuals}
+ value={'refNodeColor'}
+ visValue={visuals.refNodeColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Reference link color"
+ setVisuals={setVisuals}
+ value={'refLinkColor'}
+ visValue={visuals.refLinkColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Reference link highlight"
+ setVisuals={setVisuals}
+ value={'refLinkHighlightColor'}
+ visValue={visuals.refLinkHighlightColor}
+ />
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/ColorMenu.tsx b/components/ColorMenu.tsx
new file mode 100644
index 0000000..1bbf087
--- /dev/null
+++ b/components/ColorMenu.tsx
@@ -0,0 +1,71 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Box,
+ Button,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+} from '@chakra-ui/react'
+import React, { useCallback } from 'react'
+import { initialVisuals } from './config'
+
+export interface ColorMenuProps {
+ label: string
+ colorList: string[]
+ value: string
+ visValue: string
+ setVisuals?: any
+}
+
+export const ColorMenu = (props: ColorMenuProps) => {
+ const { label, colorList, value, visValue, setVisuals } = props
+
+ const clickCallback = useCallback(
+ (color) =>
+ setVisuals((curr: typeof initialVisuals) => {
+ return {
+ ...curr,
+ [value]: color,
+ }
+ }),
+ [],
+ )
+ return (
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>{label}</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ {<Box bgColor={visValue} borderRadius="sm" height={6} width={6}></Box>}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuItem
+ onClick={() => clickCallback('')}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box height={6} width={6}></Box>
+ </MenuItem>
+ {colorList.map((color: string) => (
+ <MenuItem
+ key={color}
+ onClick={() => clickCallback(color)}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ )
+}
diff --git a/components/ColorsPanel.tsx b/components/ColorsPanel.tsx
new file mode 100644
index 0000000..6846a93
--- /dev/null
+++ b/components/ColorsPanel.tsx
@@ -0,0 +1,237 @@
+import { ArrowRightIcon, ChevronDownIcon, RepeatIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Box,
+ Flex,
+ IconButton,
+ StackDivider,
+ Tooltip,
+ VStack,
+ MenuButton,
+ Menu,
+ Portal,
+ MenuList,
+ MenuOptionGroup,
+ MenuItemOption,
+ Button,
+ MenuItem,
+} from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from './config'
+
+export interface ColorsPanelProps {
+ visuals: typeof initialVisuals
+ setVisualsCallback: any
+ highlightColor: string
+ setHighlightColor: any
+}
+
+export const ColorsPanel = (props: ColorsPanelProps) => {
+ const { visuals, setVisualsCallback, highlightColor, setHighlightColor } = props
+
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Nodes</Text>
+ <Tooltip label="Shuffle node colors">
+ <IconButton
+ aria-label="Shuffle node colors"
+ size="sm"
+ icon={<RepeatIcon />}
+ variant="ghost"
+ onClick={() => {
+ const arr = visuals.nodeColorScheme ?? []
+ setVisualsCallback({
+ ...visuals,
+ //shuffle that guy
+ //definitely thought of this myself
+ nodeColorScheme: arr
+ .map((x: any) => [Math.random(), x])
+ .sort(([a], [b]) => a - b)
+ .map(([_, x]) => x),
+ })
+ }}
+ />
+ </Tooltip>
+ <Tooltip label="Cycle node colors">
+ <IconButton
+ aria-label="Shift node colors"
+ icon={<ArrowRightIcon />}
+ size="sm"
+ variant="ghost"
+ onClick={() => {
+ const arr = visuals.nodeColorScheme ?? []
+ setVisualsCallback({
+ ...visuals,
+ nodeColorScheme: [...arr.slice(1, arr.length), arr[0]],
+ })
+ }}
+ />
+ </Tooltip>
+ <Menu isLazy placement="right" closeOnSelect={false} matchWidth>
+ <MenuButton
+ width={20}
+ as={Button}
+ colorScheme=""
+ color="black"
+ rightIcon={<ChevronDownIcon />}
+ >
+ <Flex height={6} width={6} flexDirection="column" flexWrap="wrap">
+ {visuals.nodeColorScheme.map((color) => (
+ <Box key={color} bgColor={color} flex="1 1 8px" borderRadius="2xl"></Box>
+ ))}
+ </Flex>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuOptionGroup
+ width={500}
+ type="checkbox"
+ defaultValue={visuals.nodeColorScheme}
+ onChange={(colors) => {
+ if (!colors.length) {
+ return
+ }
+ setVisualsCallback({ ...visuals, nodeColorScheme: colors })
+ }}
+ >
+ {colorList.map((color) => (
+ <MenuItemOption
+ key={color}
+ isChecked={visuals.nodeColorScheme.some((c) => c === color)}
+ value={color}
+ isDisabled={
+ visuals.nodeColorScheme.length === 1 && visuals.nodeColorScheme[0] === color
+ }
+ >
+ <Box justifyContent="space-between" alignItems="center" display="flex">
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </Box>
+ </MenuItemOption>
+ ))}
+ </MenuOptionGroup>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Links</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ <Box>
+ {visuals.linkColorScheme ? (
+ <Box
+ bgColor={visuals.linkColorScheme}
+ borderRadius="sm"
+ height={6}
+ width={6}
+ ></Box>
+ ) : (
+ <Flex height={6} width={6} flexDirection="column" flexWrap="wrap">
+ {visuals.nodeColorScheme.map((color) => (
+ <Box key={color} bgColor={color} flex="1 1 8px" borderRadius="2xl"></Box>
+ ))}
+ </Flex>
+ )}
+ </Box>
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ <MenuItem
+ onClick={() => setVisualsCallback({ ...visuals, linkColorScheme: '' })}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Flex height={6} width={6} flexDirection="column" flexWrap="wrap">
+ {visuals.nodeColorScheme.map((color) => (
+ <Box key={color} bgColor={color} flex="1 1 8px" borderRadius="2xl"></Box>
+ ))}
+ </Flex>
+ </MenuItem>
+ {colorList.map((color) => (
+ <MenuItem
+ key={color}
+ onClick={() =>
+ setVisualsCallback({
+ ...visuals,
+ linkColorScheme: color,
+ })
+ }
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Accent</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ {<Box bgColor={highlightColor} borderRadius="sm" height={6} width={6}></Box>}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ {colorList.map((color) => (
+ <MenuItem
+ key={color}
+ onClick={() => setHighlightColor(color)}
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <ColorMenu
+ colorList={colorList}
+ label="Link highlight"
+ setVisuals={setVisualsCallback}
+ value="linkHighlight"
+ visValue={visuals.linkHighlight}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Node highlight"
+ setVisuals={setVisualsCallback}
+ value="nodeHighlight"
+ visValue={visuals.nodeHighlight}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Background"
+ setVisuals={setVisualsCallback}
+ value="backgroundColor"
+ visValue={visuals.backgroundColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Emacs node"
+ setVisuals={setVisualsCallback}
+ value="emacsNodeColor"
+ visValue={visuals.emacsNodeColor}
+ />
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/DropDownMenu.tsx b/components/DropDownMenu.tsx
new file mode 100644
index 0000000..fbd854b
--- /dev/null
+++ b/components/DropDownMenu.tsx
@@ -0,0 +1,28 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import { Button, Menu, MenuButton, MenuItem, MenuList, Portal } from '@chakra-ui/react'
+import React from 'react'
+
+export interface DropDownMenuProps {
+ textArray: string[]
+ onClickArray: (() => void)[]
+ displayValue: string
+}
+
+export const DropDownMenu = (props: DropDownMenuProps) => {
+ const { textArray, onClickArray, displayValue } = props
+ return (
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
+ {displayValue}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList zIndex="popover">
+ {textArray.map((option, i) => {
+ ;<MenuItem onClick={onClickArray[i]}> {option} </MenuItem>
+ })}
+ </MenuList>
+ </Portal>
+ </Menu>
+ )
+}
diff --git a/components/EnableSection.tsx b/components/EnableSection.tsx
new file mode 100644
index 0000000..b7981b3
--- /dev/null
+++ b/components/EnableSection.tsx
@@ -0,0 +1,31 @@
+import { Text, Box, Collapse, Switch } from '@chakra-ui/react'
+import React from 'react'
+import { InfoTooltip } from './InfoTooltip'
+
+export interface EnableSectionProps {
+ label: string
+ value: boolean | number
+ onChange: () => void
+ infoText?: string
+ children: React.ReactNode
+}
+
+export const EnableSection = (props: EnableSectionProps) => {
+ const { value, onChange, label, infoText, children } = props
+ return (
+ <Box paddingTop={2} key={label}>
+ <Box display="flex" justifyContent="space-between" paddingBottom={2}>
+ <Box display="flex" alignItems="center">
+ <Text>{label}</Text>
+ {infoText && <InfoTooltip infoText={infoText} />}
+ </Box>
+ <Switch isChecked={!!value} onChange={onChange} />
+ </Box>
+ <Collapse in={!!value} animateOpacity>
+ <Box paddingLeft={4} paddingTop={2} paddingBottom={2}>
+ {children}
+ </Box>
+ </Collapse>
+ </Box>
+ )
+}
diff --git a/components/FilterPanel.tsx b/components/FilterPanel.tsx
new file mode 100644
index 0000000..871c3d6
--- /dev/null
+++ b/components/FilterPanel.tsx
@@ -0,0 +1,171 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Box,
+ Button,
+ Flex,
+ Menu,
+ MenuButton,
+ StackDivider,
+ VStack,
+ Portal,
+ MenuList,
+ MenuItem,
+ Switch,
+ Accordion,
+ AccordionItem,
+ AccordionButton,
+ AccordionIcon,
+ AccordionPanel,
+} from '@chakra-ui/react'
+import React from 'react'
+import { TagPanel } from './TagPanel'
+import { initialFilter, TagColors } from './config'
+import { TagColorPanel } from './TagColorPanel'
+
+export interface FilterPanelProps {
+ filter: typeof initialFilter
+ setFilter: any
+ tagColors: TagColors
+ setTagColors: any
+ highlightColor: string
+ colorList: string[]
+ tags: string[]
+}
+
+const FilterPanel = (props: FilterPanelProps) => {
+ const { filter, setFilter, tagColors, setTagColors, highlightColor, colorList, tags } = props
+ return (
+ <Box>
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ paddingLeft={7}
+ color="gray.800"
+ >
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Link children to...</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} rightIcon={<ChevronDownIcon />} colorScheme="" color="black">
+ {(() => {
+ switch (filter.parent) {
+ case 'parent':
+ return <Text>File</Text>
+ case 'heading':
+ return <Text>Heading</Text>
+ default:
+ return <Text>Nothing</Text>
+ }
+ })()}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList bgColor="gray.200" zIndex="popover">
+ <MenuItem
+ onClick={() =>
+ setFilter((curr: typeof initialFilter) => ({ ...curr, parent: '' }))
+ }
+ >
+ Nothing
+ </MenuItem>
+ <MenuItem
+ onClick={() =>
+ setFilter((curr: typeof initialFilter) => ({
+ ...curr,
+ parent: 'parent',
+ }))
+ }
+ >
+ Parent file node
+ </MenuItem>
+ <MenuItem
+ onClick={() =>
+ setFilter((curr: typeof initialFilter) => ({
+ ...curr,
+ parent: 'heading',
+ }))
+ }
+ >
+ Next highest heading node
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <Flex justifyContent="space-between">
+ <Text>Orphans</Text>
+ <Switch
+ onChange={() => {
+ setFilter((curr: typeof initialFilter) => {
+ return { ...curr, orphans: !curr.orphans }
+ })
+ }}
+ isChecked={filter.orphans}
+ ></Switch>
+ </Flex>
+ <Flex justifyContent="space-between">
+ <Text>Citations without note files</Text>
+ <Switch
+ onChange={() => {
+ setFilter({ ...filter, filelessCites: !filter.filelessCites })
+ }}
+ isChecked={filter.filelessCites}
+ ></Switch>
+ </Flex>
+ <Flex justifyContent="space-between">
+ <Text>Non-existant nodes</Text>
+ <Switch
+ onChange={() => {
+ setTagColors({ ...tagColors, bad: 'white' })
+ setFilter({ ...filter, bad: !filter.bad })
+ }}
+ isChecked={filter.bad}
+ ></Switch>
+ </Flex>
+ </VStack>
+ <Accordion padding={0} allowToggle allowMultiple paddingLeft={3}>
+ <AccordionItem>
+ <AccordionButton>
+ Tag filters
+ <AccordionIcon />
+ </AccordionButton>
+ <AccordionPanel pr={0} mr={0}>
+ <TagPanel
+ highlightColor={highlightColor}
+ filter={filter}
+ setFilter={setFilter}
+ tags={tags}
+ mode="blacklist"
+ />
+ <TagPanel
+ highlightColor={highlightColor}
+ filter={filter}
+ setFilter={setFilter}
+ tags={tags}
+ mode="whitelist"
+ />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ Tag Colors
+ <AccordionIcon />
+ </AccordionButton>
+ <AccordionPanel pr={0} mr={0}>
+ <TagColorPanel
+ tags={tags}
+ colorList={colorList}
+ tagColors={tagColors}
+ setTagColors={setTagColors}
+ highlightColor={highlightColor}
+ />
+ </AccordionPanel>
+ </AccordionItem>
+ </Accordion>
+ </Box>
+ )
+}
+
+export default FilterPanel
diff --git a/components/HighlightingPanel.tsx b/components/HighlightingPanel.tsx
new file mode 100644
index 0000000..b3900e2
--- /dev/null
+++ b/components/HighlightingPanel.tsx
@@ -0,0 +1,116 @@
+import { Box, Select, StackDivider, VStack } from '@chakra-ui/react'
+import React from 'react'
+import { initialVisuals } from './config'
+import { EnableSection } from './EnableSection'
+import { SliderWithInfo } from './SliderWithInfo'
+
+export interface HighlightingPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+}
+export const HighlightingPanel = (props: HighlightingPanelProps) => {
+ const { visuals, setVisuals } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <EnableSection
+ label="Highlight"
+ onChange={() =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlight: !visuals.highlight,
+ }))
+ }
+ value={visuals.highlight}
+ >
+ <VStack
+ spacing={1}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.400" />}
+ align="stretch"
+ paddingLeft={0}
+ >
+ <SliderWithInfo
+ label="Highlight Link Thickness"
+ value={visuals.highlightLinkSize}
+ onChange={(value) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightLinkSize: value,
+ }))
+ }
+ />
+ <SliderWithInfo
+ label="Highlight Node Size"
+ value={visuals.highlightNodeSize}
+ onChange={(value) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightNodeSize: value,
+ }))
+ }
+ />
+ <SliderWithInfo
+ min={0}
+ max={1}
+ label="Highlight Fade"
+ value={visuals.highlightFade}
+ onChange={(value) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightFade: value,
+ }))
+ }
+ />
+ <EnableSection
+ label="Highlight Animation"
+ onChange={() => {
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ highlightAnim: !visuals.highlightAnim,
+ }))
+ }}
+ value={visuals.highlightAnim}
+ >
+ <SliderWithInfo
+ label="Animation speed"
+ onChange={(v) =>
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ animationSpeed: v,
+ }))
+ }
+ value={visuals.animationSpeed}
+ infoText="Slower speed has a chance of being buggy"
+ min={50}
+ max={1000}
+ step={10}
+ />
+ <Select
+ placeholder={visuals.algorithmName}
+ onChange={(v) => {
+ setVisuals((visuals: typeof initialVisuals) => ({
+ ...visuals,
+ algorithmName: v.target.value,
+ }))
+ }}
+ >
+ {visuals.algorithmOptions.map((opt: string) => (
+ <option key={opt} value={opt}>
+ {opt}
+ </option>
+ ))}
+ </Select>
+ </EnableSection>
+ </VStack>
+ </EnableSection>
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/InfoTooltip.tsx b/components/InfoTooltip.tsx
new file mode 100644
index 0000000..02c2fad
--- /dev/null
+++ b/components/InfoTooltip.tsx
@@ -0,0 +1,17 @@
+import { InfoOutlineIcon } from '@chakra-ui/icons'
+import { Box, Tooltip } from '@chakra-ui/react'
+import React from 'react'
+
+export interface InfoTooltipProps {
+ infoText?: string | boolean
+}
+export const InfoTooltip = (props: InfoTooltipProps) => {
+ const { infoText } = props
+ return (
+ <Box paddingLeft="1">
+ <Tooltip label={infoText} placement="top" color="gray.100" bg="gray.800" hasArrow>
+ <InfoOutlineIcon />
+ </Tooltip>
+ </Box>
+ )
+}
diff --git a/components/LabelsPanel.tsx b/components/LabelsPanel.tsx
new file mode 100644
index 0000000..eb7dc34
--- /dev/null
+++ b/components/LabelsPanel.tsx
@@ -0,0 +1,139 @@
+import { ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Box,
+ Button,
+ Collapse,
+ Flex,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ StackDivider,
+ VStack,
+ Text,
+} from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from './config'
+import { SliderWithInfo } from './SliderWithInfo'
+
+export interface LabelsPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+}
+export const LabelsPanel = (props: LabelsPanelProps) => {
+ const { visuals, setVisuals } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <Flex alignItems="center" justifyContent="space-between">
+ <Text>Show labels</Text>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}>
+ {!visuals.labels ? 'Never' : visuals.labels < 2 ? 'On Highlight' : 'Always'}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList zIndex="popover" bgColor="gray.200">
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 0 })}>Never</MenuItem>
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 1 })}>
+ On Highlight
+ </MenuItem>
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 2 })}>Always</MenuItem>
+ <MenuItem onClick={() => setVisuals({ ...visuals, labels: 3 })}>
+ Always (even in 3D)
+ </MenuItem>
+ </MenuList>
+ </Portal>
+ </Menu>
+ </Flex>
+ <VStack
+ spacing={1}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.400" />}
+ align="stretch"
+ paddingLeft={2}
+ color="gray.800"
+ >
+ <SliderWithInfo
+ label="Label font size"
+ value={visuals.labelFontSize}
+ min={5}
+ max={20}
+ step={0.5}
+ onChange={(value) => setVisuals({ ...visuals, labelFontSize: value })}
+ />
+ <SliderWithInfo
+ label="Max. label characters"
+ value={visuals.labelLength}
+ min={10}
+ max={100}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, labelLength: value })}
+ />
+ <SliderWithInfo
+ label="Max. label line length"
+ value={visuals.labelWordWrap}
+ min={10}
+ max={100}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, labelWordWrap: value })}
+ />
+ <SliderWithInfo
+ label="Space between label lines"
+ value={visuals.labelLineSpace}
+ min={0.2}
+ max={3}
+ step={0.1}
+ onChange={(value) => setVisuals({ ...visuals, labelLineSpace: value })}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Text"
+ setVisuals={setVisuals}
+ value="labelTextColor"
+ visValue={visuals.labelTextColor}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Background"
+ setVisuals={setVisuals}
+ value="labelBackgroundColor"
+ visValue={visuals.labelBackgroundColor}
+ />
+ <Collapse in={!!visuals.labelBackgroundColor} animateOpacity>
+ <Box paddingTop={2}>
+ <SliderWithInfo
+ label="Background opacity"
+ value={visuals.labelBackgroundOpacity}
+ onChange={(value) => {
+ console.log(visuals.labelBackgroundOpacity)
+ setVisuals({ ...visuals, labelBackgroundOpacity: value })
+ }}
+ min={0}
+ max={1}
+ step={0.01}
+ />
+ </Box>
+ </Collapse>
+ <Collapse in={visuals.labels > 1} animateOpacity>
+ <Box paddingTop={2}>
+ <SliderWithInfo
+ label="Label Appearance Scale"
+ value={visuals.labelScale * 5}
+ onChange={(value) => setVisuals({ ...visuals, labelScale: value / 5 })}
+ />
+ </Box>
+ </Collapse>
+ </VStack>
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/NodesNLinksPanel.tsx b/components/NodesNLinksPanel.tsx
new file mode 100644
index 0000000..f19a3e2
--- /dev/null
+++ b/components/NodesNLinksPanel.tsx
@@ -0,0 +1,131 @@
+import { Box, StackDivider, VStack } from '@chakra-ui/react'
+import React from 'react'
+import { ColorMenu } from './ColorMenu'
+import { colorList, initialVisuals } from './config'
+import { EnableSection } from './EnableSection'
+import { SliderWithInfo } from './SliderWithInfo'
+
+export interface NodesNLinksPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+ threeDim: boolean
+}
+
+export const NodesNLinksPanel = (props: NodesNLinksPanelProps) => {
+ const { visuals, setVisuals, threeDim } = props
+ return (
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ <Box>
+ <SliderWithInfo
+ label="Node size"
+ value={visuals.nodeRel}
+ onChange={(value) => setVisuals({ ...visuals, nodeRel: value })}
+ />
+ <SliderWithInfo
+ label="Node connections size scale"
+ value={visuals.nodeSizeLinks}
+ min={0}
+ max={2}
+ onChange={(value) => setVisuals({ ...visuals, nodeSizeLinks: value })}
+ />
+ <SliderWithInfo
+ label="Node zoom invariance"
+ value={visuals.nodeZoomSize}
+ min={0}
+ max={2}
+ infoText="How much the graph will try to keep the nodesize consistent across zoom scales. 0 is no consistency, node will always be their true size, 1 is linear, 2 is quadratic."
+ onChange={(value) =>
+ setVisuals((prev: typeof initialVisuals) => ({
+ ...prev,
+ nodeZoomSize: value,
+ }))
+ }
+ />
+ {threeDim && (
+ <>
+ <SliderWithInfo
+ label="Node opacity"
+ value={visuals.nodeOpacity}
+ min={0}
+ max={1}
+ onChange={(value) => setVisuals({ ...visuals, nodeOpacity: value })}
+ />
+ <SliderWithInfo
+ label="Node resolution"
+ value={visuals.nodeResolution}
+ min={5}
+ max={32}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, nodeResolution: value })}
+ />
+ </>
+ )}
+ <SliderWithInfo
+ label="Link width"
+ value={visuals.linkWidth}
+ onChange={(value) => setVisuals({ ...visuals, linkWidth: value })}
+ />
+ {threeDim && (
+ <SliderWithInfo
+ label="Link opacity"
+ min={0}
+ max={1}
+ value={visuals.linkOpacity}
+ onChange={(value) => setVisuals({ ...visuals, linkOpacity: value })}
+ />
+ )}
+ <EnableSection
+ label="Link arrows"
+ value={visuals.arrows}
+ onChange={() => setVisuals({ ...visuals, arrows: !visuals.arrows })}
+ >
+ <SliderWithInfo
+ label="Arrow size"
+ value={visuals.arrowsLength / 10}
+ onChange={(value) => setVisuals({ ...visuals, arrowsLength: 10 * value })}
+ />
+ <SliderWithInfo
+ label="Arrow Position"
+ value={visuals.arrowsPos}
+ min={0}
+ max={1}
+ step={0.01}
+ onChange={(value) => setVisuals({ ...visuals, arrowsPos: value })}
+ />
+ <ColorMenu
+ colorList={colorList}
+ label="Arrow Color"
+ key="arrow"
+ setVisuals={setVisuals}
+ value="arrowsColor"
+ visValue={visuals.arrowsColor}
+ />
+ </EnableSection>
+ <EnableSection
+ label="Directional Particles"
+ value={visuals.particles}
+ onChange={() => setVisuals({ ...visuals, particles: !visuals.particles })}
+ >
+ <SliderWithInfo
+ label="Particle Number"
+ value={visuals.particlesNumber}
+ max={5}
+ step={1}
+ onChange={(value) => setVisuals({ ...visuals, particlesNumber: value })}
+ />
+ <SliderWithInfo
+ label="Particle Size"
+ value={visuals.particlesWidth}
+ onChange={(value) => setVisuals({ ...visuals, particlesWidth: value })}
+ />
+ </EnableSection>
+ </Box>
+ </VStack>
+ )
+}
diff --git a/components/PhysicsPanel.tsx b/components/PhysicsPanel.tsx
new file mode 100644
index 0000000..1bdfaa0
--- /dev/null
+++ b/components/PhysicsPanel.tsx
@@ -0,0 +1,143 @@
+import {
+ Text,
+ Accordion,
+ AccordionButton,
+ AccordionIcon,
+ AccordionItem,
+ AccordionPanel,
+ Box,
+ Flex,
+ StackDivider,
+ Switch,
+ VStack,
+} from '@chakra-ui/react'
+import React, { useCallback } from 'react'
+import { initialPhysics } from './config'
+import { EnableSection } from './EnableSection'
+import { SliderWithInfo } from './SliderWithInfo'
+
+export interface PhysicsPanelProps {
+ physics: typeof initialPhysics
+ setPhysics: any
+}
+
+export const PhysicsPanel = (props: PhysicsPanelProps) => {
+ const { physics, setPhysics } = props
+ const setPhysicsCallback = useCallback((val: number, phys: string, scale: number) => {
+ setPhysics((curr: typeof initialPhysics) => {
+ return { ...curr, [phys]: val / scale }
+ })
+ }, [])
+ return (
+ <Box>
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ paddingLeft={7}
+ color="gray.800"
+ >
+ <EnableSection
+ label="Gravity"
+ value={physics.gravityOn}
+ onChange={() => setPhysics({ ...physics, gravityOn: !physics.gravityOn })}
+ >
+ <Flex justifyContent="space-between">
+ <Text>Also in local</Text>
+ <Switch
+ onChange={() => {
+ setPhysics((curr: typeof initialPhysics) => {
+ return { ...curr, gravityLocal: !curr.gravityLocal }
+ })
+ }}
+ isChecked={physics.gravityLocal}
+ ></Switch>
+ </Flex>
+ <SliderWithInfo
+ label="Strength"
+ value={physics.gravity * 10}
+ onChange={(v: number) => setPhysicsCallback(v, 'gravity', 10)}
+ />
+ </EnableSection>
+ <SliderWithInfo
+ value={-physics.charge / 100}
+ onChange={(v) => setPhysicsCallback(v, 'charge', -1 / 100)}
+ label="Repulsive Force"
+ />
+ <EnableSection
+ label="Collision"
+ infoText="Perfomance sap, disable if slow"
+ value={physics.collision}
+ onChange={() => setPhysics({ ...physics, collision: !physics.collision })}
+ >
+ <SliderWithInfo
+ value={physics.collisionStrength / 5}
+ onChange={(v) => setPhysicsCallback(v, 'collisionStrength', 1 / 5)}
+ label="Collision Radius"
+ infoText="Easy with this one, high values can lead to a real jiggly mess"
+ />
+ </EnableSection>
+ <SliderWithInfo
+ value={physics.linkStrength * 5}
+ onChange={(v) => setPhysicsCallback(v, 'linkStrength', 5)}
+ label="Link Force"
+ />
+ <SliderWithInfo
+ label="Link Iterations"
+ value={physics.linkIts}
+ onChange={(v) => setPhysicsCallback(v, 'linkIts', 1)}
+ min={0}
+ max={6}
+ step={1}
+ infoText="How many links down the line the physics of a single node affects (Slow)"
+ />
+ <SliderWithInfo
+ label="Viscosity"
+ value={physics.velocityDecay * 10}
+ onChange={(v) => setPhysicsCallback(v, 'velocityDecay', 10)}
+ />
+ </VStack>
+ <Box>
+ <Accordion paddingLeft={3} allowToggle>
+ <AccordionItem>
+ <AccordionButton>
+ <Text>Advanced</Text>
+ <AccordionIcon marginRight={2} />
+ </AccordionButton>
+ <AccordionPanel>
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ paddingLeft={3}
+ color="gray.800"
+ >
+ <SliderWithInfo
+ label="Stabilization rate"
+ value={physics.alphaDecay * 50}
+ onChange={(v) => setPhysicsCallback(v, 'alphaDecay', 50)}
+ />
+ <EnableSection
+ label="Center nodes"
+ value={physics.centering}
+ onChange={() => setPhysics({ ...physics, centering: !physics.centering })}
+ infoText="Keeps the nodes in the center of the viewport. If disabled you can drag the nodes anywhere you want."
+ >
+ <SliderWithInfo
+ label="Centering Strength"
+ value={physics.centeringStrength}
+ max={2}
+ step={0.01}
+ onChange={(v) => setPhysicsCallback(v, 'centeringStrength', 1)}
+ />
+ </EnableSection>
+ </VStack>
+ </AccordionPanel>
+ </AccordionItem>
+ </Accordion>
+ </Box>
+ </Box>
+ )
+}
diff --git a/components/SliderWithInfo.tsx b/components/SliderWithInfo.tsx
new file mode 100644
index 0000000..d4f0372
--- /dev/null
+++ b/components/SliderWithInfo.tsx
@@ -0,0 +1,48 @@
+import {
+ Text,
+ Box,
+ Slider,
+ SliderFilledTrack,
+ SliderThumb,
+ SliderTrack,
+ Tooltip,
+} from '@chakra-ui/react'
+import React, { useContext } from 'react'
+import { ThemeContext } from '../util/themecontext'
+import { InfoTooltip } from './InfoTooltip'
+
+export interface SliderWithInfoProps {
+ min?: number
+ max?: number
+ step?: number
+ value: number
+ onChange: (arg0: number) => void
+ label: string
+ infoText?: string
+}
+export const SliderWithInfo = ({
+ min = 0,
+ max = 10,
+ step = 0.1,
+ value = 1,
+ ...rest
+}: SliderWithInfoProps) => {
+ const { onChange, label, infoText } = rest
+ const { highlightColor } = useContext(ThemeContext)
+ return (
+ <Box key={label}>
+ <Box display="flex" alignItems="flex-end">
+ <Text>{label}</Text>
+ {infoText && <InfoTooltip infoText={infoText} />}
+ </Box>
+ <Slider value={value} onChange={onChange} min={min} max={max} step={step}>
+ <SliderTrack>
+ <SliderFilledTrack />
+ </SliderTrack>
+ <Tooltip bg={highlightColor} label={value.toFixed(1)}>
+ <SliderThumb bg="white" />
+ </Tooltip>
+ </Slider>
+ </Box>
+ )
+}
diff --git a/components/TagColorPanel.tsx b/components/TagColorPanel.tsx
new file mode 100644
index 0000000..f9a9815
--- /dev/null
+++ b/components/TagColorPanel.tsx
@@ -0,0 +1,138 @@
+import { DeleteIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Box,
+ Flex,
+ IconButton,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ Portal,
+ StackDivider,
+ VStack,
+ Button,
+} from '@chakra-ui/react'
+import { CUIAutoComplete } from 'chakra-ui-autocomplete'
+import React, { useState } from 'react'
+import { TagColors } from './config'
+
+export interface TagColorPanelProps {
+ tags: string[]
+ highlightColor: string
+ colorList: string[]
+ tagColors: TagColors
+ setTagColors: any
+}
+export const TagColorPanel = (props: TagColorPanelProps) => {
+ const { colorList, tagColors, setTagColors, highlightColor, tags } = props
+ const tagArray = tags.map((tag) => {
+ return { value: tag, label: tag }
+ })
+
+ const [selectedItems, setSelectedItems] = useState<typeof tagArray>(
+ Object.keys(tagColors).map((tag) => {
+ return { value: tag, label: tag }
+ }),
+ )
+
+ return (
+ <Box>
+ <CUIAutoComplete
+ items={tagArray}
+ label="Add tag to filter"
+ placeholder=" "
+ disableCreateItem={true}
+ selectedItems={selectedItems}
+ onSelectedItemsChange={(changes) => {
+ if (changes.selectedItems) {
+ setSelectedItems(Array.from(new Set(changes.selectedItems)))
+ setTagColors(
+ Object.fromEntries(
+ Array.from(new Set(changes.selectedItems)).map((item) => {
+ return [item.label, tagColors[item.label] ?? 'gray.600']
+ }),
+ ),
+ )
+ }
+ }}
+ listItemStyleProps={{ overflow: 'hidden' }}
+ highlightItemBg="gray.400"
+ toggleButtonStyleProps={{ variant: 'outline' }}
+ inputStyleProps={{
+ focusBorderColor: highlightColor,
+ color: 'gray.800',
+ borderColor: 'gray.600',
+ }}
+ tagStyleProps={{
+ display: 'none',
+ rounded: 'full',
+ bg: highlightColor,
+ height: 8,
+ paddingLeft: 4,
+ fontWeight: 'bold',
+ }}
+ hideToggleButton
+ itemRenderer={(selected) => selected.label}
+ />
+ <VStack
+ spacing={2}
+ justifyContent="flex-start"
+ divider={<StackDivider borderColor="gray.500" />}
+ align="stretch"
+ color="gray.800"
+ >
+ {Object.keys(tagColors).map((tag) => {
+ return (
+ <Flex key={tag} alignItems="center" justifyContent="space-between" width="100%" pl={2}>
+ <Box width="100%">
+ <Text fontWeight="bold">{tag}</Text>
+ </Box>
+ <Menu isLazy placement="right">
+ <MenuButton as={Button} colorScheme="" color="black">
+ {<Box bgColor={tagColors[tag]} borderRadius="sm" height={6} width={6}></Box>}
+ </MenuButton>
+ <Portal>
+ {' '}
+ <MenuList minW={10} zIndex="popover" bgColor="gray.200">
+ {colorList.map((color: string) => (
+ <MenuItem
+ key={color}
+ onClick={() =>
+ setTagColors({
+ ...tagColors,
+ [tag]: color,
+ })
+ }
+ justifyContent="space-between"
+ alignItems="center"
+ display="flex"
+ >
+ <Box bgColor={color} borderRadius="sm" height={6} width={6}></Box>
+ </MenuItem>
+ ))}
+ </MenuList>
+ </Portal>
+ </Menu>
+ <IconButton
+ aria-label="Delete tag color"
+ variant="ghost"
+ icon={<DeleteIcon />}
+ onClick={() => {
+ setTagColors(
+ Object.fromEntries(
+ Array.from(new Set(selectedItems)).map((item) => {
+ return [item.label, tagColors[item.label] ?? 'gray.600']
+ }),
+ ),
+ )
+ setSelectedItems(selectedItems.filter((item) => item.value !== tag))
+ }}
+ />
+ </Flex>
+ )
+ })}
+ </VStack>
+ </Box>
+ )
+}
diff --git a/components/TagPanel.tsx b/components/TagPanel.tsx
new file mode 100644
index 0000000..545102b
--- /dev/null
+++ b/components/TagPanel.tsx
@@ -0,0 +1,59 @@
+import { CUIAutoComplete } from 'chakra-ui-autocomplete'
+import React, { useState } from 'react'
+import { initialFilter } from './config'
+
+export interface TagPanelProps {
+ tags: string[]
+ filter: typeof initialFilter
+ setFilter: any
+ highlightColor: string
+ mode: string
+}
+
+export const TagPanel = (props: TagPanelProps) => {
+ const { filter, setFilter, tags, highlightColor, mode } = props
+ const tagArray = tags.map((tag) => {
+ return { value: tag, label: tag }
+ })
+
+ const currentTags = mode === 'blacklist' ? 'tagsBlacklist' : 'tagsWhitelist'
+ const [selectedItems, setSelectedItems] = useState<typeof tagArray>(
+ filter[currentTags].map((tag) => {
+ return { value: tag, label: tag }
+ }),
+ )
+
+ return (
+ <CUIAutoComplete
+ items={tagArray}
+ label={'Add tag to ' + mode}
+ placeholder=" "
+ onCreateItem={(item) => null}
+ disableCreateItem={true}
+ selectedItems={selectedItems}
+ onSelectedItemsChange={(changes) => {
+ if (changes.selectedItems) {
+ setSelectedItems(changes.selectedItems)
+ setFilter({ ...filter, [currentTags]: changes.selectedItems.map((item) => item.value) })
+ }
+ }}
+ listItemStyleProps={{ overflow: 'hidden' }}
+ highlightItemBg="gray.400"
+ toggleButtonStyleProps={{ variant: 'outline' }}
+ inputStyleProps={{
+ focusBorderColor: highlightColor,
+ color: 'gray.800',
+ borderColor: 'gray.600',
+ }}
+ tagStyleProps={{
+ rounded: 'full',
+ bg: highlightColor,
+ height: 8,
+ paddingLeft: 4,
+ fontWeight: 'bold',
+ }}
+ hideToggleButton
+ itemRenderer={(selected) => selected.label}
+ />
+ )
+}
diff --git a/components/VisualsPanel.tsx b/components/VisualsPanel.tsx
new file mode 100644
index 0000000..1b6fceb
--- /dev/null
+++ b/components/VisualsPanel.tsx
@@ -0,0 +1,117 @@
+import { ArrowRightIcon, ChevronDownIcon } from '@chakra-ui/icons'
+import {
+ Text,
+ Accordion,
+ AccordionButton,
+ AccordionItem,
+ Box,
+ Flex,
+ IconButton,
+ Menu,
+ MenuButton,
+ MenuItemOption,
+ MenuList,
+ MenuOptionGroup,
+ Portal,
+ Tooltip,
+ VStack,
+ AccordionIcon,
+ AccordionPanel,
+ MenuItem,
+ Collapse,
+ StackDivider,
+ Button,
+} from '@chakra-ui/react'
+import React, { useCallback } from 'react'
+import { ColorMenu } from './ColorMenu'
+import { EnableSection } from './EnableSection'
+import { SliderWithInfo } from './SliderWithInfo'
+import { HighlightingPanel } from './HighlightingPanel'
+import { ColorsPanel } from './ColorsPanel'
+import { colorList, initialVisuals } from './config'
+import { NodesNLinksPanel } from './NodesNLinksPanel'
+import { LabelsPanel } from './LabelsPanel'
+import { CitationsPanel } from './CitationsPanel'
+
+export interface VisualsPanelProps {
+ visuals: typeof initialVisuals
+ setVisuals: any
+ highlightColor: string
+ setHighlightColor: any
+ threeDim: boolean
+}
+
+export const VisualsPanel = (props: VisualsPanelProps) => {
+ const { visuals, setVisuals, highlightColor, setHighlightColor, threeDim } = props
+ const setVisualsCallback = useCallback((val) => setVisuals(val), [])
+ return (
+ <VStack justifyContent="flex-start" align="stretch">
+ <Accordion allowToggle defaultIndex={[0]} paddingLeft={3}>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Colors</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <ColorsPanel
+ visuals={visuals}
+ setVisualsCallback={setVisualsCallback}
+ highlightColor={highlightColor}
+ setHighlightColor={setHighlightColor}
+ />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Nodes & Links</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <NodesNLinksPanel
+ visuals={visuals}
+ setVisuals={setVisualsCallback}
+ threeDim={threeDim}
+ />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Labels</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <LabelsPanel visuals={visuals} setVisuals={setVisualsCallback} />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Highlighting</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <HighlightingPanel visuals={visuals} setVisuals={setVisualsCallback} />
+ </AccordionPanel>
+ </AccordionItem>
+ <AccordionItem>
+ <AccordionButton>
+ <Flex justifyContent="space-between" w="100%">
+ <Text>Citations</Text>
+ <AccordionIcon marginRight={2} />
+ </Flex>
+ </AccordionButton>
+ <AccordionPanel>
+ <CitationsPanel visuals={visuals} setVisuals={setVisualsCallback} />
+ </AccordionPanel>
+ </AccordionItem>
+ </Accordion>
+ </VStack>
+ )
+}
diff --git a/components/tweaks.tsx b/components/tweaks.tsx
index 1a7e72c..a78c7f4 100644
--- a/components/tweaks.tsx
+++ b/components/tweaks.tsx
@@ -1,11 +1,4 @@
-import {
- CloseIcon,
- RepeatClockIcon,
- ChevronDownIcon,
- SettingsIcon,
- RepeatIcon,
- ArrowRightIcon,
-} from '@chakra-ui/icons'
+import { CloseIcon, RepeatClockIcon, SettingsIcon } from '@chakra-ui/icons'
import {
Accordion,
AccordionButton,
@@ -14,26 +7,12 @@ import {
AccordionPanel,
Box,
Button,
- Flex,
IconButton,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- MenuOptionGroup,
- MenuItemOption,
- Select,
- StackDivider,
- Switch,
- Text,
Tooltip,
- VStack,
Heading,
- Collapse,
- Portal,
} from '@chakra-ui/react'
-import React, { useContext, useCallback } from 'react'
+import React, { useContext } from 'react'
import Scrollbars from 'react-custom-scrollbars-2'
import {
initialPhysics,
@@ -46,15 +25,12 @@ import {
} from './config'
import FilterPanel from './FilterPanel'
-import { ColorMenu } from './ColorMenu'
import { ThemeContext } from '../util/themecontext'
import { usePersistantState } from '../util/persistant-state'
-import { SliderWithInfo } from './SliderWithInfo'
-import { EnableSection } from './EnableSection'
-import { InfoTooltip } from './InfoTooltip'
import { PhysicsPanel } from './PhysicsPanel'
import { VisualsPanel } from './VisualsPanel'
+import { BehaviorPanel } from './BehaviorPanel'
export interface TweakProps {
physics: typeof initialPhysics
@@ -226,157 +202,12 @@ export const Tweaks = (props: TweakProps) => {
<Heading size="sm">Behavior</Heading>
</AccordionButton>
<AccordionPanel>
- <VStack
- spacing={2}
- justifyContent="flex-start"
- divider={<StackDivider borderColor="gray.500" />}
- align="stretch"
- paddingLeft={7}
- color="gray.800"
- >
- <Flex alignItems="center" justifyContent="space-between">
- <Flex>
- <Text>Expand Node</Text>
- <InfoTooltip infoText="View only the node and its direct neighbors" />
- </Flex>
- <Menu isLazy placement="right">
- <MenuButton
- as={Button}
- rightIcon={<ChevronDownIcon />}
- colorScheme=""
- color="black"
- >
- <Text>
- {mouse.local
- ? mouse.local[0]!.toUpperCase() + mouse.local!.slice(1)
- : 'Never'}
- </Text>
- </MenuButton>
- <Portal>
- {' '}
- <MenuList zIndex="popover" bgColor="gray.200">
- <MenuItem onClick={() => setMouse({ ...mouse, local: '' })}>Never</MenuItem>
- <MenuItem onClick={() => setMouse({ ...mouse, local: 'click' })}>
- Click
- </MenuItem>
- <MenuItem onClick={() => setMouse({ ...mouse, local: 'double' })}>
- Double Click
- </MenuItem>
- <MenuItem onClick={() => setMouse({ ...mouse, local: 'right' })}>
- Right Click
- </MenuItem>
- </MenuList>
- </Portal>
- </Menu>
- </Flex>
- <Flex alignItems="center" justifyContent="space-between">
- <Text>Open in Emacs</Text>
- <Menu isLazy placement="right">
- <MenuButton
- as={Button}
- rightIcon={<ChevronDownIcon />}
- colorScheme=""
- color="black"
- >
- <Text>
- {mouse.follow
- ? mouse.follow[0]!.toUpperCase() + mouse.follow!.slice(1)
- : 'Never'}
- </Text>
- </MenuButton>
- <Portal>
- {' '}
- <MenuList bgColor="gray.200" zIndex="popover">
- <MenuItem onClick={() => setMouse({ ...mouse, follow: '' })}>
- Never
- </MenuItem>
- <MenuItem onClick={() => setMouse({ ...mouse, follow: 'click' })}>
- Click
- </MenuItem>
- <MenuItem onClick={() => setMouse({ ...mouse, follow: 'double' })}>
- Double Click
- </MenuItem>
- <MenuItem onClick={() => setMouse({ ...mouse, follow: 'right' })}>
- Right Click
- </MenuItem>
- </MenuList>
- </Portal>
- </Menu>
- </Flex>
- <Flex alignItems="center" justifyContent="space-between">
- <Text>Follow Emacs by...</Text>
- <Menu isLazy 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: 'color' })}>
- Just coloring the currently opened node
- </MenuItem>
- <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>Local graph</Text>
- <InfoTooltip infoText="When in local mode and clicking a new node, should I add that node's local graph or open the new one?" />
- </Flex>
- <Menu isLazy placement="right">
- <MenuButton
- as={Button}
- rightIcon={<ChevronDownIcon />}
- colorScheme=""
- color="black"
- >
- <Text>{behavior.localSame === 'add' ? 'Add' : 'Replace'}</Text>
- </MenuButton>
- <Portal>
- {' '}
- <MenuList bgColor="gray.200" zIndex="popover">
- <MenuItem
- onClick={() => setBehavior({ ...behavior, localSame: 'replace' })}
- >
- 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>
+ <BehaviorPanel
+ behavior={behavior}
+ setBehavior={setBehavior}
+ mouse={mouse}
+ setMouse={setMouse}
+ />
</AccordionPanel>
</AccordionItem>
</Accordion>
@@ -384,28 +215,3 @@ export const Tweaks = (props: TweakProps) => {
</Box>
)
}
-
-export interface DropDownMenuProps {
- textArray: string[]
- onClickArray: (() => void)[]
- displayValue: string
-}
-
-export const DropDownMenu = (props: DropDownMenuProps) => {
- const { textArray, onClickArray, displayValue } = props
- return (
- <Menu isLazy placement="right">
- <MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
- {displayValue}
- </MenuButton>
- <Portal>
- {' '}
- <MenuList zIndex="popover">
- {textArray.map((option, i) => {
- ;<MenuItem onClick={onClickArray[i]}> {option} </MenuItem>
- })}
- </MenuList>
- </Portal>
- </Menu>
- )
-}