import { PermissionAction } from '@supabase/shared-types/out/constants' import { Handle, Node, NodeProps } from '@xyflow/react' import { TableEditor } from 'icons' import { Copy, DiamondIcon, Edit, Fingerprint, Hash, InfoIcon, Key, MoreVertical, Table2, } from 'lucide-react' import { useRouter } from 'next/router' import { toast } from 'sonner' import { Button, cn, copyToClipboard, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { useSchemaGraphContext } from './SchemaGraphContext' import { TableNodeData } from './Schemas.constants' import { getTableDefinitionAsMarkdown } from './Schemas.utils' import { buildTableEditorUrl } from '@/components/grid/BrivenGrid.utils' import { getTableDefinition } from '@/data/database/table-definition-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { formatSql } from '@/lib/formatSql' // ReactFlow is scaling everything by the factor of 2 export const TABLE_NODE_WIDTH = 320 export const TABLE_NODE_ROW_HEIGHT = 40 export const TableNode = ({ id, data, targetPosition, sourcePosition, placeholder, }: NodeProps> & { placeholder?: boolean }) => { // Important styles is a nasty hack to use Handles (required for edges calculations), but do not show them in the UI. // ref: https://github.com/wbkd/react-flow/discussions/2698 const hiddenNodeConnector = 'h-px! w-px! min-w-0! min-h-0! cursor-grab! border-0! opacity-0!' const schemaGraphContext = useSchemaGraphContext() const { data: project } = useSelectedProjectQuery() const { can: canUpdateColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) const router = useRouter() const itemHeight = 'h-[22px]' const hasEdgesSelected = schemaGraphContext.selectedEdge?.source === id || schemaGraphContext.selectedEdge?.target === id return (
{data.isForeign ? (
{data.name} {targetPosition && ( )}
) : (
{data.name}
{ // Hide the actions while downloading the schema as png/svg !schemaGraphContext.isDownloading ? (
{data.description && ( {data.description} )} {!placeholder && ( schemaGraphContext.onEditTable(data.id)} >

Edit table

router.push( buildTableEditorUrl({ projectRef: project?.ref, tableId: data.id, schema: data.schema, }) ) } >

View in Table Editor

{ e.stopPropagation() copyToClipboard(data.name) }} > Copy name { e.stopPropagation() const toastId = toast.loading('Getting table schema...') const formattedSchema = getTableDefinition({ id: data.id, projectRef: project?.ref, connectionString: project?.connectionString, }).then((tableDefinition) => { if (!tableDefinition) { throw new Error('Failed to get table schema') } return formatSql(tableDefinition) }) try { await copyToClipboard(formattedSchema, () => { toast.success('Table schema copied to clipboard', { id: toastId }) }) } catch (err) { toast.error( 'Failed to copy schema: ' + ((err as Error).message || err), { id: toastId, } ) } }} > Copy as SQL { e.stopPropagation() const markdown = getTableDefinitionAsMarkdown(data) try { await copyToClipboard(markdown, () => { toast.success('Table schema copied to clipboard') }) } catch (err) { toast.error( 'Failed to copy schema: ' + ((err as Error).message || err) ) } }} > Copy as Markdown
)}
) : null }
{data.columns.map((column) => (
{column.isPrimary && ( )} {column.isNullable && ( )} {!column.isNullable && ( )} {column.isUnique && ( )} {column.isIdentity && ( )}
{column.name} {column.format}
{targetPosition && ( )} {sourcePosition && ( )} schemaGraphContext.onEditColumn(data.id, column.id)} className="space-x-2" >

Edit column

{!canUpdateColumns && ( Additional permissions required to edit column )}
{ e.stopPropagation() copyToClipboard(column.name) }} > Copy name
))}
)}
) }