import { CloseIcon, RepeatClockIcon, ChevronDownIcon, SettingsIcon, InfoOutlineIcon, RepeatIcon, ArrowRightIcon, AddIcon, DeleteIcon, CheckCircleIcon, } from '@chakra-ui/icons' import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Box, Button, Flex, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuOptionGroup, MenuItemOption, Select, Slider, SliderFilledTrack, SliderThumb, SliderTrack, StackDivider, Switch, Text, Tooltip, VStack, Heading, Collapse, Grid, Portal, SlideFade, Input, } from '@chakra-ui/react' import { CUIAutoComplete } from 'chakra-ui-autocomplete' import React, { useState, useContext, useEffect, useCallback } from 'react' import Scrollbars from 'react-custom-scrollbars-2' import { initialPhysics, initialFilter, initialVisuals, initialMouse, initialBehavior, TagColors, colorList, } from './config' import { ThemeContext } from '../util/themecontext' import { usePersistantState } from '../util/persistant-state' export interface TweakProps { physics: typeof initialPhysics setPhysics: any threeDim: boolean setThreeDim: (newValue: boolean) => void filter: typeof initialFilter setFilter: any visuals: typeof initialVisuals setVisuals: any mouse: typeof initialMouse setMouse: any behavior: typeof initialBehavior setBehavior: any tags: string[] tagColors: TagColors setTagColors: any } export const Tweaks = (props: TweakProps) => { const { physics, setPhysics, threeDim, setThreeDim, filter, setFilter, visuals, setVisuals, mouse, setMouse, behavior, setBehavior, tags, tagColors, setTagColors, } = props const [showTweaks, setShowTweaks] = usePersistantState('showTweaks', false) const { highlightColor, setHighlightColor } = useContext(ThemeContext) const setVisualsCallback = useCallback((val) => setVisuals(val), []) const setPhysicsCallback = useCallback((val: number, phys: string, scale: number) => { setPhysics((curr: typeof initialPhysics) => { return { ...curr, [phys]: val / scale } }) }, []) return !showTweaks ? ( } onClick={() => setShowTweaks(true)} /> ) : ( } onClick={() => { setVisuals(initialVisuals) setFilter(initialFilter) setMouse(initialMouse) setPhysics(initialPhysics) setBehavior(initialBehavior) }} variant="none" size="sm" /> } aria-label="Close Tweak Panel" variant="ghost" onClick={() => setShowTweaks(false)} /> ( )} > Filter } align="stretch" paddingLeft={7} color="gray.800" > Orphans { setFilter((curr: typeof initialFilter) => { return { ...curr, orphans: !curr.orphans } }) }} isChecked={filter.orphans} > Link nodes with parent file { setFilter({ ...filter, parents: !filter.parents }) }} isChecked={filter.parents} > Citations without note files { setFilter({ ...filter, filelessCites: !filter.filelessCites }) }} isChecked={filter.filelessCites} > Non-existant nodes { setTagColors({ ...tagColors, bad: 'white' }) setFilter({ ...filter, bad: !filter.bad }) }} isChecked={filter.bad} > Tag filters Tag Colors Physics {/* setPhysics({ ...physics, enabled: !physics.enabled })} isChecked={physics.enabled} /> */} } align="stretch" paddingLeft={7} color="gray.800" > setPhysics({ ...physics, gravityOn: !physics.gravityOn })} > Also in local { setPhysics((curr: typeof initialPhysics) => { return { ...curr, gravityLocal: !curr.gravityLocal } }) }} isChecked={physics.gravityLocal} > setPhysicsCallback(v, 'gravity', 10)} /> setPhysicsCallback(v, 'gravity', -1 / 100)} label="Repulsive Force" /> setPhysics({ ...physics, collision: !physics.collision })} > setPhysicsCallback(v, 'collisionStrength', 1 / 5)} label="Collision Radius" infoText="Easy with this one, high values can lead to a real jiggly mess" /> setPhysicsCallback(v, 'linkStrength', 5)} label="Link Force" /> 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)" /> setPhysicsCallback(v, 'velocityDecay', 10)} /> Advanced } align="stretch" paddingLeft={3} color="gray.800" > setPhysicsCallback(v, 'alphaDecay', 50)} /> 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." > setPhysicsCallback(v, 'centeringStrength', 1)} /> Visual Colors } align="stretch" color="gray.800" > Nodes } variant="ghost" onClick={() => { const arr = visuals.nodeColorScheme ?? [] setVisuals({ ...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), }) }} /> } size="sm" variant="ghost" onClick={() => { const arr = visuals.nodeColorScheme ?? [] setVisuals({ ...visuals, nodeColorScheme: [...arr.slice(1, arr.length), arr[0]], }) }} /> } > {visuals.nodeColorScheme.map((color) => ( ))} {' '} { if (!colors.length) { return } setVisuals({ ...visuals, nodeColorScheme: colors }) }} > {colorList.map((color) => ( c === color)} value={color} isDisabled={ visuals.nodeColorScheme.length === 1 && visuals.nodeColorScheme[0] === color } > ))} Links } > {visuals.linkColorScheme ? ( ) : ( {visuals.nodeColorScheme.map((color) => ( ))} )} {' '} setVisuals({ ...visuals, linkColorScheme: '' })} justifyContent="space-between" alignItems="center" display="flex" > {visuals.nodeColorScheme.map((color) => ( ))} {colorList.map((color) => ( setVisuals({ ...visuals, linkColorScheme: color, }) } justifyContent="space-between" alignItems="center" display="flex" > ))} Accent } > { } {' '} {colorList.map((color) => ( setHighlightColor(color)} justifyContent="space-between" alignItems="center" display="flex" > ))} } align="stretch" paddingLeft={7} color="gray.800" > setVisuals({ ...visuals, nodeRel: value })} /> setVisuals({ ...visuals, nodeSizeLinks: value })} /> {threeDim && ( <> setVisuals({ ...visuals, nodeOpacity: value })} /> setVisuals({ ...visuals, nodeResolution: value })} /> )} setVisuals({ ...visuals, linkWidth: value })} /> {threeDim && ( setVisuals({ ...visuals, linkOpacity: value })} /> )} setVisuals({ ...visuals, citeDashes: !visuals.citeDashes })} > setVisuals({ ...visuals, citeDashLength: value * 10 })} /> setVisuals({ ...visuals, citeGapLength: value * 5 })} /> setVisuals({ ...visuals, refDashes: !visuals.refDashes })} > setVisuals({ ...visuals, refDashLength: value * 10 })} /> setVisuals({ ...visuals, refGapLength: value * 5 })} /> Labels } > {!visuals.labels ? 'Never' : visuals.labels < 2 ? 'On Highlight' : 'Always'} {' '} setVisuals({ ...visuals, labels: 0 })}> Never setVisuals({ ...visuals, labels: 1 })}> On Highlight setVisuals({ ...visuals, labels: 2 })}> Always setVisuals({ ...visuals, labels: 3 })}> Always (even in 3D) 0} animateOpacity> } align="stretch" paddingLeft={2} color="gray.800" > setVisuals({ ...visuals, labelFontSize: value })} /> setVisuals({ ...visuals, labelLength: value })} /> { console.log(visuals.labelBackgroundOpacity) setVisuals({ ...visuals, labelBackgroundOpacity: value }) }} min={0} max={1} step={0.01} /> 1} animateOpacity> setVisuals({ ...visuals, labelScale: value / 5 }) } /> setVisuals({ ...visuals, arrows: !visuals.arrows })} > setVisuals({ ...visuals, arrowsLength: 10 * value })} /> setVisuals({ ...visuals, arrowsPos: value })} /> setVisuals({ ...visuals, particles: !visuals.particles })} > setVisuals({ ...visuals, particlesNumber: value })} /> setVisuals({ ...visuals, particlesWidth: value })} /> setVisuals({ ...visuals, highlight: !visuals.highlight })} value={visuals.highlight} > } align="stretch" paddingLeft={0} > setVisuals({ ...visuals, highlightLinkSize: value })} /> setVisuals({ ...visuals, highlightNodeSize: value })} /> setVisuals({ ...visuals, highlightFade: value })} /> {/* Highlight node color Highlight link color */} { setVisuals({ ...visuals, highlightAnim: !visuals.highlightAnim }) }} value={visuals.highlightAnim} > setVisuals({ ...visuals, animationSpeed: v })} value={visuals.animationSpeed} infoText="Slower speed has a chance of being buggy" min={50} max={1000} step={10} /> Behavior } align="stretch" paddingLeft={7} color="gray.800" > Expand Node } colorScheme="" color="black" > {mouse.local ? mouse.local[0]!.toUpperCase() + mouse.local!.slice(1) : 'Never'} {' '} setMouse({ ...mouse, local: '' })}>Never setMouse({ ...mouse, local: 'click' })}> Click setMouse({ ...mouse, local: 'double' })}> Double Click setMouse({ ...mouse, local: 'right' })}> Right Click Open in Emacs } colorScheme="" color="black" > {mouse.follow ? mouse.follow[0]!.toUpperCase() + mouse.follow!.slice(1) : 'Never'} {' '} setMouse({ ...mouse, follow: '' })}> Never setMouse({ ...mouse, follow: 'click' })}> Click setMouse({ ...mouse, follow: 'double' })}> Double Click setMouse({ ...mouse, follow: 'right' })}> Right Click Follow Emacs by... } colorScheme="" color="black" > {behavior.follow[0].toUpperCase() + behavior.follow.slice(1)} {' '} setBehavior({ ...behavior, follow: 'color' })}> Just coloring the currently opened node setBehavior({ ...behavior, follow: 'local' })}> Opening the local graph setBehavior({ ...behavior, follow: 'zoom' })}> Zooming to the current node Local graph } colorScheme="" color="black" > {behavior.localSame === 'add' ? 'Add' : 'Replace'} {' '} setBehavior({ ...behavior, localSame: 'replace' })} > Open that nodes graph setBehavior({ ...behavior, localSame: 'add' })}> Add node to local graph setBehavior({ ...behavior, zoomSpeed: value })} /> setBehavior({ ...behavior, zoomPadding: value })} infoText="How much to zoom out to accomodate all nodes when changing the view." /> ) } export interface InfoTooltipProps { infoText?: string | boolean } export const InfoTooltip = (props: InfoTooltipProps) => { const { infoText } = props return ( ) } 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 ( {label} {infoText && } ) } 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 ( {label} {infoText && } {children} ) } export interface DropDownMenuProps { textArray: string[] onClickArray: (() => void)[] displayValue: string } export const DropDownMenu = (props: DropDownMenuProps) => { const { textArray, onClickArray, displayValue } = props return ( }> {displayValue} {' '} {textArray.map((option, i) => { ; {option} })} ) } 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 ( {label} }> {} {' '} clickCallback('')} justifyContent="space-between" alignItems="center" display="flex" > {colorList.map((color: string) => ( clickCallback(color)} justifyContent="space-between" alignItems="center" display="flex" > ))} ) } 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( filter[currentTags].map((tag) => { return { value: tag, label: tag } }), ) return ( 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} /> ) } 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( Object.keys(tagColors).map((tag) => { return { value: tag, label: tag } }), ) return ( { 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} /> } align="stretch" color="gray.800" > {Object.keys(tagColors).map((tag) => { return ( {tag} {} {' '} {colorList.map((color: string) => ( setTagColors({ ...tagColors, [tag]: color, }) } justifyContent="space-between" alignItems="center" display="flex" > ))} } 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)) }} /> ) })} ) }