fix(expanded-table): persist expanded state across renders (#707)

* fix(expanded-table): persist expanded state across renders

* fix(reorder-tables): account for table sizes when reordering

* some fixes

---------

Co-authored-by: Guy Ben-Aharon <baguy3@gmail.com>
This commit is contained in:
Jonathan Fishner
2025-05-20 12:04:11 +03:00
committed by GitHub
parent 481ad3c844
commit 54d5e96a6d
4 changed files with 83 additions and 21 deletions

View File

@@ -25,6 +25,7 @@ import {
import { DatabaseType } from './database-type';
import type { DatabaseMetadata } from '../data/import-metadata/metadata-types/database-metadata';
import { z } from 'zod';
import { getTableDimensions } from '@/pages/editor-page/canvas/canvas-utils';
export interface DBTable {
id: string;
@@ -41,6 +42,7 @@ export interface DBTable {
width?: number;
comments?: string;
order?: number;
expanded?: boolean;
}
export const dbTableSchema: z.ZodType<DBTable> = z.object({
@@ -58,6 +60,7 @@ export const dbTableSchema: z.ZodType<DBTable> = z.object({
width: z.number().optional(),
comments: z.string().optional(),
order: z.number().optional(),
expanded: z.boolean().optional(),
});
export const shouldShowTablesBySchemaFilter = (
@@ -175,8 +178,8 @@ export const adjustTablePositions = ({
const relationships = deepCopy(inputRelationships);
const adjustPositionsForTables = (tablesToAdjust: DBTable[]) => {
const tableWidth = 200;
const tableHeight = 300;
const defaultTableWidth = 200;
const defaultTableHeight = 300;
const gapX = 100;
const gapY = 100;
const startX = 100;
@@ -205,6 +208,20 @@ export const adjustTablePositions = ({
const positionedTables = new Set<string>();
const tablePositions = new Map<string, { x: number; y: number }>();
const getTableWidthAndHeight = (
tableId: string
): {
width: number;
height: number;
} => {
const table = tablesToAdjust.find((t) => t.id === tableId);
if (!table)
return { width: defaultTableWidth, height: defaultTableHeight };
return getTableDimensions(table);
};
const isOverlapping = (
x: number,
y: number,
@@ -212,9 +229,11 @@ export const adjustTablePositions = ({
): boolean => {
for (const [tableId, pos] of tablePositions) {
if (tableId === currentTableId) continue;
const { width, height } = getTableWidthAndHeight(tableId);
if (
Math.abs(x - pos.x) < tableWidth + gapX &&
Math.abs(y - pos.y) < tableHeight + gapY
Math.abs(x - pos.x) < width + gapX &&
Math.abs(y - pos.y) < height + gapY
) {
return true;
}
@@ -227,7 +246,8 @@ export const adjustTablePositions = ({
baseY: number,
tableId: string
): { x: number; y: number } => {
const spiralStep = Math.max(tableWidth, tableHeight) / 2;
const { width, height } = getTableWidthAndHeight(tableId);
const spiralStep = Math.max(width, height) / 2;
let angle = 0;
let radius = 0;
let iterations = 0;
@@ -279,10 +299,21 @@ export const adjustTablePositions = ({
(t) => t.id === connectedTableId
);
if (connectedTable) {
const { width: tableWidth, height: tableHeight } =
getTableWidthAndHeight(table.id);
const {
width: connectedTableWidth,
height: connectedTableHeight,
} = getTableWidthAndHeight(connectedTableId);
const avgWidth = (tableWidth + connectedTableWidth) / 2;
const avgHeight =
(tableHeight + connectedTableHeight) / 2;
const newX =
x + Math.cos(angle) * (tableWidth + gapX * 2);
x + Math.cos(angle) * (avgWidth + gapX * 2);
const newY =
y + Math.sin(angle) * (tableHeight + gapY * 2);
y + Math.sin(angle) * (avgHeight + gapY * 2);
positionTable(connectedTable, newX, newY);
angle += angleStep;
}
@@ -295,6 +326,9 @@ export const adjustTablePositions = ({
if (!positionedTables.has(table.id)) {
const row = Math.floor(index / 6);
const col = index % 6;
const { width: tableWidth, height: tableHeight } =
getTableWidthAndHeight(table.id);
const x = startX + col * (tableWidth + gapX * 2);
const y = startY + row * (tableHeight + gapY * 2);
positionTable(table, x, y);

View File

@@ -1,5 +1,9 @@
import type { Cardinality } from '@/lib/domain/db-relationship';
import { MIN_TABLE_SIZE, type TableNodeType } from './table-node/table-node';
import {
MIN_TABLE_SIZE,
TABLE_MINIMIZED_FIELDS,
type TableNodeType,
} from './table-node/table-node';
import { addEdge, createGraph, removeEdge, type Graph } from '@/lib/graph';
import type { DBTable } from '@/lib/domain/db-table';
@@ -27,9 +31,8 @@ const calcRect = ({
table?.width ??
MIN_TABLE_SIZE;
const height = node
? (node?.measured?.height ??
calcTableHeight(node?.data.table.fields.length ?? 0))
: calcTableHeight(table?.fields.length ?? 0);
? (node?.measured?.height ?? calcTableHeight(node.data.table))
: calcTableHeight(table);
return {
id,
@@ -108,17 +111,35 @@ export const findOverlappingTables = ({
return graph;
};
export const calcTableHeight = (fieldCount: number): number => {
const fieldHeight = 32; // h-8 per field
export const calcTableHeight = (table?: DBTable): number => {
if (!table) {
return 300;
}
return Math.min(fieldCount, 11) * fieldHeight + 48;
const FIELD_HEIGHT = 32; // h-8 per field
const TABLE_FOOTER_HEIGHT = 32; // h-8 for show more button
const TABLE_HEADER_HEIGHT = 42;
// Calculate how many fields are visible
const fieldCount = table.fields.length;
let visibleFieldCount = fieldCount;
// If not expanded, use minimum of field count and TABLE_MINIMIZED_FIELDS
if (!table.expanded) {
visibleFieldCount = Math.min(fieldCount, TABLE_MINIMIZED_FIELDS);
}
// Calculate height based on visible fields
const fieldsHeight = visibleFieldCount * FIELD_HEIGHT;
const showMoreButtonHeight =
fieldCount > TABLE_MINIMIZED_FIELDS ? TABLE_FOOTER_HEIGHT : 0;
return TABLE_HEADER_HEIGHT + fieldsHeight + showMoreButtonHeight;
};
export const getTableDimensions = (
table: DBTable
): { width: number; height: number } => {
const fieldCount = table.fields.length;
const height = calcTableHeight(fieldCount);
const height = calcTableHeight(table);
const width = table.width || MIN_TABLE_SIZE;
return { width, height };
};

View File

@@ -760,7 +760,10 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
const measured = {
...(node.measured ?? {}),
height: calcTableHeight(event.data.fields.length),
height: calcTableHeight({
...node.data.table,
fields: event.data.fields,
}),
};
newOverlappingGraph = findTableOverlapping(

View File

@@ -60,7 +60,7 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
const { updateTable, relationships, readonly } = useChartDB();
const edges = useStore((store) => store.edges) as EdgeType[];
const { openTableFromSidebar, selectSidebarSection } = useLayout();
const [expanded, setExpanded] = useState(false);
const [expanded, setExpanded] = useState(table.expanded ?? false);
const { t } = useTranslation();
const [editMode, setEditMode] = useState(false);
const [tableName, setTableName] = useState(table.name);
@@ -138,9 +138,13 @@ export const TableNode: React.FC<NodeProps<TableNodeType>> = React.memo(
});
}, [table.id, updateTable]);
const toggleExpand = () => {
setExpanded(!expanded);
};
const toggleExpand = useCallback(() => {
setExpanded((prev) => {
const value = !prev;
updateTable(table.id, { expanded: value });
return value;
});
}, [table.id, updateTable]);
const isMustDisplayedField = useCallback(
(field: DBField) => {