import { CloseIcon, RepeatClockIcon, ChevronDownIcon, SettingsIcon, InfoOutlineIcon, } from '@chakra-ui/icons' import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Box, Button, Flex, IconButton, Menu, MenuButton, MenuItem, MenuList, Select, Slider, SliderFilledTrack, SliderThumb, SliderTrack, StackDivider, Switch, Text, Tooltip, VStack, Heading, Collapse, } from '@chakra-ui/react' import React, { useState } from 'react' import Scrollbars from 'react-custom-scrollbars-2' import { initialPhysics, initialFilter } from './config' export interface TweakProps { physics: typeof initialPhysics setPhysics: any threeDim: boolean setThreeDim: (newValue: boolean) => void filter: typeof initialFilter setFilter: any } export const Tweaks = (props: TweakProps) => { const { physics, setPhysics, threeDim, setThreeDim, filter, setFilter } = props const [showTweaks, setShowTweaks] = useState(true) return ( <> <Box position="relative" zIndex="overlay" marginTop={10} marginLeft={10} display={showTweaks ? 'none' : 'block'} > <IconButton aria-label="Settings" icon={<SettingsIcon />} onClick={() => setShowTweaks(true)} /> </Box> <Collapse in={showTweaks} animateOpacity> <Box bg="alt.100" w="xs" marginTop={10} marginLeft={10} borderRadius="xl" maxH={650} paddingBottom={5} zIndex="overlay" 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={threeDim ? 'solid' : 'outline'} zIndex="overlay" > {threeDim ? '2D' : '3D'} </Button> </Tooltip> <Box display="flex" alignItems="center"> <Tooltip label="Reset settings to defaults"> <IconButton 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" onClick={() => setShowTweaks(false)} /> </Box> </Box> <Scrollbars autoHeight autoHeightMax={600} autoHide renderThumbVertical={({ style, ...props }) => ( <Box {...props} style={{ ...style, borderRadius: 10, }} bg="purple.500" /> )} > <Accordion allowMultiple allowToggle color="black" paddingRight={2}> <AccordionItem> <AccordionButton> <AccordionIcon marginRight={2} /> <Heading size="sm">Filter</Heading> </AccordionButton> <AccordionPanel> <VStack spacing={2} justifyContent="flex-start" divider={<StackDivider borderColor="gray.500" />} align="stretch" paddingLeft={7} color="gray.800" > <Flex justifyContent="space-between"> <Text>Orphans</Text> <Switch colorScheme="purple" onChange={() => { setFilter({ ...filter, orphans: !filter.orphans }) }} isChecked={filter.orphans} ></Switch> </Flex> <Flex justifyContent="space-between"> <Text>Link nodes with parent file</Text> <Switch colorScheme="purple" onChange={() => { setFilter({ ...filter, parents: !filter.parents }) }} isChecked={filter.parents} ></Switch> </Flex> </VStack> </AccordionPanel> </AccordionItem> <AccordionItem> <AccordionButton display="flex" justifyContent="space-between"> <Box display="flex"> <AccordionIcon marginRight={2} /> <Heading size="sm">Physics</Heading> </Box> <Switch id="physicsOn" onChange={() => setPhysics({ ...physics, enabled: !physics.enabled })} isChecked={physics.enabled} colorScheme="purple" /> </AccordionButton> <AccordionPanel> <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 })} > <SliderWithInfo label="Strength" value={physics.gravity * 10} onChange={(v) => setPhysics({ ...physics, gravity: v / 10 })} /> </EnableSection> <SliderWithInfo value={-physics.charge / 100} onChange={(value) => setPhysics({ ...physics, charge: -100 * value })} 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 * 10} onChange={(value) => setPhysics({ ...physics, collisionStrength: value / 10 }) } label="Strength" /> </EnableSection> <SliderWithInfo value={physics.linkStrength * 5} onChange={(value) => setPhysics({ ...physics, linkStrength: value / 5 })} label="Link Force" /> <SliderWithInfo label="Link Iterations" value={physics.linkIts} onChange={(value) => setPhysics({ ...physics, linkIts: value })} 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={(value) => setPhysics({ ...physics, velocityDecay: value / 10 })} /> </VStack> <Box> <Accordion 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="Iterations per tick" min={1} max={10} step={1} value={physics.iterations} onChange={(v) => setPhysics({ ...physics, iterations: v })} infoText="Number of times the physics simulation iterates per simulation step" /> <SliderWithInfo label="Stabilization rate" value={physics.alphaDecay * 50} onChange={(value) => setPhysics({ ...physics, alphaDecay: value / 50 }) } /> </VStack> </AccordionPanel> </AccordionItem> </Accordion> </Box> {/* </VStack> */} </AccordionPanel> </AccordionItem> <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" > <EnableSection label="Colors" onChange={() => setPhysics({ ...physics, colorful: !physics.colorful })} value={physics.colorful} > <Text>Child</Text> </EnableSection> <SliderWithInfo label="Node size" value={physics.nodeRel} onChange={(value) => setPhysics({ ...physics, nodeRel: value })} /> <SliderWithInfo label="Link width" value={physics.linkWidth} onChange={(value) => setPhysics({ ...physics, linkWidth: value })} /> <Box> <Flex alignItems="center" justifyContent="space-between"> <Text>Labels</Text> <Menu> <MenuButton as={Button} rightIcon={<ChevronDownIcon />}> {!physics.labels ? 'Never' : physics.labels < 2 ? 'On Highlight' : 'Always'} </MenuButton> <MenuList bgColor="gray.200"> <MenuItem onClick={() => setPhysics({ ...physics, labels: 0 })}> Never </MenuItem> <MenuItem onClick={() => setPhysics({ ...physics, labels: 1 })}> On Highlight </MenuItem> <MenuItem onClick={() => setPhysics({ ...physics, labels: 2 })}> Always </MenuItem> </MenuList> </Menu> </Flex> <Collapse in={physics.labels > 1} animateOpacity> <Box paddingLeft={4} paddingTop={2}> <SliderWithInfo label="Label Appearance Scale" value={physics.labelScale * 5} onChange={(value) => setPhysics({ ...physics, labelScale: value / 5 })} /> </Box> </Collapse> </Box> <EnableSection label="Directional Particles" value={physics.particles} onChange={() => setPhysics({ ...physics, particles: !physics.particles })} > <SliderWithInfo label="Particle Number" value={physics.particlesNumber} max={5} step={1} onChange={(value) => setPhysics({ ...physics, particlesNumber: value })} /> <SliderWithInfo label="Particle Size" value={physics.particlesWidth} onChange={(value) => setPhysics({ ...physics, particlesWidth: value })} /> </EnableSection> <EnableSection label="Highlight" onChange={() => setPhysics({ ...physics, highlight: !physics.highlight })} value={physics.highlight} > <VStack spacing={1} justifyContent="flex-start" divider={<StackDivider borderColor="gray.400" />} align="stretch" paddingLeft={0} > <SliderWithInfo label="Highlight Link Thickness" value={physics.highlightLinkSize} onChange={(value) => setPhysics({ ...physics, highlightLinkSize: value })} /> <SliderWithInfo label="Highlight Node Size" value={physics.highlightNodeSize} onChange={(value) => setPhysics({ ...physics, highlightNodeSize: value })} /> {/*<Flex justifyContent="space-between"> <Text> Highlight node color </Text> </Flex> <Flex justifyContent="space-between"> <Text> Highlight link color </Text> </Flex>*/} <EnableSection label="Highlight Animation" onChange={() => { setPhysics({ ...physics, highlightAnim: !physics.highlightAnim }) }} value={physics.highlightAnim} > <SliderWithInfo label="Animation speed" onChange={(v) => setPhysics({ ...physics, animationSpeed: v })} value={physics.animationSpeed} infoText="Slower speed has a chance of being buggy" min={50} max={1000} step={10} /> <Select placeholder={physics.algorithmName} onChange={(v) => { setPhysics({ ...physics, algorithmName: v.target.value }) }} > {physics.algorithmOptions.map((opt: string) => ( <option key={opt} value={opt}> {opt} </option> ))} </Select> {/* <DropDownMenu displayValue={physics.algorithmName} textArray={physics.algorithmOptions} onClickArray={physics.algorithmOptions.map((option) => setPhysics({ ...physics, algorithmName: { option } }), )} /> */} </EnableSection> </VStack> </EnableSection> </VStack> </AccordionPanel> </AccordionItem> <AccordionItem> <AccordionButton> <AccordionIcon marginRight={2} /> <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" ></VStack> </AccordionPanel> </AccordionItem> </Accordion> </Scrollbars> </Box> </Collapse> </> ) } 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> ) } 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 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" > <SliderTrack> <SliderFilledTrack /> </SliderTrack> <Tooltip bg="purple.500" label={value.toFixed(1)}> <SliderThumb bg="white" /> </Tooltip> </Slider> </Box> ) } 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> <Box display="flex" justifyContent="space-between"> <Box display="flex" alignItems="center"> <Text>{label}</Text> {infoText && <InfoTooltip infoText={infoText} />} </Box> <Switch isChecked={!!value} onChange={onChange} colorScheme="purple" /> </Box> <Collapse in={!!value} animateOpacity> <Box paddingLeft={4} paddingTop={2}> {children} </Box> </Collapse> </Box> ) } export interface DropDownMenuProps { textArray: string[] onClickArray: (() => void)[] displayValue: string } export const DropDownMenu = (props: DropDownMenuProps) => { const { textArray, onClickArray, displayValue } = props return ( <Menu> <MenuButton as={Button} rightIcon={<ChevronDownIcon />}> {displayValue} </MenuButton> <MenuList> {textArray.map((option, i) => { ;<MenuItem onClick={onClickArray[i]}> {option} </MenuItem> })} </MenuList> </Menu> ) }