From 148b940161f23fec9f49d596e145fce206ed2115 Mon Sep 17 00:00:00 2001 From: MarcosPierasNL <pieras.marcos@gmail.com> Date: Wed, 3 Jul 2024 10:12:25 +0200 Subject: [PATCH] feat: integrate tooltipCard with schema --- .../lib/components/CardToolTipVis/index.tsx | 15 +- .../shared/lib/components/tooltip/Tooltip.tsx | 141 +++++++++--------- libs/shared/lib/schema/model/reactflow.tsx | 4 + .../pills/nodes/entity/SchemaEntityPill.tsx | 37 ++++- .../pills/nodes/entity/SchemaEntityPopup.tsx | 2 + .../nodes/relation/SchemaRelationPill.tsx | 43 +++++- 6 files changed, 161 insertions(+), 81 deletions(-) diff --git a/libs/shared/lib/components/CardToolTipVis/index.tsx b/libs/shared/lib/components/CardToolTipVis/index.tsx index e6cb7aa54..7a274cdeb 100644 --- a/libs/shared/lib/components/CardToolTipVis/index.tsx +++ b/libs/shared/lib/components/CardToolTipVis/index.tsx @@ -32,8 +32,10 @@ export const CardToolTipVis: React.FC<CardToolTipVisProps> = ({ numberOfElements, }) => { const itemsToShow = Object.entries(data).slice(0, maxVisibleItems); + + // shadow-md return ( - <div className="border-1 border-sec-200 bg-white w-[17rem] shadow-md"> + <div className="border-1 border-sec-200 bg-white w-[12rem]"> <div className="flex m-0 items-center border-b border-sec-200"> <div className="h-9 w-2" style={{ backgroundColor: colorHeader }}></div> <span className="text-xl ml-2 font-semibold">{name}</span> @@ -63,11 +65,15 @@ export const CardToolTipVis: React.FC<CardToolTipVisProps> = ({ <TooltipProvider delayDuration={300}> <div className={`px-4 py-2 ${data.length > maxVisibleItems ? 'max-h-20 overflow-y-auto' : ''}`}> - {data && + {data && Object.keys(data).length === 0 ? ( + <div className="flex justify-center items-center h-full"> + <span>No attributes</span> + </div> + ) : ( Object.entries(data).map(([k, v]) => ( <Tooltip key={k}> <div className="flex flex-row gap-3 items-center"> - <span className="font-semibold truncate w-[40%]">{k}</span> + <span className={`font-semibold truncate ${type === 'schema' ? 'w-[90%] ' : 'w-[40%] '} `}>{k}</span> <TooltipTrigger asChild> <span className="ml-auto text-right truncate w-[60%] min-h-[24px]"> {type === 'schema' ? ( @@ -91,7 +97,8 @@ export const CardToolTipVis: React.FC<CardToolTipVisProps> = ({ </TooltipContent> </div> </Tooltip> - ))} + )) + )} </div> </TooltipProvider> </div> diff --git a/libs/shared/lib/components/tooltip/Tooltip.tsx b/libs/shared/lib/components/tooltip/Tooltip.tsx index 3541d2516..33c54848e 100644 --- a/libs/shared/lib/components/tooltip/Tooltip.tsx +++ b/libs/shared/lib/components/tooltip/Tooltip.tsx @@ -14,7 +14,7 @@ import { useInteractions, useMergeRefs, FloatingPortal, - FloatingArrow + FloatingArrow, } from '@floating-ui/react'; import type { Placement } from '@floating-ui/react'; import { FloatingDelayGroup } from '@floating-ui/react'; @@ -34,7 +34,7 @@ export function useTooltip({ open: controlledOpen, onOpenChange: setControlledOpen, boundaryElement = null, - showArrow = false + showArrow = false, }: TooltipOptions = {}): { open: boolean; setOpen: (open: boolean) => void; @@ -57,16 +57,16 @@ export function useTooltip({ flip({ crossAxis: placement.includes('-'), fallbackAxisSideDirection: 'start', - padding: 5 + padding: 5, }), shift({ padding: 5 }), ], - } + }; if (boundaryElement != null) { const boundary = boundaryElement?.current ?? undefined; - config.middleware.find(x => x.name == 'flip')!.options[0].boundary = boundary; - config.middleware.find(x => x.name == 'shift')!.options[0].boundary = boundary; + config.middleware.find((x) => x.name == 'flip')!.options[0].boundary = boundary; + config.middleware.find((x) => x.name == 'shift')!.options[0].boundary = boundary; config.middleware.push(hide({ boundary })); } @@ -120,72 +120,69 @@ export function Tooltip({ children, ...options }: { children: React.ReactNode } // This can accept any props as options, e.g. `placement`, // or other positioning options. const tooltip = useTooltip(options); - - return <TooltipContext.Provider value={tooltip}> - {children} - </TooltipContext.Provider>; + + return <TooltipContext.Provider value={tooltip}>{children}</TooltipContext.Provider>; } -export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean, x?: number, y?: number }>(function TooltipTrigger( - { children, asChild = false, x = null, y = null, ...props }, - propRef, -) { - const context = useTooltipContext(); - const childrenRef = React.useMemo(() => { - if (children == null) { - return null; - } else { - return (children as any).ref; - } - }, [children]); - - const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]); - - React.useEffect(() => { - if (x && y && context.data.refs.reference.current != null) { - const element = context.data.refs.reference.current as HTMLElement; - element.style.position = 'absolute'; - const {x: offsetX, y: offsetY} = element.getBoundingClientRect(); - element.getBoundingClientRect = () => { - return { - width: 0, - height: 0, - x: offsetX, - y: offsetY, - top: y + offsetY, - left: x + offsetX, - right: x + offsetX, - bottom: y + offsetY, - } as DOMRect +export const TooltipTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & { asChild?: boolean; x?: number; y?: number }>( + function TooltipTrigger({ children, asChild = false, x = null, y = null, ...props }, propRef) { + const context = useTooltipContext(); + const childrenRef = React.useMemo(() => { + if (children == null) { + return null; + } else { + return (children as any).ref; } - context.data.update(); + }, [children]); + + const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]); + + React.useEffect(() => { + if (x && y && context.data.refs.reference.current != null) { + const element = context.data.refs.reference.current as HTMLElement; + element.style.position = 'absolute'; + const { x: offsetX, y: offsetY } = element.getBoundingClientRect(); + element.getBoundingClientRect = () => { + return { + width: 0, + height: 0, + x: offsetX, + y: offsetY, + top: y + offsetY, + left: x + offsetX, + right: x + offsetX, + bottom: y + offsetY, + } as DOMRect; + }; + context.data.update(); + } + }, [x, y]); + + // `asChild` allows the user to pass any element as the anchor + if (asChild && React.isValidElement(children)) { + return React.cloneElement( + children, + context.interactions.getReferenceProps({ + ref, + ...props, + ...children.props, + 'data-state': context.open ? 'open' : 'closed', + }), + ); } - }, [x, y]); - - // `asChild` allows the user to pass any element as the anchor - if (asChild && React.isValidElement(children)) { - return React.cloneElement( - children, - context.interactions.getReferenceProps({ - ref, - ...props, - ...children.props, - 'data-state': context.open ? 'open' : 'closed', - }), - ); - } - return ( - <div - ref={ref} - // The user can style the trigger based on the state - data-state={context.open ? 'open' : 'closed'} - {...context.interactions.getReferenceProps(props)} - > - {children} - </div> - ); -}); + return ( + <div + ref={ref} + // The user can style the trigger based on the state + data-state={context.open ? 'open' : 'closed'} + {...context.interactions.getReferenceProps(props)} + > + {children} + </div> + ); + }, +); export const TooltipContent = React.forwardRef< HTMLDivElement, @@ -210,12 +207,10 @@ export const TooltipContent = React.forwardRef< }} {...context.interactions.getFloatingProps(props)} > - { props.children } - { context.data.middlewareData.arrow ? <FloatingArrow - ref={(context.data.refs as any).arrow} - context={context.data.context} - style={{fill: 'white'}} - /> : null } + {props.children} + {context.data.middlewareData.arrow ? ( + <FloatingArrow ref={(context.data.refs as any).arrow} context={context.data.context} style={{ fill: 'white' }} /> + ) : null} </div> </FloatingPortal> ); diff --git a/libs/shared/lib/schema/model/reactflow.tsx b/libs/shared/lib/schema/model/reactflow.tsx index 39a121106..3af36d3b0 100644 --- a/libs/shared/lib/schema/model/reactflow.tsx +++ b/libs/shared/lib/schema/model/reactflow.tsx @@ -34,6 +34,8 @@ export type SchemaReactflowEntity = SchemaReactflowData & { // handles: string[]; connectedRatio: number; name: string; + x: number; + y: number; }; export type SchemaReactflowRelation = SchemaReactflowData & { @@ -42,6 +44,8 @@ export type SchemaReactflowRelation = SchemaReactflowData & { collection: string; fromRatio: number; toRatio: number; + x: number; + y: number; }; export type SchemaReactflowNodeWithFunctions = SchemaReactflowEntity & { diff --git a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx index d3bfe7ef2..3eb5e3abc 100644 --- a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx +++ b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import { Handle, Position, NodeProps } from 'reactflow'; import { SchemaReactflowNodeWithFunctions } from '../../../model/reactflow'; import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder'; @@ -6,10 +6,13 @@ import { SchemaEntityPopup } from './SchemaEntityPopup'; import { Popup } from '@graphpolaris/shared/lib/components/Popup'; import { SchemaNode } from '../../../model'; import { EntityPill } from '@graphpolaris/shared/lib/components'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip'; +import { CardToolTipVis, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/CardToolTipVis'; +import { StringDecoder } from 'string_decoder'; export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<SchemaReactflowNodeWithFunctions>) => { const [openPopup, setOpenPopup] = useState(false); - + const ref = useRef<HTMLDivElement>(null); /** * adds drag functionality in order to be able to drag the entityNode to the schema * @param event React Mouse drag event @@ -22,6 +25,7 @@ export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<Sc }; event.dataTransfer.setData('application/reactflow', JSON.stringify(eventData)); event.dataTransfer.effectAllowed = 'move'; + console.log('drag ', event); }; /** @@ -37,15 +41,44 @@ export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<Sc const onClickToggleAttributeAnalyticsPopupMenu = (): void => { data.toggleAttributeAnalyticsPopupMenu(id); }; + //console.log(selected); + console.log(data); return ( <> + {/* {openPopup && ( <Popup open={openPopup} hAnchor="left" className="-top-8" offset="-20rem"> <SchemaEntityPopup data={data} onClose={() => setOpenPopup(false)} /> </Popup> )} + */} + {openPopup && ( + <Tooltip key={data.name} open={true} boundaryElement={ref} showArrow={true}> + <TooltipTrigger x={data.x} y={data.y} /> + <TooltipContent side="top"> + <div> + <CardToolTipVis + type="schema" + typeOfSchema="node" + name={data.name} + colorHeader="#fb7b04" + numberOfElements={1000} + data={data.attributes.reduce( + (acc, attr) => { + if (attr.name && attr.type) { + acc[attr.name] = attr.type; + } + return acc; + }, + {} as Record<string, string>, + )} + /> + </div> + </TooltipContent> + </Tooltip> + )} <div className="w-fit h-fit" onDragStart={(event) => onDragStart(event)} diff --git a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx index c0f5c9247..9514679eb 100644 --- a/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx +++ b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx @@ -31,6 +31,7 @@ export const SchemaEntityPopup = (props: SchemaEntityPopupProps) => { return ( // <FormDiv hAnchor="left"> <> + {/* <FormCard> <FormBody onSubmit={(e: FormEvent<HTMLFormElement>) => { @@ -79,6 +80,7 @@ export const SchemaEntityPopup = (props: SchemaEntityPopupProps) => { </FormControl> </FormBody> </FormCard> + */} </> ); }; diff --git a/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx index 86cdfcdb8..68eb3d1fc 100644 --- a/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx +++ b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import { Handle, Position, NodeProps } from 'reactflow'; import { SchemaReactflowRelationWithFunctions } from '../../../model/reactflow'; import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder'; @@ -7,8 +7,12 @@ import { SchemaRelationshipPopup } from './SchemaRelationshipPopup'; import { SchemaEdge } from '../../../model'; import { RelationPill } from '@graphpolaris/shared/lib/components'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip'; +import { CardToolTipVis, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/CardToolTipVis'; + export const SchemaRelationPill = React.memo(({ id, selected, data, ...props }: NodeProps<SchemaReactflowRelationWithFunctions>) => { const [openPopup, setOpenPopup] = useState(false); + const ref = useRef<HTMLDivElement>(null); /** * Adds drag functionality in order to be able to drag the relationNode to the schema. @@ -43,14 +47,49 @@ export const SchemaRelationPill = React.memo(({ id, selected, data, ...props }: const onClickToggleAttributeAnalyticsPopupMenu = (): void => { data.toggleAttributeAnalyticsPopupMenu(data.collection); }; - + console.log('relation ', data); return ( <> + {/* {openPopup && ( <Popup open={openPopup} hAnchor="left" className="-top-8" offset="-20rem"> <SchemaRelationshipPopup data={data} onClose={() => setOpenPopup(false)} /> </Popup> )} + */} + + {openPopup && ( + <Tooltip key={data.name} open={true} boundaryElement={ref} showArrow={true}> + <TooltipTrigger x={data.x} y={data.y} /> + <TooltipContent side="top"> + <div> + <CardToolTipVis + type="schema" + typeOfSchema="relationship" + name={data.collection} + colorHeader="#0676C1" + numberOfElements={1000} + connectedFrom={data.from} + connectedTo={data.to} + data={ + data.attributes.length > 0 + ? data.attributes.reduce( + (acc, attr) => { + if (attr.name && attr.type) { + acc[attr.name] = attr.type; + } + return acc; + }, + {} as Record<string, string>, + ) + : {} + } + /> + </div> + </TooltipContent> + </Tooltip> + )} + <div className="w-fit h-fit" onDragStart={(event) => onDragStart(event)} -- GitLab