diff --git a/src/renderer/components/CustomEdge/CustomEdge.scss b/src/renderer/components/CustomEdge/CustomEdge.scss index 9996e216c..781c727b0 100644 --- a/src/renderer/components/CustomEdge/CustomEdge.scss +++ b/src/renderer/components/CustomEdge/CustomEdge.scss @@ -21,7 +21,7 @@ transition-timing-function: ease-in-out; stroke: var(--bg-700); - &.hovered { + .edge-chain-group:hover & { stroke-width: 4px; } } @@ -36,9 +36,9 @@ stroke-dashoffset: 0; stroke-linecap: round; animation: 'none'; - opacity: 100%; + opacity: 1; - &.hovered { + .edge-chain-group:hover & { stroke-width: 4px; } @@ -64,7 +64,7 @@ opacity: 1; } - &.hovered { + .edge-chain-group:hover & { stroke-width: 4px; } diff --git a/src/renderer/components/CustomEdge/CustomEdge.tsx b/src/renderer/components/CustomEdge/CustomEdge.tsx index 1f426312d..aa6b34fbb 100644 --- a/src/renderer/components/CustomEdge/CustomEdge.tsx +++ b/src/renderer/components/CustomEdge/CustomEdge.tsx @@ -1,10 +1,9 @@ import { NeverType } from '@chainner/navi'; import { Center, Icon, IconButton } from '@chakra-ui/react'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { TbUnlink } from 'react-icons/tb'; import { EdgeProps, getBezierPath, getStraightPath, useKeyPress, useReactFlow } from 'reactflow'; import { useContext, useContextSelector } from 'use-context-selector'; -import { useDebouncedCallback } from 'use-debounce'; import { Circle, Vec2 } from '../../../common/2d'; import { EdgeData, NodeData } from '../../../common/common-types'; import { assertNever, parseSourceHandle } from '../../../common/util'; @@ -26,18 +25,10 @@ import './CustomEdge.scss'; const EDGE_CLASS = { RUNNING: 'running', YET_TO_RUN: 'yet-to-run', - HOVERED: 'hovered', COLLIDING: 'colliding', NONE: '', }; -const getHoveredClass = (isHovered: boolean) => { - if (isHovered) { - return EDGE_CLASS.HOVERED; - } - return EDGE_CLASS.NONE; -}; - const getCollidingClass = (isColliding: boolean) => { if (isColliding) { return EDGE_CLASS.COLLIDING; @@ -181,8 +172,6 @@ export const CustomEdge = memo( const { removeEdgeById, addEdgeBreakpoint } = useContext(GlobalContext); const { functionDefinitions } = useContext(BackendContext); - const [isHovered, setIsHovered] = useState(false); - const { outputId } = useMemo(() => parseSourceHandle(sourceHandleId!), [sourceHandleId]); const definitionType = functionDefinitions.get(edgeParentNode.data.schemaId)?.outputDefaults.get(outputId) ?? @@ -196,11 +185,6 @@ export const CustomEdge = memo( const buttonSize = 32; - // Prevent hovered state from getting stuck - const hoverTimeout = useDebouncedCallback(() => { - setIsHovered(false); - }, 7500); - const showRunning = animated && !paused; const isColliding = useContextSelector( @@ -210,12 +194,12 @@ export const CustomEdge = memo( const classModifier = useMemo( () => - `${getHoveredClass(isHovered)} ${getRunningStateClass( + `${getRunningStateClass( sourceStatus, targetStatus, animateChain )} ${getCollidingClass(isColliding)}`, - [isHovered, sourceStatus, targetStatus, isColliding, animateChain] + [sourceStatus, targetStatus, isColliding, animateChain] ); // NOTE: I know that technically speaking this is bad @@ -255,6 +239,7 @@ export const CustomEdge = memo( return ( removeEdgeById(id)} - onMouseEnter={() => setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - onMouseOver={() => hoverTimeout()} > {showRunning && (
diff --git a/src/renderer/components/NodeSelectorPanel/RepresentativeNode.tsx b/src/renderer/components/NodeSelectorPanel/RepresentativeNode.tsx index d4e0fd42c..56ac1fbee 100644 --- a/src/renderer/components/NodeSelectorPanel/RepresentativeNode.tsx +++ b/src/renderer/components/NodeSelectorPanel/RepresentativeNode.tsx @@ -1,170 +1,170 @@ +/* eslint-disable react/prop-types */ import { StarIcon } from '@chakra-ui/icons'; import { Box, Center, Flex, HStack, Heading, Spacer } from '@chakra-ui/react'; -import { memo, useState } from 'react'; +import { memo, useMemo } from 'react'; import { useContext } from 'use-context-selector'; -import { CategoryId, SchemaId } from '../../../common/common-types'; +import { NodeSchema } from '../../../common/common-types'; import { BackendContext } from '../../contexts/BackendContext'; import { getCategoryAccentColor } from '../../helpers/accentColors'; import { useNodeFavorites } from '../../hooks/useNodeFavorites'; import { IconFactory } from '../CustomIcons'; interface RepresentativeNodeProps { - category: CategoryId; - icon: string; - name: string; - collapsed?: boolean; - schemaId: SchemaId; - createNodeFromSelector: () => void; + schema: NodeSchema; + collapsed: boolean; + onCreateNode: () => void; } export const RepresentativeNode = memo( - ({ - category, - name, - icon, - schemaId, - collapsed = false, - createNodeFromSelector, - }: RepresentativeNodeProps) => { - const { categories, schemata } = useContext(BackendContext); - const schema = schemata.get(schemaId); + ({ schema, collapsed, onCreateNode }: RepresentativeNodeProps) => { + const { categories } = useContext(BackendContext); const bgColor = 'var(--selector-node-bg)'; - const accentColor = getCategoryAccentColor(categories, category); - - const [hover, setHover] = useState(false); + const accentColor = getCategoryAccentColor(categories, schema.category); const { favorites, addFavorites, removeFavorite } = useNodeFavorites(); - const isFavorite = favorites.has(schemaId); + const isFavorite = favorites.has(schema.schemaId); const isIterator = schema.kind === 'newIterator' || schema.kind === 'collector'; let bgGradient = `linear-gradient(90deg, ${accentColor} 0%, ${accentColor} 100%)`; if (isIterator) { bgGradient = `repeating-linear(to right,${accentColor},${accentColor} 2px,${bgColor} 2px,${bgColor} 4px)`; - } else if (!hover) { - bgGradient = `linear-gradient(90deg, ${accentColor} 0%, ${bgColor} 100%)`; } - return ( -
{ - if (e.key === 'Enter') { - createNodeFromSelector(); - } - }} - onMouseEnter={() => setHover(true)} - onMouseLeave={() => setHover(false)} - > - ( +
{ + if (e.key === 'Enter') { + onCreateNode(); + } + }} > - -
- -
- {!collapsed && ( - - +
+ {!collapsed && ( + - - {name} - -
- - - + {schema.name} + + + + { - e.stopPropagation(); - if (isFavorite) { - removeFavorite(schemaId); - } else { - addFavorites(schemaId); - } - }} - onDoubleClick={(e) => e.stopPropagation()} - /> - - - )} - - -
+ w="fit-content" + > + { + e.stopPropagation(); + if (isFavorite) { + removeFavorite(schema.schemaId); + } else { + addFavorites(schema.schemaId); + } + }} + onDoubleClick={(e) => e.stopPropagation()} + /> + + + )} + + +
+ ), + [ + accentColor, + addFavorites, + bgGradient, + collapsed, + isFavorite, + onCreateNode, + removeFavorite, + schema, + ] ); } ); diff --git a/src/renderer/components/NodeSelectorPanel/RepresentativeNodeWrapper.tsx b/src/renderer/components/NodeSelectorPanel/RepresentativeNodeWrapper.tsx index af9759c5b..25c9ebbbd 100644 --- a/src/renderer/components/NodeSelectorPanel/RepresentativeNodeWrapper.tsx +++ b/src/renderer/components/NodeSelectorPanel/RepresentativeNodeWrapper.tsx @@ -1,6 +1,7 @@ +/* eslint-disable react/prop-types */ import { StarIcon } from '@chakra-ui/icons'; import { Box, Center, MenuItem, MenuList, Text, Tooltip, useDisclosure } from '@chakra-ui/react'; -import { DragEvent, memo, useCallback, useEffect, useState } from 'react'; +import { DragEvent, memo, useCallback, useEffect, useMemo, useState } from 'react'; import { BsFillJournalBookmarkFill } from 'react-icons/bs'; import { useReactFlow } from 'reactflow'; import { useContext } from 'use-context-selector'; @@ -50,11 +51,11 @@ const onDragStart = (event: DragEvent, node: NodeSchema) => { interface RepresentativeNodeWrapperProps { node: NodeSchema; - collapsed?: boolean; + collapsed: boolean; } export const RepresentativeNodeWrapper = memo( - ({ node, collapsed = false }: RepresentativeNodeWrapperProps) => { + ({ node, collapsed }: RepresentativeNodeWrapperProps) => { const { reactFlowWrapper, createNode } = useContext(GlobalContext); const { featureStates } = useContext(BackendContext); const reactFlowInstance = useReactFlow(); @@ -100,7 +101,7 @@ export const RepresentativeNodeWrapper = memo( )); - const createNodeFromSelector = useCallback(() => { + const onCreateNode = useCallback(() => { if (!reactFlowWrapper.current) return; const { @@ -137,69 +138,78 @@ export const RepresentativeNodeWrapper = memo( .join('\n') : undefined; - return ( - - ( + - - - } - openDelay={500} - placement="bottom" - px={2} - py={1} - > -
{ - setDidSingleClick(true); - }} - onDoubleClick={() => { - setDidSingleClick(false); - createNodeFromSelector(); - }} - onDragStart={(event) => { - setDidSingleClick(false); - onDragStart(event, node); - }} + + + + } + openDelay={500} + placement="bottom" + px={2} + py={1} > - -
-
-
-
-
+
{ + setDidSingleClick(true); + }} + onDoubleClick={() => { + setDidSingleClick(false); + onCreateNode(); + }} + onDragStart={(event) => { + setDidSingleClick(false); + onDragStart(event, node); + }} + > + +
+ + + + + ), + [ + collapsed, + isDisabled, + isOpen, + node, + onClose, + onContextMenu, + onCreateNode, + unavailableReason, + ] ); } ); diff --git a/src/renderer/components/node/BreakPoint.tsx b/src/renderer/components/node/BreakPoint.tsx index 9b2cd65c5..63dca703e 100644 --- a/src/renderer/components/node/BreakPoint.tsx +++ b/src/renderer/components/node/BreakPoint.tsx @@ -1,10 +1,9 @@ import { NeverType } from '@chainner/navi'; import { DeleteIcon } from '@chakra-ui/icons'; import { Box, Center, MenuItem, MenuList } from '@chakra-ui/react'; -import { memo, useEffect, useMemo, useState } from 'react'; +import { memo, useEffect, useMemo } from 'react'; import { Handle, Position, useReactFlow } from 'reactflow'; import { useContext, useContextSelector } from 'use-context-selector'; -import { useDebouncedCallback } from 'use-debounce'; import { EdgeData, NodeData, OutputId } from '../../../common/common-types'; import { BackendContext } from '../../contexts/BackendContext'; import { GlobalContext, GlobalVolatileContext } from '../../contexts/GlobalNodeState'; @@ -102,35 +101,27 @@ const BreakPointInner = memo(({ id }: NodeProps) => { [type, definitionType] ); - const [isHovered, setIsHovered] = useState(false); - // Prevent hovered state from getting stuck - const hoverTimeout = useDebouncedCallback(() => { - setIsHovered(false); - }, 7500); - return ( removeEdgeBreakpoint(id)} >
removeEdgeBreakpoint(id)} - onMouseEnter={() => setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - onMouseOver={() => hoverTimeout()} + width="12px" > { transform="translate(-50%, -50%)" transition="all 0.2s ease-in-out" width="26px" - onContextMenu={menu.onContextMenu} - onDoubleClick={() => removeEdgeBreakpoint(id)} - onMouseEnter={() => setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - onMouseOver={() => hoverTimeout()} /> );