Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,78 @@ export const defaultDataModelEdgeOptions: DefaultEdgeOptions = {
},
};

export type DataModelHandleSide = 'l' | 'r';

export function relatedHandleId(fieldName: string, side: DataModelHandleSide) {
return `related:${fieldName}:${side}`;
}

export function belongsToHandleId(side: DataModelHandleSide) {
return `belongs_to:header:${side}`;
}

export function getLinkToSingleDataEdgeId(linkToSingleData: LinkToSingleData) {
const { original } = linkToSingleData;
return original.childTableId + original.name;
}

/** Default LR wiring (child right → parent left); geometry may retarget sides later. */
export function getLinkToSingleDataEdge(linkToSingleData: LinkToSingleData) {
const { original } = linkToSingleData;
const isRelated = original.relationType === 'related';
return {
source: original.childTableName,
sourceHandle: isRelated ? `related:${original.childFieldName}` : 'belongs_to:header',
sourceHandle: isRelated ? relatedHandleId(original.childFieldName, 'r') : belongsToHandleId('r'),
target: original.parentTableName,
targetHandle: isRelated ? `related:${original.parentFieldName}` : 'belongs_to:header',
targetHandle: isRelated ? relatedHandleId(original.parentFieldName, 'l') : belongsToHandleId('l'),
};
}

/** Pick left/right handles from node centers (Δx ≥ 0 → source right / target left). */
export function retargetDataModelHandles<
N extends {
id: string;
position: { x: number; y: number };
measured?: { width?: number | null; height?: number | null } | null;
width?: number | null;
height?: number | null;
},
E extends {
source: string;
target: string;
sourceHandle?: string | null;
targetHandle?: string | null;
data?: LinkToSingleData | null;
},
>(nodes: N[], edges: E[]): E[] {
const centers = new Map<string, { x: number; y: number }>();
for (const node of nodes) {
const width = node.measured?.width ?? node.width ?? 0;
const height = node.measured?.height ?? node.height ?? 0;
centers.set(node.id, { x: node.position.x + width / 2, y: node.position.y + height / 2 });
}

return edges.map((edge) => {
const from = centers.get(edge.source);
const to = centers.get(edge.target);
const original = edge.data?.original;
if (!from || !to || !original) return edge;

const sourceSide: DataModelHandleSide = to.x - from.x >= 0 ? 'r' : 'l';
const targetSide: DataModelHandleSide = to.x - from.x >= 0 ? 'l' : 'r';
const isRelated = original.relationType === 'related';
const sourceHandle = isRelated
? relatedHandleId(original.childFieldName, sourceSide)
: belongsToHandleId(sourceSide);
const targetHandle = isRelated
? relatedHandleId(original.parentFieldName, targetSide)
: belongsToHandleId(targetSide);

if (edge.sourceHandle === sourceHandle && edge.targetHandle === targetHandle) return edge;
return { ...edge, sourceHandle, targetHandle };
});
}

export function LinkRelation({
id,
sourceX,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,47 @@ import { adaptUpdateTableValue } from '../EditTable/updateTable-adapter';
import { DatatypeIcon } from '../Shared/DatatypeOption';
import { ChangeRecord, LinkValue, SemanticTableFormValues } from '../Shared/semanticData-types';
import { UploadTableDrawer } from '../UploadData/UploadTableDrawer';
import { belongsToHandleId, relatedHandleId } from './LinkRelation';
import { TableRecordPreviewDrawer } from './TableRecordPreviewDrawer';
import '@xyflow/react/dist/style.css';

const transparentHandleStyle = { background: 'transparent', border: 'none' } as const;

function SideHandles({ leftId, rightId }: { leftId: string; rightId: string }) {
return (
<>
<Handle
type="target"
id={leftId}
position={Position.Left}
isConnectable={false}
style={{ ...transparentHandleStyle, left: 'calc(-1 * var(--spacing-md))' }}
/>
<Handle
type="source"
id={leftId}
position={Position.Left}
isConnectable={false}
style={{ ...transparentHandleStyle, left: 'calc(-1 * var(--spacing-md))' }}
/>
<Handle
type="target"
id={rightId}
position={Position.Right}
isConnectable={false}
style={{ ...transparentHandleStyle, right: 'calc(-1 * var(--spacing-md))' }}
/>
<Handle
type="source"
id={rightId}
position={Position.Right}
isConnectable={false}
style={{ ...transparentHandleStyle, right: 'calc(-1 * var(--spacing-md))' }}
/>
</>
);
}

export interface TableDetailsProps {
tableModel: TableModel;
relationFieldNames: string[];
Expand Down Expand Up @@ -93,27 +131,12 @@ export function TableDetails({ data }: NodeProps<TableDetailsFlowNode>) {
)}
>
{hasHandles && (
<Handle
type="target"
id={`related:${field.name}`}
position={Position.Left}
isConnectable={false}
style={{ background: 'transparent', border: 'none', left: 'calc(-1 * var(--spacing-md))' }}
/>
<SideHandles leftId={relatedHandleId(field.name, 'l')} rightId={relatedHandleId(field.name, 'r')} />
)}
<div className="flex items-center gap-xs justify-between">
<span title={field.name}>{field.alias || field.name}</span>
{extended && field.dataType !== 'String' && <DatatypeIcon dataType={field.dataType as PrimitiveTypes} />}
</div>
{hasHandles && (
<Handle
type="source"
id={`related:${field.name}`}
position={Position.Right}
isConnectable={false}
style={{ background: 'transparent', border: 'none', right: 'calc(-1 * var(--spacing-md))' }}
/>
)}
</div>
);
};
Expand Down Expand Up @@ -168,17 +191,7 @@ export function TableDetails({ data }: NodeProps<TableDetailsFlowNode>) {
<>
<div className="relative border border-purple-border-light bg-purple-background-light rounded-lg px-md py-xl">
<div className="relative flex items-center gap-sm">
<Handle
type="target"
id="belongs_to:header"
position={Position.Left}
isConnectable={false}
style={{
background: 'transparent',
border: 'none',
left: 'calc(-1 * var(--spacing-md))',
}}
/>
<SideHandles leftId={belongsToHandleId('l')} rightId={belongsToHandleId('r')} />
<div className="flex-1 flex flex-col gap-xs">
<Typo variant="subtitle2" className="text-purple-primary" title={data.tableModel.name}>
{data.tableModel.alias || data.tableModel.name}
Expand Down Expand Up @@ -242,17 +255,6 @@ export function TableDetails({ data }: NodeProps<TableDetailsFlowNode>) {
</MenuCommand.List>
</MenuCommand.Content>
</MenuCommand.Menu>
<Handle
type="source"
id="belongs_to:header"
position={Position.Right}
isConnectable={false}
style={{
background: 'transparent',
border: 'none',
right: 'calc(-1 * var(--spacing-md))',
}}
/>
</div>
<div className="mt-md flex flex-col gap-sm">
{isNumberOfFieldsOpen ? (
Expand Down
Loading