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( const createTable: ChartDBContext['createTable'] = useCallback(
async (attributes) => { 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 = { const table: DBTable = {
id: generateId(), id: generateId(),
name: `table_${tables.length + 1}`, name,
x: 0, x: 0,
y: 0, y: 0,
fields: [ fields: [

View File

@@ -78,6 +78,8 @@ import { DependencyEdge } from './dependency-edge/dependency-edge';
import { import {
BOTTOM_SOURCE_HANDLE_ID_PREFIX, BOTTOM_SOURCE_HANDLE_ID_PREFIX,
TARGET_DEP_PREFIX, TARGET_DEP_PREFIX,
TARGET_TOP_DEP_PREFIX,
TARGET_BOTTOM_DEP_PREFIX,
TOP_SOURCE_HANDLE_ID_PREFIX, TOP_SOURCE_HANDLE_ID_PREFIX,
} from './table-node/table-node-dependency-indicator'; } from './table-node/table-node-dependency-indicator';
import type { DatabaseType } from '@/lib/domain/database-type'; import type { DatabaseType } from '@/lib/domain/database-type';
@@ -583,14 +585,21 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
const onConnectHandler = useCallback( const onConnectHandler = useCallback(
async (params: AddEdgeParams) => { async (params: AddEdgeParams) => {
if ( // Check if this is a dependency connection (from a view to a table)
const isDependencyConnection =
params.sourceHandle?.startsWith?.( params.sourceHandle?.startsWith?.(
TOP_SOURCE_HANDLE_ID_PREFIX TOP_SOURCE_HANDLE_ID_PREFIX
) || ) ||
params.sourceHandle?.startsWith?.( params.sourceHandle?.startsWith?.(
BOTTOM_SOURCE_HANDLE_ID_PREFIX 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 tableId = params.target;
const dependentTableId = params.source; 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 TOP_SOURCE_HANDLE_ID_PREFIX = 'top_dep_';
export const BOTTOM_SOURCE_HANDLE_ID_PREFIX = 'bottom_dep_'; export const BOTTOM_SOURCE_HANDLE_ID_PREFIX = 'bottom_dep_';
export const TARGET_DEP_PREFIX = 'target_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 { export interface TableNodeDependencyIndicatorProps {
table: DBTable; table: DBTable;
@@ -23,17 +25,16 @@ export const TableNodeDependencyIndicator: React.FC<TableNodeDependencyIndicator
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const connection = useConnection(); const connection = useConnection();
const isTarget = useMemo( const isConnectionFromView = useMemo(
() => () =>
connection.inProgress && connection.inProgress &&
connection.fromNode.id !== table.id && (connection.fromHandle?.id?.startsWith(
(connection.fromHandle.id?.startsWith(
TOP_SOURCE_HANDLE_ID_PREFIX TOP_SOURCE_HANDLE_ID_PREFIX
) || ) ||
connection.fromHandle.id?.startsWith( connection.fromHandle?.id?.startsWith(
BOTTOM_SOURCE_HANDLE_ID_PREFIX BOTTOM_SOURCE_HANDLE_ID_PREFIX
)), )),
[connection, table.id] [connection.inProgress, connection.fromHandle?.id]
); );
const numberOfEdgesToTable = useMemo( const numberOfEdgesToTable = useMemo(
@@ -77,17 +78,38 @@ export const TableNodeDependencyIndicator: React.FC<TableNodeDependencyIndicator
type="target" type="target"
/> />
))} ))}
{isTarget ? ( {/* Show visible connection points at top and bottom when dragging from view */}
<Handle {!table.isView &&
id={`${TARGET_DEP_PREFIX}${numberOfEdgesToTable}_${table.id}`} !table.isMaterializedView &&
className={ isConnectionFromView ? (
isTarget <>
? '!absolute !left-0 !top-0 !h-full !w-full !transform-none !rounded-none !border-none !opacity-0' {/* Top connection point */}
: `!invisible` <Handle
} id={`${TARGET_TOP_DEP_PREFIX}${table.id}`}
position={Position.Top} className="!h-4 !w-4 !border-2 !bg-pink-600"
type="target" 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} ) : null}
</> </>
); );

View File

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