Compare commits

...

2 Commits

Author SHA1 Message Date
johnnyfish
830c0aca7c fix: update UX for connecting view to tables 2025-08-26 14:10:41 +03:00
johnnyfish
847acd681c fix: create view with view_1 instead of table_1 2025-08-26 14:10:41 +03:00
4 changed files with 86 additions and 24 deletions

View File

@@ -325,9 +325,20 @@ export const ChartDBProvider: React.FC<
const createTable: ChartDBContext['createTable'] = useCallback(
async (attributes) => {
const isView = attributes?.isView || false;
let name: string;
if (isView) {
const viewCount = tables.filter((t) => t.isView).length;
name = `view_${viewCount + 1}`;
} else {
const tableCount = tables.filter((t) => !t.isView).length;
name = `table_${tableCount + 1}`;
}
const table: DBTable = {
id: generateId(),
name: `table_${tables.length + 1}`,
name,
x: 0,
y: 0,
fields: [

View File

@@ -78,6 +78,8 @@ import { DependencyEdge } from './dependency-edge/dependency-edge';
import {
BOTTOM_SOURCE_HANDLE_ID_PREFIX,
TARGET_DEP_PREFIX,
TARGET_TOP_DEP_PREFIX,
TARGET_BOTTOM_DEP_PREFIX,
TOP_SOURCE_HANDLE_ID_PREFIX,
} from './table-node/table-node-dependency-indicator';
import type { DatabaseType } from '@/lib/domain/database-type';
@@ -583,14 +585,21 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
const onConnectHandler = useCallback(
async (params: AddEdgeParams) => {
if (
// Check if this is a dependency connection (from a view to a table)
const isDependencyConnection =
params.sourceHandle?.startsWith?.(
TOP_SOURCE_HANDLE_ID_PREFIX
) ||
params.sourceHandle?.startsWith?.(
BOTTOM_SOURCE_HANDLE_ID_PREFIX
)
) {
);
const isDependencyTarget =
params.targetHandle?.startsWith?.(TARGET_TOP_DEP_PREFIX) ||
params.targetHandle?.startsWith?.(TARGET_BOTTOM_DEP_PREFIX) ||
params.targetHandle?.startsWith?.(TARGET_DEP_PREFIX);
if (isDependencyConnection || isDependencyTarget) {
const tableId = params.target;
const dependentTableId = params.source;

View File

@@ -11,6 +11,8 @@ import React, { useEffect, useMemo, useRef } from 'react';
export const TOP_SOURCE_HANDLE_ID_PREFIX = 'top_dep_';
export const BOTTOM_SOURCE_HANDLE_ID_PREFIX = 'bottom_dep_';
export const TARGET_DEP_PREFIX = 'target_dep_';
export const TARGET_TOP_DEP_PREFIX = 'target_top_dep_';
export const TARGET_BOTTOM_DEP_PREFIX = 'target_bottom_dep_';
export interface TableNodeDependencyIndicatorProps {
table: DBTable;
@@ -23,17 +25,16 @@ export const TableNodeDependencyIndicator: React.FC<TableNodeDependencyIndicator
const updateNodeInternals = useUpdateNodeInternals();
const connection = useConnection();
const isTarget = useMemo(
const isConnectionFromView = useMemo(
() =>
connection.inProgress &&
connection.fromNode.id !== table.id &&
(connection.fromHandle.id?.startsWith(
(connection.fromHandle?.id?.startsWith(
TOP_SOURCE_HANDLE_ID_PREFIX
) ||
connection.fromHandle.id?.startsWith(
connection.fromHandle?.id?.startsWith(
BOTTOM_SOURCE_HANDLE_ID_PREFIX
)),
[connection, table.id]
[connection.inProgress, connection.fromHandle?.id]
);
const numberOfEdgesToTable = useMemo(
@@ -77,17 +78,38 @@ export const TableNodeDependencyIndicator: React.FC<TableNodeDependencyIndicator
type="target"
/>
))}
{isTarget ? (
<Handle
id={`${TARGET_DEP_PREFIX}${numberOfEdgesToTable}_${table.id}`}
className={
isTarget
? '!absolute !left-0 !top-0 !h-full !w-full !transform-none !rounded-none !border-none !opacity-0'
: `!invisible`
}
position={Position.Top}
type="target"
/>
{/* Show visible connection points at top and bottom when dragging from view */}
{!table.isView &&
!table.isMaterializedView &&
isConnectionFromView ? (
<>
{/* Top connection point */}
<Handle
id={`${TARGET_TOP_DEP_PREFIX}${table.id}`}
className="!h-4 !w-4 !border-2 !bg-pink-600"
style={{
position: 'absolute',
top: '-8px',
left: '50%',
transform: 'translateX(-50%)',
}}
position={Position.Top}
type="target"
/>
{/* Bottom connection point */}
<Handle
id={`${TARGET_BOTTOM_DEP_PREFIX}${table.id}`}
className="!h-4 !w-4 !border-2 !bg-pink-600"
style={{
position: 'absolute',
bottom: '-8px',
left: '50%',
transform: 'translateX(-50%)',
}}
position={Position.Bottom}
type="target"
/>
</>
) : null}
</>
);

View File

@@ -11,6 +11,10 @@ import {
useConnection,
useUpdateNodeInternals,
} from '@xyflow/react';
import {
TOP_SOURCE_HANDLE_ID_PREFIX,
BOTTOM_SOURCE_HANDLE_ID_PREFIX,
} from './table-node-dependency-indicator';
import { Button } from '@/components/button/button';
import {
Check,
@@ -87,12 +91,28 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
const updateNodeInternals = useUpdateNodeInternals();
const connection = useConnection();
// Check if connection is from a view (dependency connection)
const isConnectionFromView = useMemo(
() =>
connection.inProgress &&
(connection.fromHandle?.id?.startsWith(
TOP_SOURCE_HANDLE_ID_PREFIX
) ||
connection.fromHandle?.id?.startsWith(
BOTTOM_SOURCE_HANDLE_ID_PREFIX
)),
[connection.inProgress, connection.fromHandle?.id]
);
const isTarget = useMemo(
() =>
connection.inProgress &&
connection.fromNode.id !== tableNodeId &&
(connection.fromHandle.id?.startsWith(RIGHT_HANDLE_ID_PREFIX) ||
connection.fromHandle.id?.startsWith(
connection.fromNode?.id !== tableNodeId &&
(connection.fromHandle?.id?.startsWith(
RIGHT_HANDLE_ID_PREFIX
) ||
connection.fromHandle?.id?.startsWith(
LEFT_HANDLE_ID_PREFIX
)),
[
@@ -285,7 +305,7 @@ export const TableNodeField: React.FC<TableNodeFieldProps> = React.memo(
}
)}
>
{isConnectable ? (
{isConnectable && !isConnectionFromView ? (
<>
<Handle
id={`${RIGHT_HANDLE_ID_PREFIX}${field.id}`}