diff --git a/libs/shared/lib/components/buttons/Button.tsx b/libs/shared/lib/components/buttons/Button.tsx index 91aa5b33e1a76e030b4bce41b7a5a5ff53bc48ae..c5d5e5ef232a761d7f7f078f1f0b4092f14e7fdd 100644 --- a/libs/shared/lib/components/buttons/Button.tsx +++ b/libs/shared/lib/components/buttons/Button.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useMemo } from 'react'; +import React, { ReactElement, ReactPropTypes, useMemo } from 'react'; import styles from './buttons.module.scss'; import Icon, { Sizes } from '../icon'; import { forwardRef } from 'react'; @@ -19,6 +19,19 @@ type ButtonProps = { ariaLabel?: string; children?: React.ReactNode; className?: string; + style?: React.CSSProperties; + onMouseUp?: (e: any) => void; + onMouseDown?: (e: any) => void; + onMouseEnter?: (e: any) => void; + onMouseLeave?: (e: any) => void; + onMouseOver?: (e: any) => void; + onMouseOut?: (e: any) => void; + onMouseUpCapture?: (e: any) => void; + onMouseDownCapture?: (e: any) => void; + onMouseEnterCapture?: (e: any) => void; + onMouseLeaveCapture?: (e: any) => void; + onMouseOverCapture?: (e: any) => void; + onMouseOutCapture?: (e: any) => void; }; export const Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement | HTMLDivElement, ButtonProps>( diff --git a/libs/shared/lib/components/pills/Pill.tsx b/libs/shared/lib/components/pills/Pill.tsx index 2cf653552b8d5b29ff83fc61cd828fe3b268c1af..b107d1d88062ddbacf58b7ca2ac36cdc839a38c2 100644 --- a/libs/shared/lib/components/pills/Pill.tsx +++ b/libs/shared/lib/components/pills/Pill.tsx @@ -1,7 +1,8 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { pillWidth, pillHeight, pillXPadding, pillInnerMargin, topLineHeight, pillBorderWidth, pillDropdownPadding } from './pill.const'; import { Position } from 'reactflow'; import { PillHandle } from './PillHandle'; +import { PillContext } from './PillContext'; export type PillI = { onHovered?: (hovered: boolean) => void; @@ -11,7 +12,6 @@ export type PillI = { handles?: React.ReactNode; style?: React.CSSProperties; corner?: 'rounded' | 'square' | 'diamond'; - topColor: string; handleUp?: React.ReactNode; handleDown?: React.ReactNode; handleLeft?: React.ReactNode; @@ -21,6 +21,7 @@ export type PillI = { }; export const Pill = React.memo((props: PillI) => { + const pillContext = useContext(PillContext); const [hovered, setHovered] = useState(false); const onMouseEnter = (event: React.MouseEvent) => { @@ -87,25 +88,17 @@ export const Pill = React.memo((props: PillI) => { ...outerContentStyle, }} > - <div - className="absolute -z-10" - style={{ - top: pillHeight - pillInnerMargin, - width: pillWidth, - paddingLeft: pillDropdownPadding, - paddingRight: pillDropdownPadding, - }} - > - {props.children} - </div> <div className={'bg-secondary-100 ' + (corner !== 'square' ? 'rounded-[3px]' : '')} style={{ ...innerContentStyle, }} > - {props.topColor && ( - <div className={(corner !== 'square' ? 'rounded-t ' : '') + props.topColor} style={{ height: topLineHeight }}></div> + {pillContext.color && ( + <div + className={corner !== 'square' ? 'rounded-t ' : ''} + style={{ height: topLineHeight, backgroundColor: pillContext.color }} + ></div> )} <div className={'font-semibold bg-neutral-100 ' + (corner !== 'square' ? 'rounded-b-[3px]' : '')} @@ -125,6 +118,18 @@ export const Pill = React.memo((props: PillI) => { </div> </div> </div> + + <div + className="absolute -z-10" + style={{ + top: pillHeight - pillInnerMargin + (corner === 'diamond' ? pillHeight / 2 - pillBorderWidth : 0), + width: pillWidth - (corner === 'diamond' ? pillWidth * 0.05 : 0), + paddingLeft: pillDropdownPadding + (corner === 'diamond' ? pillWidth * 0.05 : 0), + paddingRight: pillDropdownPadding, + }} + > + {props.children} + </div> <div className="absolute z-50 pointer-events-auto">{props.handles}</div> </div> ); @@ -133,56 +138,68 @@ export const Pill = React.memo((props: PillI) => { export const EntityPill = React.memo((props: Omit<PillI, 'topColor'> & { withHandles?: 'vertical' | 'horizontal' }) => { const handles = !props.withHandles ? undefined : props.withHandles === 'horizontal' ? ( <> - <PillHandle position={Position.Left} className={'fill-accent-500 stroke-white'} type="square"> + <PillHandle position={Position.Left} className={'stroke-white'} type="square"> {props.handleLeft} </PillHandle> - <PillHandle position={Position.Right} className={'fill-accent-500 stroke-white'} type="square"> + <PillHandle position={Position.Right} className={'stroke-white'} type="square"> {props.handleRight} </PillHandle> </> ) : ( <> - <PillHandle position={Position.Top} className={'fill-accent-500 stroke-white stroke-1'} type="arrowUp"> + <PillHandle position={Position.Top} className={'stroke-white stroke-1'} type="arrowUp"> {props.handleUp} </PillHandle> - <PillHandle position={Position.Bottom} className={'fill-accent-500 stroke-white stroke-1'} type="arrowDown"> + <PillHandle position={Position.Bottom} className={'stroke-white stroke-1'} type="arrowDown"> {props.handleDown} </PillHandle> </> ); - return <Pill {...props} corner="rounded" topColor="bg-accent-500" handles={handles} />; + return ( + <PillContext.Provider value={{ color: 'hsl(29 96 60)' }}> + <Pill {...props} corner="rounded" handles={handles} /> + </PillContext.Provider> + ); }); export const RelationPill = React.memo((props: Omit<PillI, 'topColor'> & { withHandles?: 'vertical' | 'horizontal' }) => { const handles = !props.withHandles ? undefined : props.withHandles === 'horizontal' ? ( <> - <PillHandle position={Position.Left} className={'fill-[#0676C1] stroke-white'} type="square"> + <PillHandle position={Position.Left} className={'stroke-white'} type="square"> {props.handleLeft} </PillHandle> - <PillHandle position={Position.Right} className={'fill-[#0676C1] stroke-white'} type="square" mr={-pillBorderWidth}> + <PillHandle position={Position.Right} className={'stroke-white'} type="square" mr={-pillBorderWidth}> {props.handleRight} </PillHandle> </> ) : ( <> - <PillHandle position={Position.Top} className={'fill-[#0676C1] stroke-white stroke-1'} type="arrowUp"> + <PillHandle position={Position.Top} className={'stroke-white stroke-1'} type="arrowUp"> {props.handleUp} </PillHandle> - <PillHandle position={Position.Bottom} className={'fill-[#0676C1] stroke-white stroke-1'} type="arrowDown"> + <PillHandle position={Position.Bottom} className={'stroke-white stroke-1'} type="arrowDown"> {props.handleDown} </PillHandle> </> ); - return <Pill {...props} corner="diamond" topColor="bg-[#0676C1]" handles={handles} />; + return ( + <PillContext.Provider value={{ color: '#0676C1' }}> + <Pill {...props} corner="diamond" handles={handles} /> + </PillContext.Provider> + ); }); export const LogicPill = React.memo((props: Omit<PillI, 'topColor'>) => { const handles = ( - <PillHandle position={Position.Left} className={'fill-[#543719] stroke-white'} type="square"> + <PillHandle position={Position.Left} className={'stroke-white'} type="square"> {props.handleLeft} </PillHandle> ); - return <Pill {...props} corner="square" topColor="bg-[#543719]" handles={handles} />; + return ( + <PillContext.Provider value={{ color: '#543719' }}> + <Pill {...props} corner="square" handles={handles} /> + </PillContext.Provider> + ); }); diff --git a/libs/shared/lib/components/pills/PillContext.tsx b/libs/shared/lib/components/pills/PillContext.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3ec61a12a9b6d1f0b1ae1cfc5d107db90e2c2e19 --- /dev/null +++ b/libs/shared/lib/components/pills/PillContext.tsx @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +export const PillContext = createContext({ + color: 'hsl(29 96 60)', +}); diff --git a/libs/shared/lib/components/pills/PillHandle.tsx b/libs/shared/lib/components/pills/PillHandle.tsx index e2e04d0f906fd066312310a9ae8676788866c3ea..88aa24aaab58e18c6cdea18043d902e1d0e05937 100644 --- a/libs/shared/lib/components/pills/PillHandle.tsx +++ b/libs/shared/lib/components/pills/PillHandle.tsx @@ -1,6 +1,7 @@ -import React, { useMemo } from 'react'; +import React, { useContext, useMemo } from 'react'; import { Position } from 'reactflow'; import { pillHeight, pillWidth, topLineHeight, pillBorderWidth } from './pill.const'; +import { PillContext } from './PillContext'; export const PillHandle = (props: { handleTop?: 'auto' | 'fixed'; @@ -14,6 +15,7 @@ export const PillHandle = (props: { outerSize?: number; innerSize?: number; }) => { + const pillContext = useContext(PillContext); const outerSize = props.outerSize || (props.type === 'square' ? 6 : 4); const innerSize = props.innerSize || (props.type === 'square' ? 4 : 6); @@ -38,6 +40,7 @@ export const PillHandle = (props: { const innerStyle: React.CSSProperties = { width: innerSize * 2, height: innerSize * 2 }; innerStyle.left = outerSize - innerSize; innerStyle.top = outerSize - innerSize; + innerStyle.fill = pillContext.color; const innerHandle = useMemo(() => { if (props.type === 'square') diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx index 32d00d60cbc838416615a77f5c66fe9275c65e44..ae777b716086bf093effd239355eb6740eab453c 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx @@ -3,7 +3,8 @@ import React, { useMemo, useRef, useState } from 'react'; import { Handle, Position, useUpdateNodeInternals } from 'reactflow'; import { NodeAttribute, SchemaReactflowEntityNode, toHandleId } from '../../../model'; import { PillDropdown } from '../../pilldropdown/PillDropdown'; -import { EntityPill } from '@graphpolaris/shared/lib/components'; +import { Button, DropdownContainer, DropdownItemContainer, DropdownTrigger, EntityPill } from '@graphpolaris/shared/lib/components'; +import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material'; /** * Component to render an entity flow element @@ -12,7 +13,6 @@ import { EntityPill } from '@graphpolaris/shared/lib/components'; export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { const updateNodeInternals = useUpdateNodeInternals(); const ref = useRef<HTMLDivElement | null>(null); - const dropdownActive = useRef(false); const data = node.data; if (!data.leftRelationHandleId) throw new Error('EntityFlowElement: data.leftRelationHandleId is undefined'); @@ -24,63 +24,28 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { [graph], ); - const [hovered, setHovered] = useState(false); - const [dragging, setDragging] = useState(false); - const [handleBeingDragged, setHandleBeingDragged] = useState(-1); - - const onMouseEnter = (event: React.MouseEvent) => { - if (!hovered) setHovered(true); - ref.current?.addEventListener('mousedown', onMouseDown, true); - setTimeout(() => { - updateNodeInternals(node.id); - }, 100); - }; - - const onMouseLeave = (event: React.MouseEvent) => { - ref.current?.removeEventListener('mousedown', onMouseDown, true); - if (hovered) setHovered(false); - setTimeout(() => { - updateNodeInternals(node.id); - }, 100); - }; - - const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => { - setHandleBeingDragged(i); - window.addEventListener('mouseup', onHandleMouseUp, true); - }; - - const onHandleMouseUp = () => { - setHandleBeingDragged(-1); - window.removeEventListener('mouseup', onHandleMouseUp, true); - }; - - const onMouseDown = React.useCallback(() => { - window.addEventListener('mouseup', onMouseUp, true); - setDragging(true); - }, []); - - const onMouseUp = () => { - setDragging(false); - setHovered(true); - window.removeEventListener('mouseup', onMouseUp, true); - }; - - const onConnect = (params: any) => { - console.log('EntityPill onConnect', params); - }; - - const onMouseEnterDropdown = (event: React.MouseEvent) => { - ref.current?.removeEventListener('mousedown', onMouseDown, true); - }; - - const onMouseLeaveDropdown = (event: React.MouseEvent) => { - ref.current?.addEventListener('mousedown', onMouseDown, true); - }; + const [openDropdown, setOpenDropdown] = useState(false); return ( - <div className="w-fit h-fit nowheel" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} ref={ref} id="asd"> + <div className="w-fit h-fit nowheel" ref={ref} id="asd"> <EntityPill - title={data.name || ''} + title={ + <div className="flex flex-row justify-between items-center"> + <span>{data.name || ''}</span> + <Button + variantType="secondary" + variant="ghost" + size="2xs" + iconComponent={openDropdown ? <ArrowDropUp /> : <ArrowDropDown />} + onMouseDownCapture={(e) => { + e.stopPropagation(); + e.preventDefault(); + setOpenDropdown(!openDropdown); + }} + className={openDropdown ? 'border-secondary-200' : ''} + /> + </div> + } withHandles="horizontal" handleLeft={ <Handle @@ -102,14 +67,10 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { > {data?.attributes && ( <PillDropdown - onMouseEnterDropdown={onMouseEnterDropdown} - onMouseLeaveDropdown={onMouseLeaveDropdown} node={node} attributes={data.attributes} attributeEdges={attributeEdges.map((edge) => edge?.attributes)} - hovered={hovered && !dragging} - handleBeingDraggedIdx={handleBeingDragged} - onHandleMouseDown={onHandleMouseDown} + open={openDropdown} /> )} </EntityPill> diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx index 7df04570621dfa47e2bfd4932b90b810d946322c..3bb9e75d3b2a75f6547594fc47946df45407dfb7 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx @@ -4,8 +4,10 @@ import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderSettings } from '@ import { addWarning } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { PillDropdown } from '../../pilldropdown/PillDropdown'; -import { RelationPill } from '@graphpolaris/shared/lib/components'; +import { Button, DropdownContainer, DropdownTrigger, RelationPill } from '@graphpolaris/shared/lib/components'; import { Handle, Position } from 'reactflow'; +import { ArrowDropUp, ArrowDropDown } from '@mui/icons-material'; +import { pillWidth } from '@graphpolaris/shared/lib/components/pills/pill.const'; export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { const data = node.data; @@ -21,8 +23,7 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { () => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType), [graph], ); - const [hovered, setHovered] = useState(false); - const [handleBeingDragged, setHandleBeingDragged] = useState(-1); + const [openDropdown, setOpenDropdown] = useState(false); const [depth, setDepth] = useState<{ min: number; max: number }>({ min: data.depth.min || settings.depth.min, @@ -65,79 +66,73 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { return data.toString().length + 0.5 + 'ch'; }; - const onMouseEnter = (event: React.MouseEvent) => { - if (!hovered) setHovered(true); - }; - - const onMouseLeave = (event: React.MouseEvent) => { - if (hovered) setHovered(false); - }; - - const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => { - setHandleBeingDragged(i); - window.addEventListener('mouseup', onHandleMouseUp, true); - }; - - const onHandleMouseUp = () => { - setHandleBeingDragged(-1); - window.removeEventListener('mouseup', onHandleMouseUp, true); - }; - return ( - <div className="p-3 bg-transparent nowheel" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}> + <div className="w-fit h-fit p-3 bg-transparent nowheel"> <RelationPill title={ <div className="flex flex-row w-full"> <span className="flex-grow text-justify truncate">{data?.name}</span> - <span className="pr-1"> - <span> [</span> - <input - className={ - 'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' + - (depth.min < 0 || depth.min > depth.max ? ' bg-danger-400 ' : '') - } - style={{ maxWidth: calcWidth(depth.min) }} - type="number" - min={0} - placeholder={'?'} - value={depth.min} - onChange={(e) => { - setDepth({ ...depth, min: parseInt(e.target.value) }); - }} - onBlur={(e) => { - onNodeUpdated(); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - onNodeUpdated(); - } - }} - ></input> - <span>..</span> - <input - className={ - 'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' + - (depth.max > 99 || depth.min > depth.max ? ' bg-danger-400 ' : '') - } - style={{ maxWidth: calcWidth(depth.max) }} - type="number" - min={1} - placeholder={'?'} - value={depth.max} - onChange={(e) => { - setDepth({ ...depth, max: parseInt(e.target.value) }); - }} - onBlur={(e) => { - onNodeUpdated(); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - onNodeUpdated(); - } - }} - ></input> - <span>]</span> - </span> + <Button + variantType="secondary" + variant="ghost" + size="2xs" + iconComponent={openDropdown ? <ArrowDropUp /> : <ArrowDropDown />} + onMouseDownCapture={(e) => { + e.stopPropagation(); + e.preventDefault(); + setOpenDropdown(!openDropdown); + }} + className={openDropdown ? 'border-secondary-200' : ''} + /> + {/* <span className="pr-1"> + <span> [</span> + <input + className={ + 'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' + + (depth.min < 0 || depth.min > depth.max ? ' bg-danger-400 ' : '') + } + style={{ maxWidth: calcWidth(depth.min) }} + type="number" + min={0} + placeholder={'?'} + value={depth.min} + onChange={(e) => { + setDepth({ ...depth, min: parseInt(e.target.value) }); + }} + onBlur={(e) => { + onNodeUpdated(); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onNodeUpdated(); + } + }} + ></input> + <span>..</span> + <input + className={ + 'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' + + (depth.max > 99 || depth.min > depth.max ? ' bg-danger-400 ' : '') + } + style={{ maxWidth: calcWidth(depth.max) }} + type="number" + min={1} + placeholder={'?'} + value={depth.max} + onChange={(e) => { + setDepth({ ...depth, max: parseInt(e.target.value) }); + }} + onBlur={(e) => { + onNodeUpdated(); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onNodeUpdated(); + } + }} + ></input> + <span>]</span> + </span> */} </div> } withHandles="horizontal" @@ -165,9 +160,8 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { node={node} attributes={data.attributes} attributeEdges={attributeEdges.map((edge) => edge?.attributes)} - hovered={hovered} - handleBeingDraggedIdx={handleBeingDragged} - onHandleMouseDown={onHandleMouseDown} + open={openDropdown} + mr={-pillWidth * 0.05} /> )} </RelationPill> diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx index e357b0237360a43a724b1946e85d30da73058207..3b796d784ee2d4288556a0dbe350ddfb6885e915 100644 --- a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx +++ b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx @@ -13,11 +13,9 @@ type PillDropdownProps = { node: SchemaReactflowEntityNode; attributes: NodeAttribute[]; attributeEdges: (QueryGraphEdges | undefined)[]; - hovered: boolean; - handleBeingDraggedIdx: number; - onHandleMouseDown: (attribute: NodeAttribute, i: number, event: React.MouseEvent) => void; - onMouseEnterDropdown?: (event: React.MouseEvent) => void; - onMouseLeaveDropdown?: (event: React.MouseEvent) => void; + open: boolean; + mr?: number; + className?: string; }; type IconMapType = { @@ -45,15 +43,7 @@ export const PillDropdown = (props: PillDropdownProps) => { }, [attributesBeingShown]); return ( - <div - className={'border-[1px] border-secondary-200 divide-y divide-secondary-200 !z-50'} - onMouseEnter={(e) => { - if (props.onMouseEnterDropdown) props.onMouseEnterDropdown(e); - }} - onMouseLeave={(e) => { - if (props.onMouseLeaveDropdown) props.onMouseLeaveDropdown(e); - }} - > + <div className={'border-[1px] border-secondary-200 divide-y divide-secondary-200 !z-50'}> {attributesOfInterest && attributesOfInterest.map((showing, i) => { if (showing === false) return null; @@ -67,17 +57,14 @@ export const PillDropdown = (props: PillDropdownProps) => { <div className="px-2 py-1 bg-secondary-100 flex justify-between items-center" key={(attribute.handleData.attributeName || '') + i} - onMouseDown={(event: React.MouseEvent) => { - props.onHandleMouseDown(attribute, i, event); - }} > <p className="truncate text-[0.6rem]">{attribute.handleData.attributeName}</p> {attribute.handleData?.attributeDimension && <Icon component={IconMap[attribute.handleData.attributeDimension]} size={16} />} <PillHandle - mr={-pillDropdownPadding} + mr={-pillDropdownPadding + (props.mr || 0)} handleTop="auto" position={Position.Right} - className={'fill-accent-500 stroke-white'} + className={`stroke-white${props.className ? ` ${props.className}` : ''}`} type="square" > <Handle @@ -90,7 +77,7 @@ export const PillDropdown = (props: PillDropdownProps) => { </div> ); })} - {(props.hovered || forceOpen) && ( + {(props.open || forceOpen) && ( <> <h4 className="p-1 bg-white border-t-[2px] font-semibold text-2xs">Available Attributes:</h4> <TextInput type={'text'} placeholder="Filter" size="xs" className="!p-0.5" value={filter} onChange={(v) => setFilter(v)} /> @@ -110,7 +97,9 @@ export const PillDropdown = (props: PillDropdownProps) => { variant="ghost" size={'xs'} label={attribute.handleData.attributeName} - onClick={(event: React.MouseEvent) => { + onMouseDownCapture={(e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); dispatch(attributeShownToggle(attribute.handleData)); }} ></Button> diff --git a/libs/shared/lib/vis/components/VisualizationPanel.tsx b/libs/shared/lib/vis/components/VisualizationPanel.tsx index 2e2dd74b77adc9bde302f29f51b2a9df7e42a81c..c7446a261d91974ff5c412ad92eb9a04314716cc 100644 --- a/libs/shared/lib/vis/components/VisualizationPanel.tsx +++ b/libs/shared/lib/vis/components/VisualizationPanel.tsx @@ -62,6 +62,12 @@ export const VisualizationPanel = ({ fullSize }: { fullSize: () => void }) => { } const activeVisualization = openVisualizationArray[activeVisualizationIndex]; + + if (!activeVisualization) { + setViz(undefined); + return; + } + const componentModule = await Visualizations[activeVisualization.id](); const component = componentModule.default;