diff options
-rw-r--r-- | components/config.ts | 24 | ||||
-rw-r--r-- | components/tweaks.tsx | 142 | ||||
-rw-r--r-- | pages/_app.tsx | 120 | ||||
-rw-r--r-- | pages/index.tsx | 41 |
4 files changed, 247 insertions, 80 deletions
diff --git a/components/config.ts b/components/config.ts index d6c2296..753f2db 100644 --- a/components/config.ts +++ b/components/config.ts @@ -64,3 +64,27 @@ export const initialFilter = { links: [], date: [], } + +export const initialVisuals = { + particles: false, + particlesNumber: 0, + particlesWidth: 4, + linkOpacity: 0.7, + linkWidth: 1, + nodeRel: 4, + nodeOpacity: 0.9, + nodeResolution: 8, + labels: 2, + labelScale: 1.5, + highlight: true, + highlightNodeSize: 2, + highlightLinkSize: 2, + highlightAnim: false, + animationSpeed: 250, + algorithms: algorithms, + algorithmOptions: options, + algorithmName: 'CubicOut', + linkColorScheme: 'plain', + nodeColorScheme: 'colorful', + highlightColor: 'purple', +} diff --git a/components/tweaks.tsx b/components/tweaks.tsx index f9c58a1..15fadcf 100644 --- a/components/tweaks.tsx +++ b/components/tweaks.tsx @@ -32,9 +32,11 @@ import { Heading, Collapse, } from '@chakra-ui/react' -import React, { useState } from 'react' +import React, { useState, useContext } from 'react' import Scrollbars from 'react-custom-scrollbars-2' -import { initialPhysics, initialFilter } from './config' +import { initialPhysics, initialFilter, initialVisuals } from './config' + +import { ThemeContext } from '../pages/themecontext' export interface TweakProps { physics: typeof initialPhysics @@ -43,11 +45,15 @@ export interface TweakProps { setThreeDim: (newValue: boolean) => void filter: typeof initialFilter setFilter: any + visuals: typeof initialVisuals + setVisuals: any } export const Tweaks = (props: TweakProps) => { - const { physics, setPhysics, threeDim, setThreeDim, filter, setFilter } = props + const { physics, setPhysics, threeDim, setThreeDim, filter, setFilter, visuals, setVisuals } = + props const [showTweaks, setShowTweaks] = useState(true) + const { highlightColor, setHighlightColor } = useContext(ThemeContext) return ( <> @@ -67,24 +73,19 @@ export const Tweaks = (props: TweakProps) => { <Collapse in={showTweaks} animateOpacity> <Box bg="alt.100" - w="xs" + w={300} marginTop={10} marginLeft={10} borderRadius="xl" maxH={650} paddingBottom={5} - zIndex="overlay" + zIndex={300} position="relative" boxShadow="xl" > <Box display="flex" justifyContent="space-between" alignItems="center"> <Tooltip label={'Switch to ' + threeDim ? '2D' : '3D' + ' view'}> - <Button - onClick={() => setThreeDim(!threeDim)} - colorScheme="purple" - variant="ghost" - zIndex="overlay" - > + <Button onClick={() => setThreeDim(!threeDim)} variant="ghost" zIndex="overlay"> {threeDim ? '3D' : '2D'} </Button> </Tooltip> @@ -94,14 +95,12 @@ export const Tweaks = (props: TweakProps) => { aria-label="Reset Defaults" icon={<RepeatClockIcon />} onClick={() => setPhysics(initialPhysics)} - colorScheme="purple" variant="none" size="sm" /> </Tooltip> <IconButton size="sm" - colorScheme="purple" icon={<CloseIcon />} aria-label="Close Tweak Panel" variant="ghost" @@ -120,7 +119,7 @@ export const Tweaks = (props: TweakProps) => { ...style, borderRadius: 10, }} - bg="purple.500" + bg={highlightColor + '.500'} /> )} > @@ -142,7 +141,6 @@ export const Tweaks = (props: TweakProps) => { <Flex justifyContent="space-between"> <Text>Orphans</Text> <Switch - colorScheme="purple" onChange={() => { setFilter({ ...filter, orphans: !filter.orphans }) }} @@ -152,7 +150,6 @@ export const Tweaks = (props: TweakProps) => { <Flex justifyContent="space-between"> <Text>Link nodes with parent file</Text> <Switch - colorScheme="purple" onChange={() => { setFilter({ ...filter, parents: !filter.parents }) }} @@ -172,7 +169,6 @@ export const Tweaks = (props: TweakProps) => { id="physicsOn" onChange={() => setPhysics({ ...physics, enabled: !physics.enabled })} isChecked={physics.enabled} - colorScheme="purple" /> </AccordionButton> <AccordionPanel> @@ -270,7 +266,7 @@ export const Tweaks = (props: TweakProps) => { label="Centering Strength" value={physics.centeringStrength} max={2} - step={0.1} + step={0.01} onChange={(v) => setPhysics({ ...physics, centeringStrength: v })} /> </EnableSection> @@ -296,13 +292,95 @@ export const Tweaks = (props: TweakProps) => { paddingLeft={7} color="gray.800" > - <EnableSection - label="Colors" - onChange={() => setPhysics({ ...physics, colorful: !physics.colorful })} - value={physics.colorful} - > - <Text>Child</Text> - </EnableSection> + <Accordion> + <AccordionItem> + <AccordionButton> + <AccordionIcon marginRight={2} /> + <Heading size="sm">Visual</Heading> + </AccordionButton> + <AccordionPanel> + <VStack + spacing={2} + justifyContent="flex-start" + divider={<StackDivider borderColor="gray.500" />} + align="stretch" + paddingLeft={7} + color="gray.800" + > + <Box> + <Flex alignItems="center" justifyContent="space-between"> + <Text>Node Color Scheme</Text> + <Menu> + <MenuButton as={Button} rightIcon={<ChevronDownIcon />}> + {visuals.nodeColorScheme === 'colorful' ? 'Colorful' : 'Plain'} + </MenuButton> + <MenuList bgColor="gray.200"> + <MenuItem onClick={() => setPhysics({ ...physics, labels: 1 })}> + Colorful + </MenuItem> + <MenuItem onClick={() => setPhysics({ ...physics, labels: 2 })}> + Plain + </MenuItem> + </MenuList> + </Menu> + </Flex> + <Flex alignItems="center" justifyContent="space-between"> + <Text>Link Color Scheme</Text> + <Menu> + <MenuButton as={Button} rightIcon={<ChevronDownIcon />}> + {visuals.linkColorScheme === 'colorful' ? 'Colorful' : 'Plain'} + </MenuButton> + <MenuList bgColor="gray.200"> + <MenuItem onClick={() => setPhysics({ ...physics, labels: 1 })}> + Colorful + </MenuItem> + <MenuItem onClick={() => setPhysics({ ...physics, labels: 2 })}> + Plain + </MenuItem> + </MenuList> + </Menu> + </Flex> + <Flex alignItems="center" justifyContent="space-between"> + <Text>Highlight color</Text> + <Menu> + <MenuButton as={Button} rightIcon={<ChevronDownIcon />}> + {highlightColor[0]!.toUpperCase() + highlightColor!.slice(1)} + </MenuButton> + <MenuList bgColor="gray.200" width={50}> + {[ + 'red', + 'orange', + 'yellow', + 'green', + 'cyan', + 'blue', + 'pink', + 'purple', + ].map((color) => ( + <MenuItem + key={color} + onClick={() => setHighlightColor(color)} + justifyContent="space-between" + alignItems="center" + display="flex" + > + <Text>{color[0]!.toUpperCase() + color!.slice(1)}</Text> + <Box + bgColor={color + '.500'} + borderRadius="sm" + height={6} + width={6} + ></Box> + </MenuItem> + ))} + </MenuList> + </Menu> + </Flex> + </Box> + </VStack> + </AccordionPanel> + </AccordionItem> + </Accordion> <SliderWithInfo label="Node size" value={physics.nodeRel} @@ -516,24 +594,18 @@ export const SliderWithInfo = ({ ...rest }: SliderWithInfoProps) => { const { onChange, label, infoText } = rest + const { highlightColor } = useContext(ThemeContext) return ( <Box> <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} - colorScheme="purple" - > + <Slider value={value} onChange={onChange} min={min} max={max} step={step}> <SliderTrack> <SliderFilledTrack /> </SliderTrack> - <Tooltip bg="purple.500" label={value.toFixed(1)}> + <Tooltip bg={highlightColor + '.500'} label={value.toFixed(1)}> <SliderThumb bg="white" /> </Tooltip> </Slider> @@ -558,7 +630,7 @@ export const EnableSection = (props: EnableSectionProps) => { <Text>{label}</Text> {infoText && <InfoTooltip infoText={infoText} />} </Box> - <Switch isChecked={!!value} onChange={onChange} colorScheme="purple" /> + <Switch isChecked={!!value} onChange={onChange} /> </Box> <Collapse in={!!value} animateOpacity> <Box paddingLeft={4} paddingTop={2}> diff --git a/pages/_app.tsx b/pages/_app.tsx index 410b333..c2edb69 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,9 +1,12 @@ import '../styles/globals.css' import type { AppProps } from 'next/app' -import { ChakraProvider, extendTheme } from '@chakra-ui/react' -import { useEffect, useState, useMemo } from 'react' +import { ChakraProvider, extendTheme, withDefaultColorScheme } from '@chakra-ui/react' +import { useEffect, useState, useMemo, useContext, useReducer } from 'react' import * as d3int from 'd3-interpolate' +import { ThemeContext } from './themecontext' +import { usePersistantState } from '../util/persistant-state' + function MyApp({ Component, pageProps }: AppProps) { const initialTheme = { base1: '#1c1f24', @@ -31,30 +34,81 @@ function MyApp({ Component, pageProps }: AppProps) { violet: '#a991f1', yellow: '#FCCE7B', } - const [emacsTheme, setEmacsTheme] = useState<typeof initialTheme>(initialTheme) - /* useEffect(() => { - * const trackTheme = new EventSource('http://127.0.0.1:35901/theme') - * trackTheme.addEventListener('message', (e) => { - * const themeData = JSON.parse(e.data) - * console.log("aa") - * if (!themeData.base4) { - * const bgfgInterpolate = d3int.interpolate(emacsTheme.bg, emacsTheme.fg) - * themeData.base1 = bgfgInterpolate(0.1) - * themeData.base2 = bgfgInterpolate(0.2) - * themeData.base3 = bgfgInterpolate(0.3) - * themeData.base4 = bgfgInterpolate(0.4) - * themeData.base5 = bgfgInterpolate(0.5) - * themeData.base6 = bgfgInterpolate(0.6) - * themeData.base7 = bgfgInterpolate(0.7) - * themeData.base8 = bgfgInterpolate(0.8) - * console.log('o o') - * } - * emacsTheme.bg !== themeData.bg && setEmacsTheme(themeData) - * }) - * }, []) - */ - const borderColor = emacsTheme.violet + 'aa' + const [isInitialized, setIsInitialized] = useState(false) + + const [emacsTheme, setEmacsTheme] = useState(initialTheme) + const [highlightColor, setHighlightColor] = useState('purple') + + useEffect(() => { + if (isInitialized) { + localStorage.setItem('theme', JSON.stringify(emacsTheme)) + } + }, [emacsTheme]) + + useEffect(() => { + if (isInitialized) { + localStorage.setItem('highlightColor', JSON.stringify(highlightColor)) + } + }, [highlightColor]) + + useEffect(() => { + setEmacsTheme( + JSON.parse(localStorage.getItem('theme') ?? JSON.stringify(initialTheme)) ?? initialTheme, + ) + setHighlightColor( + JSON.parse(localStorage.getItem('highlightColor') ?? JSON.stringify(highlightColor)) ?? + highlightColor, + ) + setIsInitialized(true) + }, []) + + const themeObject = { + emacsTheme: emacsTheme, + setEmacsTheme: setEmacsTheme, + highlightColor: highlightColor, + setHighlightColor: setHighlightColor, + } + return ( + <ThemeContext.Provider value={themeObject as typeof themeObject}> + <SubApp> + <Component {...pageProps} /> + </SubApp> + </ThemeContext.Provider> + ) +} + +function SubApp(props: any) { + const { children } = props + const { highlightColor, emacsTheme } = useContext(ThemeContext) + // yeah it's annoying, should put this someplace more sensible + const getBorderColor = () => { + if (highlightColor === 'purple') { + return emacsTheme.violet + 'aa' + } + if (highlightColor === 'pink') { + return emacsTheme.magenta + 'aa' + } + if (highlightColor === 'blue') { + return emacsTheme.blue + 'aa' + } + if (highlightColor === 'cyan') { + return emacsTheme.cyan + 'aa' + } + if (highlightColor === 'green') { + return emacsTheme.green + 'aa' + } + if (highlightColor === 'yellow') { + return emacsTheme.yellow + 'aa' + } + if (highlightColor === 'orange') { + return emacsTheme.orange + 'aa' + } + if (highlightColor === 'red') { + return emacsTheme.red + 'aa' + } + } const missingColor = d3int.interpolate(emacsTheme.base1, emacsTheme.base2)(0.2) + const borderColor = getBorderColor() const theme = useMemo(() => { console.log('ii') return { @@ -112,23 +166,19 @@ function MyApp({ Component, pageProps }: AppProps) { variants: { outline: { border: '2px solid', - borderColor: 'purple.500', - color: 'purple.500', + borderColor: highlightColor + '.500', + color: highlightColor + '.500', }, ghost: { - color: 'purple.500', + color: highlightColor + '.500', }, }, }, }, } - }, [JSON.stringify(emacsTheme)]) + }, [highlightColor, JSON.stringify(emacsTheme)]) - const extendedTheme = extendTheme(theme) - return ( - <ChakraProvider theme={extendedTheme}> - <Component {...pageProps} setEmacsTheme={setEmacsTheme} /> - </ChakraProvider> - ) + const extendedTheme = extendTheme(theme, withDefaultColorScheme({ colorScheme: highlightColor })) + return <ChakraProvider theme={extendedTheme}>{children}</ChakraProvider> } export default MyApp diff --git a/pages/index.tsx b/pages/index.tsx index e330b91..6add88e 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,4 +1,11 @@ -import React, { ComponentPropsWithoutRef, useEffect, useRef, useState, useMemo } from 'react' +import React, { + ComponentPropsWithoutRef, + useEffect, + useRef, + useState, + useMemo, + useContext, +} from 'react' import { usePersistantState } from '../util/persistant-state' const d3promise = import('d3-force-3d') import * as d3int from 'd3-interpolate' @@ -15,9 +22,10 @@ import { useAnimation } from '@lilib/hooks' import { Box, useTheme } from '@chakra-ui/react' -import { initialPhysics, initialFilter } from '../components/config' +import { initialPhysics, initialFilter, initialVisuals } from '../components/config' import { Tweaks } from '../components/tweaks' +import { ThemeContext, ThemeContextProps } from './themecontext' // react-force-graph fails on import when server-rendered // https://github.com/vasturiano/react-force-graph/issues/155 const ForceGraph2D = ( @@ -35,7 +43,7 @@ export type Scope = { nodeIds: string[] } -export default function Home(setEmacsTheme: any) { +export default function Home() { // only render on the client const [showPage, setShowPage] = useState(false) useEffect(() => { @@ -46,12 +54,13 @@ export default function Home(setEmacsTheme: any) { return null } - return <GraphPage setEmacsTheme={setEmacsTheme} /> + return <GraphPage /> } -export function GraphPage(setEmacsTheme: any) { +export function GraphPage() { const [physics, setPhysics] = usePersistantState('physics', initialPhysics) const [filter, setFilter] = usePersistantState('filter', initialFilter) + const [visuals, setVisuals] = usePersistantState('visuals', initialVisuals) const [graphData, setGraphData] = useState<GraphData | null>(null) const [emacsNodeId, setEmacsNodeId] = useState<string | null>(null) @@ -114,7 +123,7 @@ export function GraphPage(setEmacsTheme: any) { const orgRoamGraphDataClone = JSON.parse(JSON.stringify(orgRoamGraphDataWithFileLinks)) setGraphData(orgRoamGraphDataClone) } - + const { setEmacsTheme } = useContext(ThemeContext) useEffect(() => { const socket = new WebSocket('ws://localhost:35903') socket.addEventListener('open', (e) => { @@ -132,7 +141,7 @@ export function GraphPage(setEmacsTheme: any) { console.log('Received theme data') console.log(message.data) console.log(setEmacsTheme) - setEmacsTheme.setEmacsTheme.setEmacsTheme(message.data) + setEmacsTheme(message.data) break case 'command': console.log('command') @@ -182,6 +191,8 @@ export function GraphPage(setEmacsTheme: any) { setThreeDim, filter, setFilter, + visuals, + setVisuals, }} /> <Box position="absolute" alignItems="top"> @@ -194,6 +205,7 @@ export function GraphPage(setEmacsTheme: any) { threeDim, emacsNodeId, filter, + visuals, }} /> </Box> @@ -209,10 +221,12 @@ export interface GraphProps { threeDim: boolean filter: typeof initialFilter emacsNodeId: string | null + visuals: typeof initialVisuals } export const Graph = function (props: GraphProps) { - const { physics, graphData, threeDim, linksByNodeId, filter, emacsNodeId, nodeById } = props + const { physics, graphData, threeDim, linksByNodeId, filter, emacsNodeId, nodeById, visuals } = + props const graph2dRef = useRef<any>(null) const graph3dRef = useRef<any>(null) @@ -410,10 +424,15 @@ export const Graph = function (props: GraphProps) { } }, [hoverNode]) const theme = useTheme() + const themeContext = useContext<ThemeContextProps>(ThemeContext) const interPurple = useMemo( () => d3int.interpolate(theme.colors.gray[500], theme.colors.purple[500]), [theme], ) + const interHighlight = useMemo( + () => d3int.interpolate(theme.colors.gray[500], theme.colors[themeContext.highlightColor][500]), + [theme, visuals.highlightColor], + ) const interGray = useMemo( () => d3int.interpolate(theme.colors.gray[500], theme.colors.gray[400]), [theme], @@ -446,7 +465,7 @@ export const Graph = function (props: GraphProps) { nodeColor: (node) => { if (!physics.colorful) { return previouslyHighlightedNodes[node.id!] || highlightedNodes[node.id!] - ? interPurple(opacity) + ? interHighlight(opacity) : interGray(opacity) } if (node.id === emacsNodeId) { @@ -554,7 +573,9 @@ export const Graph = function (props: GraphProps) { linkColor: (link) => { const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode) const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current) - return linkIsHighlighted || linkWasHighlighted ? interPurple(opacity) : theme.colors.gray[500] + return linkIsHighlighted || linkWasHighlighted + ? interHighlight(opacity) + : theme.colors.gray[500] }, linkWidth: (link) => { const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode) |