diff --git a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx index 4b0cbe9a3f382e439b0b10d910ab34a47cd65daa..de4c9ef31cfbb1869bcf45145296604fd57456ec 100644 --- a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx +++ b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx @@ -77,7 +77,7 @@ export interface RelationStruct { ID?: string; label?: string; depth: QuerySearchDepthStruct; - direction: 'TO' | 'FROM'; + direction: 'TO' | 'FROM' | 'BOTH'; node?: NodeStruct; } diff --git a/libs/shared/lib/querybuilder/model/graphology/model.ts b/libs/shared/lib/querybuilder/model/graphology/model.ts index 09832ac558c7ad3e344ab7b43fae76eecac7dc8d..02b336deb8cf39c292610ff35934e5714047f4de 100644 --- a/libs/shared/lib/querybuilder/model/graphology/model.ts +++ b/libs/shared/lib/querybuilder/model/graphology/model.ts @@ -39,6 +39,7 @@ export interface RelationData { depth: { min: number; max: number }; leftEntityHandleId?: QueryGraphEdgeHandle; rightEntityHandleId?: QueryGraphEdgeHandle; + direction?: 'left' | 'right' | 'both'; } export interface LogicData { diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx index 694540acb0b20125123d2409e41bf7531b4eeb49..1ee444dbab71e42657ab2bd4b37442fbea59bfbe 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx @@ -423,6 +423,16 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { connection={connectingNodeId?.current} /> </Dialog> + <svg height={0}> + <defs> + <marker id="arrowIn" markerWidth="9" markerHeight="10" refX={0} refY="5" orient="auto"> + <polygon points="0 0, 9 5, 0 10" /> + </marker> + <marker id="arrowOut" markerWidth="10" markerHeight="10" refX={0} refY="5" orient="auto"> + <polygon points="0 0, 10 5, 0 10" /> + </marker> + </defs> + </svg> <ReactFlow edges={elements.edges} nodes={elements.nodes} diff --git a/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx b/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx index 9ac746889c46692c1f85171944e0e3c4b5c8aca0..9bded7d29f68fbeae4fd1b4b5a52cd382a58cab1 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { EdgeProps, getSmoothStepPath, Position } from 'reactflow'; +import { EdgeProps, getSmoothStepPath, Position, Background, MarkerType, BaseEdge } from 'reactflow'; + import './connection.scss'; /** @@ -58,7 +59,7 @@ export function ConnectionLine({ id, sourceX, sourceY, targetX, targetY, style, return ( <g stroke="#2e2e2e"> - <path id={id} fill="none" strokeWidth={1.5} style={style} d={path[0]} /> + <path id={id} fill="none" strokeWidth={1} style={style} d={path[0]} /> </g> ); } diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ba88189a6b022d21cbe98d417c2f8cc066eca55e --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx @@ -0,0 +1,61 @@ +import { Handle, HandleType, Position } from 'reactflow'; +import { QueryGraphEdgeHandle, toHandleId } from '../../..'; +import { tailwindColors } from 'config'; + +const rightArrow = '0,0 8,5 0,10'; +const leftArrow = '8,0 0,5 8,10'; +const square = '9,9 1,9 1,1 9,1'; +const getArrow = { + right: rightArrow, + left: leftArrow, + both: square, +}; +export type RelationshipHandleArrowType = 'right' | 'left' | 'both'; + +type Props = { id: QueryGraphEdgeHandle; type: HandleType; point: RelationshipHandleArrowType; onDoubleClick?: () => void }; + +export const LeftHandle = (props: Props) => { + const offset = 15; + + return ( + <Handle + id={toHandleId(props.id)} + type={props.type} + position={Position.Left} + className="" + style={{ transform: `translate(${offset}px, -3px)` }} + onDoubleClickCapture={(e) => { + e.preventDefault(); + e.stopPropagation(); + if (props.onDoubleClick) props.onDoubleClick(); + }} + > + <svg className="pointer-events-none" height={10} style={{ transform: `translate(-2px, -3px)` }}> + <polygon points={getArrow[props.point]} fill={tailwindColors.relation[200]} /> + </svg> + </Handle> + ); +}; + +export const RightHandle = (props: Props) => { + const offset = -13; + + return ( + <Handle + id={toHandleId(props.id)} + type={props.type} + position={Position.Right} + className="" + style={{ transform: `translate(${offset}px, -3px)` }} + onDoubleClickCapture={(e) => { + e.preventDefault(); + e.stopPropagation(); + if (props.onDoubleClick) props.onDoubleClick(); + }} + > + <svg height={10} className="pointer-events-none" style={{ transform: `translate(-2px, -3px)` }}> + <polygon points={getArrow[props.point]} fill={tailwindColors.relation[200]} /> + </svg> + </Handle> + ); +}; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss deleted file mode 100644 index bd38e97d08cb3a012c6a0ea798b56c6c8be879b5..0000000000000000000000000000000000000000 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss +++ /dev/null @@ -1,117 +0,0 @@ -@import '../../querypills.module.scss'; - -$height: 10px; -$width: 325; -// Relation element - -.relation { - min-width: $width + px; - text-align: center; - font-weight: bold; - border-left: 3px solid; - @apply bg-relation-50; - @apply border-l-relation-600; - font-size: 13px; -} - -.relationWrapper { - display: inherit; - width: inherit; - align-items: center; - justify-content: space-between; -} - -.relationHandleTriangle { - border-radius: 0px !important; - background: transparent !important; - width: 0 !important; - height: 0 !important; - border-left: 5px solid transparent !important; - border-right: 5px solid transparent !important; - border-bottom: 8px solid !important; - @apply border-b-relation-200 #{!important}; -} - -.relationHandleLeft { - @extend .relationHandleTriangle; - transform: rotate(-90deg) translate(50%, 150%) !important; -} - -.relationHandleRight { - @extend .relationHandleTriangle; - transform: rotate(90deg) translate(-50%, 150%) !important; -} - -// .relationHandleAttribute { -// // SECOND ONE -// border-radius: 1px !important; -// left: 22.5px !important; -// background: rgba(255, 255, 255, 0.6) !important; -// transform: rotate(45deg) translate(-68%, 0) scale(0.9) !important; -// border-color: rgba(22, 110, 110, 1) !important; -// border-width: 1px !important; -// transform-origin: center, center; -// } - -// .relationHandleFunction { -// // THIRD ONE -// left: 39px !important; -// background: rgba(255, 255, 255, 0.6) !important; -// border-color: rgba(22, 110, 110, 1) !important; -// border-width: 1px !important; -// transform-origin: center, center; -// } - -.relationDataWrapper { - display: flex; - width: 100%; - justify-content: center; - - .relationHandleFiller { - flex: 1 1 0; - } - - .relationSpan { - float: left; - margin-left: 5; - } - - .relationInputHolder { - display: flex; - float: right; - margin-right: 20px; - margin-top: 4px; - margin-left: 5px; - max-width: 80px; - border-radius: 2px; - align-items: center; - max-height: 12px; - - .relationInput { - z-index: 1; - cursor: text; - min-width: 0px; - max-width: 1.5ch; - border: none; - background: transparent; - text-align: center; - font-size: 0.8rem; - user-select: none; - &:focus { - outline: none; - user-select: none; - } - &::placeholder { - outline: none; - user-select: none; - font-style: italic; - } - } - - .relationReadonly { - cursor: grab !important; - user-select: none; - font-style: normal !important; - } - } -} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts deleted file mode 100644 index 432ea72c9fc83e70e90bce2dbca2911362df4a5a..0000000000000000000000000000000000000000 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -declare const classNames: { - readonly handle: 'handle'; - readonly handle_logic: 'handle_logic'; - readonly handle_logic_duration: 'handle_logic_duration'; - readonly handle_logic_datetime: 'handle_logic_datetime'; - readonly handle_logic_time: 'handle_logic_time'; - readonly handle_logic_date: 'handle_logic_date'; - readonly handle_logic_bool: 'handle_logic_bool'; - readonly handle_logic_float: 'handle_logic_float'; - readonly handle_logic_int: 'handle_logic_int'; - readonly handle_logic_string: 'handle_logic_string'; - readonly 'react-flow__node': 'react-flow__node'; - readonly selected: 'selected'; - readonly entityWrapper: 'entityWrapper'; - readonly hidden: 'hidden'; - readonly 'react-flow__edges': 'react-flow__edges'; - readonly 'react-flow__edge-default': 'react-flow__edge-default'; - readonly handleConnectedFill: 'handleConnectedFill'; - readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; - readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; - readonly handleFunction: 'handleFunction'; - readonly relation: 'relation'; - readonly relationWrapper: 'relationWrapper'; - readonly relationHandleTriangle: 'relationHandleTriangle'; - readonly relationHandleRight: 'relationHandleRight'; - readonly relationHandleLeft: 'relationHandleLeft'; - readonly relationDataWrapper: 'relationDataWrapper'; - readonly relationHandleFiller: 'relationHandleFiller'; - readonly relationSpan: 'relationSpan'; - readonly relationInputHolder: 'relationInputHolder'; - readonly relationInput: 'relationInput'; - readonly relationReadonly: 'relationReadonly'; -}; -export = classNames; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx index 2dfb0e5371cf830d4ff827059c6e6ea93a19150f..6c67d686933392357762594925ec20bf0ffea63a 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx @@ -42,6 +42,7 @@ export const Default: StoryObj<{ data: RelationData }> = { name: 'TestEntity', collection: 'test', depth: { min: 0, max: 1 }, + direction: 'right', }, }, }; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx index dbda2f433602305b0bc42a70bf0ac74143a1432c..d1f3975ff1ca96cd561a28edd8c53271b314bbc5 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx @@ -1,7 +1,6 @@ import { memo, useRef, useState, useMemo, useEffect } from 'react'; import { Handle, Position } from 'reactflow'; import { RelationNodeAttributes, SchemaReactflowRelationNode, toHandleId } from '../../../model'; -import styles from './relationpill.module.scss'; import { setQuerybuilderNodes, useAppDispatch, @@ -12,6 +11,7 @@ import { import { addWarning } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import graphology from 'graphology'; +import { LeftHandle, RelationshipHandleArrowType, RightHandle } from './relation-handles'; /** * Component to render a relation flow element @@ -32,6 +32,8 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => { max: data.depth.max || settings.depth.max, }); + const [direction, setDirection] = useState<RelationshipHandleArrowType>('right'); + useEffect(() => { setDepth({ min: data.depth.min || settings.depth.min, max: data.depth.max || settings.depth.max }); }, [data.depth]); @@ -49,37 +51,32 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => { } }; + const onChangeDirection = () => { + if (direction === 'right') { + setDirection('both'); + graphologyGraph.setNodeAttribute<any>(node.id, 'direction', 'both'); + } else { + setDirection('right'); + graphologyGraph.setNodeAttribute<any>(node.id, 'direction', 'right'); + } + dispatch(setQuerybuilderGraphology(graphologyGraph)); + }; + const calcWidth = (data: number) => { return data.toString().length + 0.5 + 'ch'; }; return ( - <div className={styles.relation}> - <div className={styles.relationWrapper}> - <span className={styles.relationHandleFiller}> - {data.leftEntityHandleId && ( - <Handle id={toHandleId(data.leftEntityHandleId)} type="target" position={Position.Left} className={styles.relationHandleLeft} /> + <div className="text-center font-bold bg-relation-50 border-l-relation-600 border-l-[3px] text-[13px] min-w-[325px]"> + <div> + <span> + {data.rightEntityHandleId && ( + <RightHandle id={data.rightEntityHandleId} type="source" point={direction} onDoubleClick={onChangeDirection} /> )} </span> - {/* <span className={styles.relationHandleFiller}> - <Handle - id={getHandleId(data.name, data.type, Handles.ToAttribute, '')} - type="target" - position={Position.Left} - className={styles.relationHandleAttribute + ' ' + (false ? styles.handleConnectedFill : '')} - /> - </span> - <span className={styles.relationHandleFiller}> - <Handle - id={getHandleId(data.name, data.type, Handles.ReceiveFunction, '')} - type="target" - position={Position.Left} - className={styles.relationHandleFunction + ' ' + (false ? styles.handleConnectedFill : '')} - /> - </span> */} - <div className={styles.relationDataWrapper}> - <span className={styles.relationSpan}>{data?.name}</span> - <span className={styles.relationInputHolder}> + <div> + <span>{data?.name}</span> + <span> <span>[</span> <input className={ @@ -129,14 +126,9 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => { <span>]</span> </span> </div> - <span className={styles.relationHandleFiller}> - {data.rightEntityHandleId && ( - <Handle - id={toHandleId(data.rightEntityHandleId)} - type="source" - position={Position.Right} - className={styles.relationHandleRight + ' ' + (false ? styles.handleConnectedBorderRight : '')} - /> + <span> + {data.leftEntityHandleId && ( + <LeftHandle id={data.leftEntityHandleId} type="target" point={direction} onDoubleClick={onChangeDirection} /> )} </span> </div> diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts index 04b325bf18f4273106962a0e9e092fcc5944e77c..085dcba4efeb71339226e7159b6c52db3f36bf9d 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts @@ -595,7 +595,7 @@ describe('QueryUtils Entity and Relations', () => { node: { ID: 'e1', label: 'Airport 1', - relation: { direction: 'TO', node: { ID: 'e2', label: 'Airport 1' } }, + relation: { direction: 'BOTH', node: { ID: 'e2', label: 'Airport 1' } }, }, }, ], @@ -668,7 +668,7 @@ describe('QueryUtils Entity and Relations', () => { node: { ID: 'e1', label: 'Airport 1', - relation: { direction: 'TO', node: { ID: 'e2', label: 'Airport 1' } }, + relation: { direction: 'BOTH', node: { ID: 'e2', label: 'Airport 1' } }, }, }, { @@ -676,7 +676,7 @@ describe('QueryUtils Entity and Relations', () => { node: { ID: 'e2', label: 'Airport 1', - relation: { direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } }, + relation: { direction: 'BOTH', node: { ID: 'e1', label: 'Airport 1' } }, }, }, ], @@ -698,7 +698,7 @@ describe('QueryUtils Entity and Relations', () => { query: [ { ID: 'path_0', - node: { ID: 'e1', label: 'Airport 1', relation: { direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } } }, + node: { ID: 'e1', label: 'Airport 1', relation: { direction: 'BOTH', node: { ID: 'e1', label: 'Airport 1' } } }, }, ], }; diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts index e50c0d2d7b488133dab966eff5ac62df0bbc53ae..57b65c31d952c19211400f287fc8778e5c9284b1 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts @@ -42,6 +42,7 @@ const traverseEntityRelationPaths = ( x: node.attributes.x, y: node.attributes.x, depth: { min: settings.depth.min, max: settings.depth.max }, + direction: 'both', }); } else { paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x }); @@ -258,7 +259,7 @@ export function Query2BackendQuery( ID: _currNode.id, label: _currNode.name || undefined, depth: _currNode.depth, - direction: 'TO', + direction: !_currNode.direction || _currNode.direction === 'right' ? 'TO' : _currNode.direction === 'left' ? 'FROM' : 'BOTH', node: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as NodeStruct), }; return ret;