From 7ad0e7712de975a23b2a337dc0a4a7fb4b122bd1 Mon Sep 17 00:00:00 2001 From: Guy Ben-Aharon Date: Thu, 16 Oct 2025 17:37:20 +0300 Subject: [PATCH] fix: manipulate schema directly from the canvas (#947) --- .../canvas/canvas-context-menu.tsx | 91 ++++----- .../table-edit-mode/table-edit-mode.tsx | 185 ++++++++++++++++-- 2 files changed, 202 insertions(+), 74 deletions(-) diff --git a/src/pages/editor-page/canvas/canvas-context-menu.tsx b/src/pages/editor-page/canvas/canvas-context-menu.tsx index 3dd17548..6fcb8348 100644 --- a/src/pages/editor-page/canvas/canvas-context-menu.tsx +++ b/src/pages/editor-page/canvas/canvas-context-menu.tsx @@ -14,14 +14,14 @@ import { Table, Workflow, Group, View } from 'lucide-react'; import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter'; import { useLocalConfig } from '@/hooks/use-local-config'; import { useCanvas } from '@/hooks/use-canvas'; -import type { DBTable } from '@/lib/domain'; +import { defaultSchemas } from '@/lib/data/default-schemas'; export const CanvasContextMenu: React.FC = ({ children, }) => { - const { createTable, readonly, createArea } = useChartDB(); + const { createTable, readonly, createArea, databaseType } = useChartDB(); const { schemasDisplayed } = useDiagramFilter(); - const { openCreateRelationshipDialog, openTableSchemaDialog } = useDialog(); + const { openCreateRelationshipDialog } = useDialog(); const { screenToFlowPosition } = useReactFlow(); const { t } = useTranslation(); const { showDBViews } = useLocalConfig(); @@ -36,31 +36,24 @@ export const CanvasContextMenu: React.FC = ({ y: event.clientY, }); - let newTable: DBTable | null = null; - - if (schemasDisplayed.length > 1) { - openTableSchemaDialog({ - onConfirm: async ({ schema }) => { - newTable = await createTable({ - x: position.x, - y: position.y, - schema: schema.name, - }); - }, - schemas: schemasDisplayed, - }); - } else { - const schema = - schemasDisplayed?.length === 1 - ? schemasDisplayed[0]?.name - : undefined; - newTable = await createTable({ - x: position.x, - y: position.y, - schema, - }); + // Auto-select schema with priority: default schema > first displayed schema > undefined + let schema: string | undefined = undefined; + if (schemasDisplayed.length > 0) { + const defaultSchemaName = defaultSchemas[databaseType]; + const defaultSchemaInList = schemasDisplayed.find( + (s) => s.name === defaultSchemaName + ); + schema = defaultSchemaInList + ? defaultSchemaInList.name + : schemasDisplayed[0]?.name; } + const newTable = await createTable({ + x: position.x, + y: position.y, + schema, + }); + if (newTable) { setEditTableModeTable({ tableId: newTable.id }); } @@ -68,9 +61,9 @@ export const CanvasContextMenu: React.FC = ({ [ createTable, screenToFlowPosition, - openTableSchemaDialog, schemasDisplayed, setEditTableModeTable, + databaseType, ] ); @@ -81,33 +74,25 @@ export const CanvasContextMenu: React.FC = ({ y: event.clientY, }); - let newView: DBTable | null = null; - - if (schemasDisplayed.length > 1) { - openTableSchemaDialog({ - onConfirm: async ({ schema }) => { - newView = await createTable({ - x: position.x, - y: position.y, - schema: schema.name, - isView: true, - }); - }, - schemas: schemasDisplayed, - }); - } else { - const schema = - schemasDisplayed?.length === 1 - ? schemasDisplayed[0]?.name - : undefined; - newView = await createTable({ - x: position.x, - y: position.y, - schema, - isView: true, - }); + // Auto-select schema with priority: default schema > first displayed schema > undefined + let schema: string | undefined = undefined; + if (schemasDisplayed.length > 0) { + const defaultSchemaName = defaultSchemas[databaseType]; + const defaultSchemaInList = schemasDisplayed.find( + (s) => s.name === defaultSchemaName + ); + schema = defaultSchemaInList + ? defaultSchemaInList.name + : schemasDisplayed[0]?.name; } + const newView = await createTable({ + x: position.x, + y: position.y, + schema, + isView: true, + }); + if (newView) { setEditTableModeTable({ tableId: newView.id }); } @@ -115,9 +100,9 @@ export const CanvasContextMenu: React.FC = ({ [ createTable, screenToFlowPosition, - openTableSchemaDialog, schemasDisplayed, setEditTableModeTable, + databaseType, ] ); diff --git a/src/pages/editor-page/canvas/table-node/table-edit-mode/table-edit-mode.tsx b/src/pages/editor-page/canvas/table-node/table-edit-mode/table-edit-mode.tsx index 655b4590..c62f4ce3 100644 --- a/src/pages/editor-page/canvas/table-node/table-edit-mode/table-edit-mode.tsx +++ b/src/pages/editor-page/canvas/table-node/table-edit-mode/table-edit-mode.tsx @@ -1,7 +1,13 @@ import { Input } from '@/components/input/input'; import type { DBTable } from '@/lib/domain'; -import { FileType2, X } from 'lucide-react'; -import React, { useEffect, useState, useRef, useCallback } from 'react'; +import { FileType2, X, SquarePlus } from 'lucide-react'; +import React, { + useEffect, + useState, + useRef, + useCallback, + useMemo, +} from 'react'; import { TableEditModeField } from './table-edit-mode-field'; import { cn } from '@/lib/utils'; import { ScrollArea } from '@/components/scroll-area/scroll-area'; @@ -11,6 +17,14 @@ import { Separator } from '@/components/separator/separator'; import { useChartDB } from '@/hooks/use-chartdb'; import { useUpdateTable } from '@/hooks/use-update-table'; import { useTranslation } from 'react-i18next'; +import { SelectBox } from '@/components/select-box/select-box'; +import type { SelectBoxOption } from '@/components/select-box/select-box'; +import { + databasesWithSchemas, + schemaNameToSchemaId, +} from '@/lib/domain/db-schema'; +import type { DBSchema } from '@/lib/domain/db-schema'; +import { defaultSchemas } from '@/lib/data/default-schemas'; export interface TableEditModeProps { table: DBTable; @@ -25,7 +39,8 @@ export const TableEditMode: React.FC = React.memo( const scrollAreaRef = useRef(null); const fieldRefs = useRef>(new Map()); const [isVisible, setIsVisible] = useState(false); - const { createField, updateTable } = useChartDB(); + const { createField, updateTable, schemas, databaseType } = + useChartDB(); const { t } = useTranslation(); const { tableName, handleTableNameChange } = useUpdateTable(table); const [focusFieldId, setFocusFieldId] = useState( @@ -33,6 +48,39 @@ export const TableEditMode: React.FC = React.memo( ); const inputRef = useRef(null); + // Schema-related state + const [isCreatingNewSchema, setIsCreatingNewSchema] = useState(false); + const [newSchemaName, setNewSchemaName] = useState(''); + const [selectedSchemaId, setSelectedSchemaId] = useState(() => + table.schema ? schemaNameToSchemaId(table.schema) : '' + ); + + // Sync selectedSchemaId when table.schema changes + useEffect(() => { + setSelectedSchemaId( + table.schema ? schemaNameToSchemaId(table.schema) : '' + ); + }, [table.schema]); + + const supportsSchemas = useMemo( + () => databasesWithSchemas.includes(databaseType), + [databaseType] + ); + + const defaultSchemaName = useMemo( + () => defaultSchemas?.[databaseType], + [databaseType] + ); + + const schemaOptions: SelectBoxOption[] = useMemo( + () => + schemas.map((schema) => ({ + value: schema.id, + label: schema.name, + })), + [schemas] + ); + useEffect(() => { setFocusFieldId(focusFieldIdProp); if (!focusFieldIdProp) { @@ -115,6 +163,43 @@ export const TableEditMode: React.FC = React.memo( [updateTable, table.id] ); + const handleSchemaChange = useCallback( + (schemaId: string) => { + const schema = schemas.find((s) => s.id === schemaId); + if (schema) { + updateTable(table.id, { schema: schema.name }); + setSelectedSchemaId(schemaId); + } + }, + [schemas, updateTable, table.id] + ); + + const handleCreateNewSchema = useCallback(() => { + if (newSchemaName.trim()) { + const trimmedName = newSchemaName.trim(); + const newSchema: DBSchema = { + id: schemaNameToSchemaId(trimmedName), + name: trimmedName, + tableCount: 0, + }; + updateTable(table.id, { schema: newSchema.name }); + setSelectedSchemaId(newSchema.id); + setIsCreatingNewSchema(false); + setNewSchemaName(''); + } + }, [newSchemaName, updateTable, table.id]); + + const handleToggleSchemaMode = useCallback(() => { + if (isCreatingNewSchema && newSchemaName.trim()) { + // If we're leaving create mode with a value, create the schema + handleCreateNewSchema(); + } else { + // Otherwise just toggle modes + setIsCreatingNewSchema(!isCreatingNewSchema); + setNewSchemaName(''); + } + }, [isCreatingNewSchema, newSchemaName, handleCreateNewSchema]); + return (
= React.memo( onClick={(e) => e.stopPropagation()} >
-
+
- e.stopPropagation()} - popoverOnClick={(e) => e.stopPropagation()} - /> + {supportsSchemas && !isCreatingNewSchema && ( + + handleSchemaChange(value as string) + } + placeholder={ + defaultSchemaName || 'Select schema' + } + className="h-6 min-h-6 w-20 shrink-0 rounded-sm border-slate-600 bg-background py-0 pl-2 pr-0.5 text-sm" + popoverClassName="w-[200px]" + commandOnMouseDown={(e) => e.stopPropagation()} + commandOnClick={(e) => e.stopPropagation()} + footerButtons={ + + } + /> + )} + {supportsSchemas && isCreatingNewSchema && ( + + setNewSchemaName(e.target.value) + } + placeholder={`Enter schema name${defaultSchemaName ? ` (e.g. ${defaultSchemaName})` : ''}`} + className="h-6 w-28 shrink-0 rounded-sm border-slate-600 bg-background text-sm" + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleCreateNewSchema(); + } else if (e.key === 'Escape') { + handleToggleSchemaMode(); + } + }} + onBlur={handleToggleSchemaMode} + autoFocus + /> + )} = React.memo( -
- +
+
+ {!table.isView ? ( + <> + + e.stopPropagation() + } + popoverOnClick={(e) => e.stopPropagation()} + /> + + ) : ( +
+ )} + +
{table.fields.length}{' '} {t('side_panel.tables_section.table.fields')}